Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tls: enable multiple SSL certificate support. #5317

Merged
merged 16 commits into from
Dec 17, 2018
Merged
7 changes: 4 additions & 3 deletions api/envoy/api/v2/auth/cert.proto
Original file line number Diff line number Diff line change
Expand Up @@ -227,16 +227,17 @@ message CommonTlsContext {
// TLS protocol versions, cipher suites etc.
TlsParameters tls_params = 1;

// Multiple TLS certificates can be associated with the same context.
// E.g. to allow both RSA and ECDSA certificates, two TLS certificates can be configured.
// :ref:`Multiple TLS certificates <arch_overview_ssl_cert_select>` can be associated with the
// same context to allow both RSA and ECDSA certificates.
//
// Only a single TLS certificate is supported in client contexts. In server contexts, the first
// RSA certificate is used for clients that only support RSA and the first ECDSA certificate is
// used for clients that support ECDSA.
repeated TlsCertificate tls_certificates = 2;

// Configs for fetching TLS certificates via SDS API.
repeated SdsSecretConfig tls_certificate_sds_secret_configs = 6;
repeated SdsSecretConfig tls_certificate_sds_secret_configs = 6
[(validate.rules).repeated .max_items = 1];

message CombinedCertificateValidationContext {
// How to validate peer certificates.
Expand Down
22 changes: 22 additions & 0 deletions docs/root/intro/arch_overview/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,28 @@ standard Debian installations. Common paths for system CA bundles on Linux and B
See the reference for :ref:`UpstreamTlsContexts <envoy_api_msg_auth.UpstreamTlsContext>` and
:ref:`DownstreamTlsContexts <envoy_api_msg_auth.DownstreamTlsContext>` for other TLS options.

.. _arch_overview_ssl_cert_select:

Certificate selection
---------------------

:ref:`DownstreamTlsContexts <envoy_api_msg_auth.DownstreamTlsContext>` support multiple TLS
certificates. These may be a mix of RSA and P-256 ECDSA certificates. The following rules apply:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that I asked about this before, but could we enforce that at most single RSA and single P-256 ECDSA are configured? We know that anything more is never going to be used at runtime, so it's most likely misconfiguration. This should only require a few easy checks in ContextImpl() and ServerContextConfigImpl().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, OK, let's go with this, it simplifies other logic later on.


* Only one certificate of a particular type (RSA or ECDSA) may be specified.
* Non-P-256 server ECDSA certificates are rejected.
* If the client supports P-256 ECDSA, a P-256 ECDSA certificate will be selected if present in the
:ref:`DownstreamTlsContext <envoy_api_msg_auth.DownstreamTlsContext>`.
* If the client only supports RSA certificates, a RSA certificate will be selected if present in the
:ref:`DownstreamTlsContext <envoy_api_msg_auth.DownstreamTlsContext>`.
* Otherwise, the first certificate listed is used. This will result in a failed handshake if the
client only supports RSA certificates and the server only has ECDSA certificates.
* Static and SDS certificates may not be mixed in a given :ref:`DownstreamTlsContext
<envoy_api_msg_auth.DownstreamTlsContext>`.

Only a single TLS certificate is supported today for :ref:`UpstreamTlsContexts
<envoy_api_msg_auth.UpstreamTlsContext>`.

Secret discovery service (SDS)
------------------------------

Expand Down
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ Version history
* tls: added ssl.versions.<version> to :ref:`listener metrics <config_listener_stats>` to track TLS versions in use.
* tls: added support for :ref:`client-side session resumption <envoy_api_field_auth.UpstreamTlsContext.max_session_keys>`.
* tls: added support for CRLs in :ref:`trusted_ca <envoy_api_field_auth.CertificateValidationContext.trusted_ca>`.
* tls: added support for :ref:`multiple server TLS certificates <arch_overview_ssl_cert_select>`.
* tls: added support for :ref:`password encrypted private keys <envoy_api_field_auth.TlsCertificate.password>`.
* tls: removed support for ECDSA certificates with curves other than P-256.
* tls: removed support for RSA certificates with keys smaller than 2048-bits.
Expand Down
7 changes: 3 additions & 4 deletions source/common/ssl/context_config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -308,13 +308,12 @@ ServerContextConfigImpl::ServerContextConfigImpl(

return ret;
}()) {
// TODO(PiotrSikora): Support multiple TLS certificates.
if ((config.common_tls_context().tls_certificates().size() +
config.common_tls_context().tls_certificate_sds_secret_configs().size()) == 0) {
throw EnvoyException("No TLS certificates found for server context");
} else if ((config.common_tls_context().tls_certificates().size() +
config.common_tls_context().tls_certificate_sds_secret_configs().size()) > 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I recall, we still only support single certificate served over SDS, don't we?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you may have multiple sds config for single cluster/listener? https://github.com/envoyproxy/envoy/blob/master/api/envoy/api/v2/auth/cert.proto#L239

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it doesn't work with multiple certificates, because callback clears all other certificates on update, see:

// This breaks multiple certificate support, but today SDS is only single cert.

throw EnvoyException("A single TLS certificate is required for server contexts");
} else if (!config.common_tls_context().tls_certificates().empty() &&
!config.common_tls_context().tls_certificate_sds_secret_configs().empty()) {
throw EnvoyException("SDS and non-SDS TLS certificates may not be mixed in server contexts");
}
}

Expand Down
14 changes: 11 additions & 3 deletions source/common/ssl/context_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const ContextConfig& config, TimeS
}
}

std::unordered_set<int> cert_pkey_ids;
for (uint32_t i = 0; i < tls_certificates.size(); ++i) {
auto& ctx = tls_contexts_[i];
// Load certificate chain.
Expand Down Expand Up @@ -247,15 +248,22 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const ContextConfig& config, TimeS
}

bssl::UniquePtr<EVP_PKEY> public_key(X509_get_pubkey(ctx.cert_chain_.get()));
switch (EVP_PKEY_id(public_key.get())) {
const int pkey_id = EVP_PKEY_id(public_key.get());
if (!cert_pkey_ids.insert(pkey_id).second) {
throw EnvoyException(fmt::format("Failed to load certificate chain from {}, at most one "
"certificate of a given type may be specified",
ctx.cert_chain_file_path_));
}
ctx.is_ecdsa_ = pkey_id == EVP_PKEY_EC;
switch (pkey_id) {
case EVP_PKEY_EC: {
// We only support P-256 ECDSA today.
const EC_KEY* ecdsa_public_key = EVP_PKEY_get0_EC_KEY(public_key.get());
// Since we checked the key type above, this should be valid.
ASSERT(ecdsa_public_key != nullptr);
const EC_GROUP* ecdsa_group = EC_KEY_get0_group(ecdsa_public_key);
if (ecdsa_group == nullptr || EC_GROUP_get_curve_name(ecdsa_group) != NID_X9_62_prime256v1) {
throw EnvoyException(fmt::format("Failed to load certificate from chain {}, only P-256 "
throw EnvoyException(fmt::format("Failed to load certificate chain from {}, only P-256 "
"ECDSA certificates are supported",
ctx.cert_chain_file_path_));
}
Expand All @@ -268,7 +276,7 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const ContextConfig& config, TimeS
ASSERT(rsa_public_key != nullptr);
const unsigned rsa_key_length = RSA_size(rsa_public_key);
if (rsa_key_length < 2048 / 8) {
throw EnvoyException(fmt::format("Failed to load certificate from chain {}, only RSA "
throw EnvoyException(fmt::format("Failed to load certificate chain from {}, only RSA "
"certificates with 2048-bit or larger keys are supported",
ctx.cert_chain_file_path_));
}
Expand Down
88 changes: 76 additions & 12 deletions test/common/ssl/context_impl_test.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <string>
#include <vector>

#include "envoy/api/v2/auth/cert.pb.validate.h"

#include "common/json/json_loader.h"
#include "common/secret/sds_api.h"
#include "common/ssl/context_config_impl.h"
Expand All @@ -22,6 +24,7 @@
#include "openssl/x509v3.h"

using Envoy::Protobuf::util::MessageDifferencer;
using testing::EndsWith;
using testing::NiceMock;
using testing::ReturnRef;

Expand Down Expand Up @@ -295,6 +298,52 @@ TEST_F(SslContextImplTest, TestNoCert) {
EXPECT_TRUE(context->getCertChainInformation().empty());
}

// Multiple RSA certificates are rejected.
TEST_F(SslContextImplTest, AtMostOneRsaCert) {
envoy::api::v2::auth::DownstreamTlsContext tls_context;
NiceMock<Server::Configuration::MockTransportSocketFactoryContext> factory_context;
const std::string tls_context_yaml = R"EOF(
common_tls_context:
tls_certificates:
- certificate_chain:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_cert.pem"
private_key:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_key.pem"
- certificate_chain:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned2_cert.pem"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you re-use existing certs (selfsigned & selfsigned_rsa_1024* and selfsigned_ecdsa_p256 and selfsigned_ecdsa_p384) instead of adding another set? We have a bit too many test certs already, and we could get rid of the OUTPUT_PREFIX hack in certs.sh, then.

*either copy it from #5318 or wait until it's merged (later today?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we did that, it might work, because we check for duplicate certs of a given type before validating the cert, but this is just an implementation detail which could potentially change so let's stick with this for now.

Long term, I think the right direction to go here is to have some custom Go/Python script for programatically generating the certs (since we can't rely on the openssl CLI in any Bazel automation), so we don't need to check in so many certs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long term, I think the right direction to go here is to have some custom Go/Python script for programatically generating the certs (since we can't rely on the openssl CLI in any Bazel automation), so we don't need to check in so many certs.

We used to do openssl in genrule and that broke once in OSX, but I think we may enable that again by using bssl binary?

private_key:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_key.pem"
)EOF";
MessageUtil::loadFromYaml(TestEnvironment::substitute(tls_context_yaml), tls_context);
ServerContextConfigImpl server_context_config(tls_context, factory_context);
EXPECT_THROW_WITH_REGEX(manager_.createSslServerContext(store_, server_context_config, {}),
EnvoyException,
"at most one certificate of a given type may be specified");
}

// Multiple ECDSA certificates are rejected.
TEST_F(SslContextImplTest, AtMostOneEcdsaCert) {
envoy::api::v2::auth::DownstreamTlsContext tls_context;
NiceMock<Server::Configuration::MockTransportSocketFactoryContext> factory_context;
const std::string tls_context_yaml = R"EOF(
common_tls_context:
tls_certificates:
- certificate_chain:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_ecdsa_p256_cert.pem"
private_key:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_ecdsa_p256_key.pem"
- certificate_chain:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned2_ecdsa_p256_cert.pem"
private_key:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_ecdsa_p256_key.pem"
)EOF";
MessageUtil::loadFromYaml(TestEnvironment::substitute(tls_context_yaml), tls_context);
ServerContextConfigImpl server_context_config(tls_context, factory_context);
EXPECT_THROW_WITH_REGEX(manager_.createSslServerContext(store_, server_context_config, {}),
EnvoyException,
"at most one certificate of a given type may be specified");
}

class SslServerContextImplTicketTest : public SslContextImplTest {
public:
void loadConfig(ServerContextConfigImpl& cfg) {
Expand Down Expand Up @@ -575,7 +624,7 @@ TEST(ClientContextConfigImplTest, RSA1024Cert) {
Stats::IsolatedStoreImpl store;
EXPECT_THROW_WITH_REGEX(manager.createSslClientContext(store, client_context_config),
EnvoyException,
"Failed to load certificate from chain .*selfsigned_rsa_1024_cert.pem, "
"Failed to load certificate chain from .*selfsigned_rsa_1024_cert.pem, "
"only RSA certificates with 2048-bit or larger keys are supported");
}

Expand Down Expand Up @@ -616,7 +665,7 @@ TEST(ClientContextConfigImplTest, NonP256EcdsaCert) {
Stats::IsolatedStoreImpl store;
EXPECT_THROW_WITH_REGEX(manager.createSslClientContext(store, client_context_config),
EnvoyException,
"Failed to load certificate from chain .*selfsigned_ecdsa_p384_cert.pem, "
"Failed to load certificate chain from .*selfsigned_ecdsa_p384_cert.pem, "
"only P-256 ECDSA certificates are supported");
}

Expand Down Expand Up @@ -942,28 +991,34 @@ TEST(ClientContextConfigImplTest, MissingStaticCertificateValidationContext) {
"Unknown static certificate validation context: missing");
}

// Multiple TLS certificates are not yet supported, but one is expected for
// server.
// TODO(PiotrSikora): Support multiple TLS certificates.
// Multiple TLS certificates are supported.
TEST(ServerContextConfigImplTest, MultipleTlsCertificates) {
envoy::api::v2::auth::DownstreamTlsContext tls_context;
NiceMock<Server::Configuration::MockTransportSocketFactoryContext> factory_context;
EXPECT_THROW_WITH_MESSAGE(
ServerContextConfigImpl client_context_config(tls_context, factory_context), EnvoyException,
"No TLS certificates found for server context");
const std::string tls_certificate_yaml = R"EOF(
const std::string rsa_tls_certificate_yaml = R"EOF(
certificate_chain:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_cert.pem"
private_key:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_key.pem"
)EOF";
MessageUtil::loadFromYaml(TestEnvironment::substitute(tls_certificate_yaml),
const std::string ecdsa_tls_certificate_yaml = R"EOF(
certificate_chain:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_ecdsa_p256_cert.pem"
private_key:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_ecdsa_p256_key.pem"
)EOF";
MessageUtil::loadFromYaml(TestEnvironment::substitute(rsa_tls_certificate_yaml),
*tls_context.mutable_common_tls_context()->add_tls_certificates());
MessageUtil::loadFromYaml(TestEnvironment::substitute(tls_certificate_yaml),
MessageUtil::loadFromYaml(TestEnvironment::substitute(ecdsa_tls_certificate_yaml),
*tls_context.mutable_common_tls_context()->add_tls_certificates());
EXPECT_THROW_WITH_MESSAGE(
ServerContextConfigImpl client_context_config(tls_context, factory_context), EnvoyException,
"A single TLS certificate is required for server contexts");
ServerContextConfigImpl server_context_config(tls_context, factory_context);
auto tls_certs = server_context_config.tlsCertificates();
ASSERT_EQ(2, tls_certs.size());
EXPECT_THAT(tls_certs[0].get().privateKeyPath(), EndsWith("selfsigned_key.pem"));
EXPECT_THAT(tls_certs[1].get().privateKeyPath(), EndsWith("selfsigned_ecdsa_p256_key.pem"));
}

TEST(ServerContextConfigImplTest, TlsCertificatesAndSdsConfig) {
Expand All @@ -983,7 +1038,16 @@ TEST(ServerContextConfigImplTest, TlsCertificatesAndSdsConfig) {
tls_context.mutable_common_tls_context()->add_tls_certificate_sds_secret_configs();
EXPECT_THROW_WITH_MESSAGE(
ServerContextConfigImpl server_context_config(tls_context, factory_context), EnvoyException,
"A single TLS certificate is required for server contexts");
"SDS and non-SDS TLS certificates may not be mixed in server contexts");
}

TEST(ServerContextConfigImplTest, MultiSdsConfig) {
envoy::api::v2::auth::DownstreamTlsContext tls_context;
tls_context.mutable_common_tls_context()->add_tls_certificate_sds_secret_configs();
tls_context.mutable_common_tls_context()->add_tls_certificate_sds_secret_configs();
EXPECT_THROW_WITH_REGEX(
MessageUtil::validate<envoy::api::v2::auth::DownstreamTlsContext>(tls_context),
EnvoyException, "Proto constraint validation failed");
}

TEST(ServerContextConfigImplTest, SecretNotReady) {
Expand Down
34 changes: 34 additions & 0 deletions test/common/ssl/ssl_socket_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "test/common/ssl/test_data/san_dns2_cert_info.h"
#include "test/common/ssl/test_data/san_dns_cert_info.h"
#include "test/common/ssl/test_data/san_uri_cert_info.h"
#include "test/common/ssl/test_data/selfsigned_ecdsa_p256_cert_info.h"
#include "test/mocks/buffer/mocks.h"
#include "test/mocks/network/mocks.h"
#include "test/mocks/secret/mocks.h"
Expand Down Expand Up @@ -563,6 +564,39 @@ TEST_P(SslSocketTest, NoCert) {
GetParam());
}

// Prefer ECDSA certificate when multiple RSA certificates are present and the
// client is RSA/ECDSA capable. We validate TLSv1.2 only here, since we validate
// the e2e behavior on TLSv1.2/1.3 in ssl_integration_test.
TEST_P(SslSocketTest, MultiCertPreferEcdsa) {
const std::string client_ctx_yaml = absl::StrCat(R"EOF(
common_tls_context:
tls_params:
tls_minimum_protocol_version: TLSv1_2
tls_maximum_protocol_version: TLSv1_2
cipher_suites:
- ECDHE-ECDSA-AES128-GCM-SHA256
- ECDHE-RSA-AES128-GCM-SHA256
validation_context:
verify_certificate_hash: )EOF",
TEST_SELFSIGNED_ECDSA_P256_CERT_HASH);

const std::string server_ctx_yaml = R"EOF(
common_tls_context:
tls_certificates:
- certificate_chain:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_cert.pem"
private_key:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_key.pem"
- certificate_chain:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_ecdsa_p256_cert.pem"
private_key:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_ecdsa_p256_key.pem"
)EOF";

testUtil(client_ctx_yaml, server_ctx_yaml, "", "", "", "", "", "", "", "ssl.handshake", true,
GetParam());
}

TEST_P(SslSocketTest, GetUriWithLocalUriSan) {
const std::string client_ctx_yaml = R"EOF(
common_tls_context:
Expand Down
16 changes: 8 additions & 8 deletions test/common/ssl/test_data/ca_cert.crl
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
MIIB7TCB1gIBATANBgkqhkiG9w0BAQsFADB2MQswCQYDVQQGEwJVUzETMBEGA1UE
CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwE
THlmdDEZMBcGA1UECwwQTHlmdCBFbmdpbmVlcmluZzEQMA4GA1UEAwwHVGVzdCBD
QRcNMTgxMjE2MDczMzU1WhcNMjgxMjEzMDczMzU1WjAcMBoCCQCXsSoXHUplgBcN
MTgxMjE2MDczMzU1WqAOMAwwCgYDVR0UBAMCAQAwDQYJKoZIhvcNAQELBQADggEB
AJ3WAGYnAzaHr/Q+ErPh5cwWwt2F+wfXTSesPH+L1u+3kZEi6EjZnLYiwz2OLsNK
nAigZIHaDpVpSA1YWgDXmRHCfXquNkXCyXLpHsWqlwk+4vqcFF0AYG3U/WZBr6dn
XyWbB4OMLLWTbc2sfjRuOtSJoDPsFLbmTjoQQAvw5v3kLxivj3fPA0tq22e8SbHG
EvApHgzD/AWSyuP/wphgJtZYe1PMTxsqztTN1zaYXkYtFOYUhynOsc9T8WEfI8ow
SBNsmdlAhs5MwvHm2x7o6YtowK9s3ExSXhU828cfAWK9zjqXwbW9udjvzRirk7CA
V5ffMGsoT6F9WUTmGP2Z7vA=
QRcNMTgxMjE3MjAxNzI3WhcNMjgxMjE0MjAxNzI3WjAcMBoCCQCKgFI+BqV1ZhcN
MTgxMjE3MjAxNzI3WqAOMAwwCgYDVR0UBAMCAQAwDQYJKoZIhvcNAQELBQADggEB
AH3d7kNoOWBdaYYkARYySS49IftQzjx9dNu7xt2aotAOz5Ff01jqHvS5Jtit2orU
AEfwnvg5ucTxFWo3xHPSah5/tFb/PogNO0A2OgG6tfXhOyM118N1czriSAYW3TEI
bBVtS9HgFkvO7jKJJH278DZjviEstOW/gnLZOff97fWQ4JxIzGHEgVlvLi54oWRA
Cyauce1GPok04GvHU3bNmFR7U8WwbrcjycUypMUPNge6CGpjD4U8BVDzvjc2patl
MWgUsbUiB/nR2cVj8jKSbLoUdDXVcHhrsrrkTysys8rnm6yaW5+78WHmx8sGY9cX
wiiwWVa9gXPH2zh5VvHNuMk=
-----END X509 CRL-----
32 changes: 16 additions & 16 deletions test/common/ssl/test_data/ca_cert.pem
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID0jCCArqgAwIBAgIJANylJk5fPxXPMA0GCSqGSIb3DQEBCwUAMHYxCzAJBgNV
MIID0jCCArqgAwIBAgIJAIcAyF1+jvpEMA0GCSqGSIb3DQEBCwUAMHYxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp
c2NvMQ0wCwYDVQQKDARMeWZ0MRkwFwYDVQQLDBBMeWZ0IEVuZ2luZWVyaW5nMRAw
DgYDVQQDDAdUZXN0IENBMB4XDTE4MTIxNjA3MzM1M1oXDTIwMTIxNTA3MzM1M1ow
DgYDVQQDDAdUZXN0IENBMB4XDTE4MTIxNzIwMTcyNVoXDTIwMTIxNjIwMTcyNVow
djELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNh
biBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5naW5l
ZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDSieHtBaocM+llhrXyWBePg2ux0n7Kd2nYL4pyL2TQLKVurOGyfotT
2XLucOYcIB3lDvKJIuUmoKjQSPAGk0thcSWip3FcFYqhBsqVPRkeO2UG8YgYkONO
8eb7PjqCb2OW7gdoV7VGn9vyugCfW61vxo//VqUTfRehhVCgnrjoRoK8xDUXRjYh
ko4RpPoDtT74o45V2NhQudoS3c0hQPuC3bzz3rjIrajE5ERUWu498+EXBsKleJc9
vZGaB2zmwTeOZSTfIGeD1OPLUmfsOuTnMhTAVJ2zfS3PRoJcFqqQe+ZHVv3/1FQn
UYUJalF75Ntp3ND4mGfJvKRVWoiqnPD9AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMB
Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSkLI5XDZR39Oz/TrYHUwcKLzZU
AzAfBgNVHSMEGDAWgBSkLI5XDZR39Oz/TrYHUwcKLzZUAzANBgkqhkiG9w0BAQsF
AAOCAQEAB+Uul7u8+rjIuiGfZRdiLSPMtWHH5sqG8S4UDwcNvqGDjn+MODVOyuHe
Fqly3eArTIFFoS5B+C1GHQzti1Eljr/W4ZCjSjChhup0vf6FXjCj/ZojNIIWGFt+
7ggfDIUOB2uTbssHU5Q8wus/g0ZyWHURaGKCPJD6XLcYVqbbxCZ4iMokvSl4Nu64
WbsOuuxEomK1iMCrcghckArxdUOom7gZgSTc/Ya2pGeEo5cbtxL0PXOKSqTvuAGO
EtWD4/OElPc+cvx1aYUsqWBqHXEwmNgESGWkOfwygjX4M+i/k/Azf79wbXofbbsq
XQ6sraf3cGi21W4GrIAz67Os2lxE/w==
ggEKAoIBAQDMuACQq9YTqCCpej9zgiUOurHJl0AhYp4uXP5nCF/Sxtf6olu97jDs
T6XZzsow4LZRYsiVdLNklNdnGdFiqndl4jKJtnLQ+XdVMJcTP/kVVflx9NH1kuWt
HqOBuIktv5FYJA3cDc0t8B/myZOl8K7nlJGoToudnV7PmZnfz1NMb+QaIYIXQyio
5rppHq4aC8TTL3VOZL93wzjBXDT2OtOBWec8yCnFm3JfU37BfAM2mbTkgpol7qvA
UsjAef3C6gbIvL79CNA1G6sRDR6pjvQ8XhNr/hXj4IeUrMnwMaRub5MXo2/naTRA
zMSenxKNJ0FL9y7fBmkySiEur6fZLbHJAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMB
Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTTfgjkmQUAL7gTfjZPv+FRy5KS
TzAfBgNVHSMEGDAWgBTTfgjkmQUAL7gTfjZPv+FRy5KSTzANBgkqhkiG9w0BAQsF
AAOCAQEAbtJILJvq+KfayHHdHkPrC4XWScuUwlSDPjy614oXzJfjkV34T6YsInpf
xqiIkm3WuQYkoNAitlU/JGt4lZdaUL6JKSaAq2WFueYpVLPQkVN5M0U62rGP8sJ9
q5B2W5ZW/X1/cPimdpRzU0TGW5DDdSP8KXq4ND4SBaQfttw/xTnSCHJ1sL+Xy8KP
6jAPuCcH/6jWx6qYTddI8E7OtHWeOFQ1iNomTBRC8T2F+vnHnu4NHM5aYDA5kxr+
lgNS2qZJKUApKqi7s4/d/wc1tFBt2/kZYNfoTvpNtYuEMdPwZ1Hq8BhuAG7ETk2e
Y5SaIOpV5Zsta9j3+D6lfSwnJ+f6nQ==
-----END CERTIFICATE-----
Loading