Add property to enable key verification on PEM SSL bundles

Closes gh-37727
pull/37768/head
Moritz Halbritter 1 year ago
parent 85aeedeace
commit 0a16ec17e9

@ -23,6 +23,7 @@ import org.springframework.boot.ssl.pem.PemSslStoreBundle;
* *
* @author Scott Frederick * @author Scott Frederick
* @author Phillip Webb * @author Phillip Webb
* @author Moritz Halbritter
* @since 3.1.0 * @since 3.1.0
* @see PemSslStoreBundle * @see PemSslStoreBundle
*/ */
@ -38,6 +39,11 @@ public class PemSslBundleProperties extends SslBundleProperties {
*/ */
private final Store truststore = new Store(); private final Store truststore = new Store();
/**
* Whether to verify that the private key matches the public key.
*/
private boolean verifyKeys;
public Store getKeystore() { public Store getKeystore() {
return this.keystore; return this.keystore;
} }
@ -46,6 +52,14 @@ public class PemSslBundleProperties extends SslBundleProperties {
return this.truststore; return this.truststore;
} }
public boolean isVerifyKeys() {
return this.verifyKeys;
}
public void setVerifyKeys(boolean verifyKeys) {
this.verifyKeys = verifyKeys;
}
/** /**
* Store properties. * Store properties.
*/ */

@ -109,7 +109,8 @@ public final class PropertiesSslBundle implements SslBundle {
private static SslStoreBundle asSslStoreBundle(PemSslBundleProperties properties) { private static SslStoreBundle asSslStoreBundle(PemSslBundleProperties properties) {
PemSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore()); PemSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore());
PemSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore()); PemSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore());
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, properties.getKey().getAlias()); return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, properties.getKey().getAlias(), null,
properties.isVerifyKeys());
} }
private static PemSslStoreDetails asStoreDetails(PemSslBundleProperties.Store properties) { private static PemSslStoreDetails asStoreDetails(PemSslBundleProperties.Store properties) {

@ -0,0 +1,104 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.ssl.pem;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
/**
* Performs checks on keys, e.g., if a public key and a private key belong together.
*
* @author Moritz Halbritter
*/
class KeyVerifier {
private static final byte[] DATA = "Just some piece of data which gets signed".getBytes(StandardCharsets.UTF_8);
/**
* Checks if the given private key belongs to the given public key.
* @param privateKey the private key
* @param publicKey the public key
* @return whether the keys belong together
*/
Result matches(PrivateKey privateKey, PublicKey publicKey) {
try {
if (!privateKey.getAlgorithm().equals(publicKey.getAlgorithm())) {
// Keys are of different type
return Result.NO;
}
String algorithm = getSignatureAlgorithm(privateKey.getAlgorithm());
if (algorithm == null) {
return Result.UNKNOWN;
}
byte[] signature = createSignature(privateKey, algorithm);
return verifySignature(publicKey, algorithm, signature);
}
catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException ex) {
return Result.UNKNOWN;
}
}
private static byte[] createSignature(PrivateKey privateKey, String algorithm)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature signer = Signature.getInstance(algorithm);
signer.initSign(privateKey);
signer.update(DATA);
return signer.sign();
}
private static Result verifySignature(PublicKey publicKey, String algorithm, byte[] signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature verifier = Signature.getInstance(algorithm);
verifier.initVerify(publicKey);
verifier.update(DATA);
try {
if (verifier.verify(signature)) {
return Result.YES;
}
else {
return Result.NO;
}
}
catch (SignatureException ex) {
return Result.NO;
}
}
private static String getSignatureAlgorithm(String keyAlgorithm) {
// https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#signature-algorithms
// https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#keypairgenerator-algorithms
return switch (keyAlgorithm) {
case "RSA" -> "SHA256withRSA";
case "DSA" -> "SHA256withDSA";
case "EC" -> "SHA256withECDSA";
case "EdDSA" -> "EdDSA";
default -> null;
};
}
enum Result {
YES, NO, UNKNOWN
}
}

@ -16,12 +16,16 @@
package org.springframework.boot.ssl.pem; package org.springframework.boot.ssl.pem;
import java.io.IOException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import org.springframework.boot.ssl.SslStoreBundle; import org.springframework.boot.ssl.SslStoreBundle;
import org.springframework.boot.ssl.pem.KeyVerifier.Result;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -71,8 +75,24 @@ public class PemSslStoreBundle implements SslStoreBundle {
*/ */
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String keyAlias, public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String keyAlias,
String keyPassword) { String keyPassword) {
this.keyStore = createKeyStore("key", keyStoreDetails, keyAlias, keyPassword); this(keyStoreDetails, trustStoreDetails, keyAlias, keyPassword, false);
this.trustStore = createKeyStore("trust", trustStoreDetails, keyAlias, keyPassword); }
/**
* Create a new {@link PemSslStoreBundle} instance.
* @param keyStoreDetails the key store details
* @param trustStoreDetails the trust store details
* @param keyAlias the key alias to use or {@code null} to use a default alias
* @param keyPassword the password to use for the key
* @param verifyKeys whether to verify that the private key matches the public key
* @since 3.2.0
*/
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String keyAlias,
String keyPassword, boolean verifyKeys) {
this.keyStore = createKeyStore("key", keyStoreDetails, (keyAlias != null) ? keyAlias : DEFAULT_KEY_ALIAS,
keyPassword, verifyKeys);
this.trustStore = createKeyStore("trust", trustStoreDetails, (keyAlias != null) ? keyAlias : DEFAULT_KEY_ALIAS,
keyPassword, verifyKeys);
} }
@Override @Override
@ -90,20 +110,25 @@ public class PemSslStoreBundle implements SslStoreBundle {
return this.trustStore; return this.trustStore;
} }
private KeyStore createKeyStore(String name, PemSslStoreDetails details, String alias, String keyPassword) { private static KeyStore createKeyStore(String name, PemSslStoreDetails details, String keyAlias, String keyPassword,
boolean verifyKeys) {
if (details == null || details.isEmpty()) { if (details == null || details.isEmpty()) {
return null; return null;
} }
try { try {
Assert.notNull(details.certificate(), "Certificate content must not be null"); Assert.notNull(details.certificate(), "Certificate content must not be null");
String type = (!StringUtils.hasText(details.type())) ? KeyStore.getDefaultType() : details.type(); KeyStore store = createKeyStore(details);
KeyStore store = KeyStore.getInstance(type); X509Certificate[] certificates = loadCertificates(details);
store.load(null); PrivateKey privateKey = loadPrivateKey(details);
String certificateContent = PemContent.load(details.certificate()); if (privateKey != null) {
String privateKeyContent = PemContent.load(details.privateKey()); if (verifyKeys) {
X509Certificate[] certificates = PemCertificateParser.parse(certificateContent); verifyKeys(privateKey, certificates);
PrivateKey privateKey = PemPrivateKeyParser.parse(privateKeyContent, details.privateKeyPassword()); }
addCertificates(store, certificates, privateKey, (alias != null) ? alias : DEFAULT_KEY_ALIAS, keyPassword); addPrivateKey(store, privateKey, keyAlias, keyPassword, certificates);
}
else {
addCertificates(store, certificates, keyAlias);
}
return store; return store;
} }
catch (Exception ex) { catch (Exception ex) {
@ -111,17 +136,48 @@ public class PemSslStoreBundle implements SslStoreBundle {
} }
} }
private void addCertificates(KeyStore keyStore, X509Certificate[] certificates, PrivateKey privateKey, String alias, private static void verifyKeys(PrivateKey privateKey, X509Certificate[] certificates) {
String keyPassword) throws KeyStoreException { KeyVerifier keyVerifier = new KeyVerifier();
if (privateKey != null) { // Key should match one of the certificates
keyStore.setKeyEntry(alias, privateKey, (keyPassword != null) ? keyPassword.toCharArray() : null, for (X509Certificate certificate : certificates) {
certificates); Result result = keyVerifier.matches(privateKey, certificate.getPublicKey());
} if (result == Result.YES) {
else { return;
for (int index = 0; index < certificates.length; index++) {
keyStore.setCertificateEntry(alias + "-" + index, certificates[index]);
} }
} }
throw new IllegalStateException("Private key matches none of the certificates");
}
private static PrivateKey loadPrivateKey(PemSslStoreDetails details) {
String privateKeyContent = PemContent.load(details.privateKey());
return PemPrivateKeyParser.parse(privateKeyContent, details.privateKeyPassword());
}
private static X509Certificate[] loadCertificates(PemSslStoreDetails details) {
String certificateContent = PemContent.load(details.certificate());
X509Certificate[] certificates = PemCertificateParser.parse(certificateContent);
Assert.state(certificates != null && certificates.length > 0, "Loaded certificates are empty");
return certificates;
}
private static KeyStore createKeyStore(PemSslStoreDetails details)
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
String type = StringUtils.hasText(details.type()) ? details.type() : KeyStore.getDefaultType();
KeyStore store = KeyStore.getInstance(type);
store.load(null);
return store;
}
private static void addPrivateKey(KeyStore keyStore, PrivateKey privateKey, String alias, String keyPassword,
X509Certificate[] certificates) throws KeyStoreException {
keyStore.setKeyEntry(alias, privateKey, (keyPassword != null) ? keyPassword.toCharArray() : null, certificates);
}
private static void addCertificates(KeyStore keyStore, X509Certificate[] certificates, String alias)
throws KeyStoreException {
for (int index = 0; index < certificates.length; index++) {
keyStore.setCertificateEntry(alias + "-" + index, certificates[index]);
}
} }
} }

@ -0,0 +1,90 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.ssl.pem;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECGenParameterSpec;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.boot.ssl.pem.KeyVerifier.Result;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link KeyVerifier}.
*
* @author Moritz Halbritter
*/
class KeyVerifierTests {
private static final List<Algorithm> ALGORITHMS = List.of(Algorithm.of("RSA"), Algorithm.of("DSA"),
Algorithm.of("ed25519"), Algorithm.of("ed448"), Algorithm.ec("secp256r1"), Algorithm.ec("secp521r1"));
private final KeyVerifier keyVerifier = new KeyVerifier();
@ParameterizedTest(name = "{0}")
@MethodSource("arguments")
void test(PrivateKey privateKey, PublicKey publicKey, List<PublicKey> invalidPublicKeys) {
assertThat(this.keyVerifier.matches(privateKey, publicKey)).isEqualTo(Result.YES);
for (PublicKey invalidPublicKey : invalidPublicKeys) {
assertThat(this.keyVerifier.matches(privateKey, invalidPublicKey)).isEqualTo(Result.NO);
}
}
static Stream<Arguments> arguments() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
List<KeyPair> keyPairs = new LinkedList<>();
for (Algorithm algorithm : ALGORITHMS) {
KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm.name());
if (algorithm.spec() != null) {
generator.initialize(algorithm.spec());
}
keyPairs.add(generator.generateKeyPair());
keyPairs.add(generator.generateKeyPair());
}
return keyPairs.stream()
.map((kp) -> Arguments.arguments(Named.named(kp.getPrivate().getAlgorithm(), kp.getPrivate()),
kp.getPublic(), without(keyPairs, kp).map(KeyPair::getPublic).toList()));
}
private static Stream<KeyPair> without(List<KeyPair> keyPairs, KeyPair without) {
return keyPairs.stream().filter((kp) -> !kp.equals(without));
}
private record Algorithm(String name, AlgorithmParameterSpec spec) {
static Algorithm of(String name) {
return new Algorithm(name, null);
}
static Algorithm ec(String curve) {
return new Algorithm("EC", new ECGenParameterSpec(curve));
}
}
}

@ -24,12 +24,14 @@ import org.junit.jupiter.api.Test;
import org.springframework.util.function.ThrowingConsumer; import org.springframework.util.function.ThrowingConsumer;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/** /**
* Tests for {@link PemSslStoreBundle}. * Tests for {@link PemSslStoreBundle}.
* *
* @author Scott Frederick * @author Scott Frederick
* @author Phillip Webb * @author Phillip Webb
* @author Moritz Halbritter
*/ */
class PemSslStoreBundleTests { class PemSslStoreBundleTests {
@ -131,6 +133,34 @@ class PemSslStoreBundleTests {
.satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray())); .satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
} }
@Test
void shouldVerifyKeysIfEnabled() {
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
.forCertificate("classpath:org/springframework/boot/ssl/pem/key1.crt")
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key1.pem");
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, "test-alias", "keysecret", true);
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
}
@Test
void shouldVerifyKeysIfEnabledAndCertificateChainIsUsed() {
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
.forCertificate("classpath:org/springframework/boot/ssl/pem/key2-chain.crt")
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key2.pem");
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, "test-alias", "keysecret", true);
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
}
@Test
void shouldFailIfVerifyKeysIsEnabledAndKeysDontMatch() {
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
.forCertificate("classpath:org/springframework/boot/ssl/pem/key2.crt")
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key1.pem");
assertThatIllegalStateException()
.isThrownBy(() -> new PemSslStoreBundle(keyStoreDetails, null, null, null, true))
.withMessageContaining("Private key matches none of the certificates");
}
private Consumer<KeyStore> storeContainingCert(String keyAlias) { private Consumer<KeyStore> storeContainingCert(String keyAlias) {
return storeContainingCert(KeyStore.getDefaultType(), keyAlias); return storeContainingCert(KeyStore.getDefaultType(), keyAlias);
} }

@ -0,0 +1,19 @@
-----BEGIN TRUSTED CERTIFICATE-----
MIIDIDCCAgsCFH3lh1RXOEy2ESqUPyzb+9zxMYUnMA0GCSqGSIb3DQEBCwUAME8x
CzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl
ZmF1bHQgQ29tcGFueSBMdGQxCzAJBgNVBAMMAkNBMCAXDTIzMTAwNTA3MjU1M1oY
DzIxMjMwOTExMDcyNTUzWjBPMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs
dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMQswCQYDVQQDDAJD
QTCCAR4wDQYJKoZIhvcNAQEBBQADggELADCCAQYCgf4NNpc+6B3qvwKcRYgoXmJ4
3wyWktBK7BdShz/YnW1OlFZ+R845ZiDw0KdzElZWkYqn+BYJus6lPIS5dfLcrGSf
a1e8IK02RpBiY/WJvupetnSk8gKA7emF94NlV4gXr4ICJAhXvXUFyBLpdEUE/lcg
lgCbVJzs5jWUnffEF9mrClzzo0+iXw34zwmyYyBTFmlOEr+QUEdAb6Lr/klpTVit
as2Ddg1QT4EaSIdTEpkVRZp2dyYVdqSxpaBq21xg0viDHsYQrP96IfacmUB7kFFn
HsnptDHFvJj2WSQDX+PRS7tLl4mmfizZg80eGfLD22ShNspRSGnbJc0OzegPiwID
AQABMA0GCSqGSIb3DQEBCwUAA4H/AAnC+FQqdeJaG5I7R+pNjgKplL2UsxW983kA
CVVkv/Dt0+4rbPC67o9/8Tr+g4eo/wUntMNo2ghF3oBItGr7pJE16zPiLwIvha9c
8BDhCEZWyhz3vkamZUi19lOnkm3zTmmDE/nX4WYH6CL4UWjxvniZYwW8AdVSnFXY
ncriuvfliLa3dw1SJ7FtxdcBn4yfzrZWcY+psYNHpftLGYRmQF/VCDSB9EAIEggr
yBcP749u2y8s44WvKAnnwfLcALIrylY25zN0pao/l2X8HI6qHUeA/QbbEBpDoQvR
du/rgaHCVvFFxATefhBJ0CUA1Nn5nrGwyRTKnZWtR080qwUp
-----END TRUSTED CERTIFICATE-----

@ -0,0 +1,27 @@
-----BEGIN PRIVATE KEY-----
MIIEqQIBADANBgkqhkiG9w0BAQEFAASCBJMwggSPAgEAAoH+DTaXPugd6r8CnEWI
KF5ieN8MlpLQSuwXUoc/2J1tTpRWfkfOOWYg8NCncxJWVpGKp/gWCbrOpTyEuXXy
3Kxkn2tXvCCtNkaQYmP1ib7qXrZ0pPICgO3phfeDZVeIF6+CAiQIV711BcgS6XRF
BP5XIJYAm1Sc7OY1lJ33xBfZqwpc86NPol8N+M8JsmMgUxZpThK/kFBHQG+i6/5J
aU1YrWrNg3YNUE+BGkiHUxKZFUWadncmFXaksaWgattcYNL4gx7GEKz/eiH2nJlA
e5BRZx7J6bQxxbyY9lkkA1/j0Uu7S5eJpn4s2YPNHhnyw9tkoTbKUUhp2yXNDs3o
D4sCAwEAAQKB/goGHht1EC0kFyDihvbJE79Kx0v7uNT94nuTa1Yzp9bzJeLLKqHU
3qySPlZH1QP7icr/pAhhlZ85GB9yYXoTtopSbs6jo4QHaEWcO4vyL+8GT9tKVafl
1UDyktXw36fIV8Kz/zhA3GQ0clR1Bl9RbFumMHOmbx4xTvieFnbG+TQ2THfFccGS
jCO6+dab6daXs8sBt0rGMh72utIISVsFJc7v3B8BpaNOI4iBMciRSyZeE4Vw/lRg
e3iErAVUmUjBrUK/wBy/l9cbbpkp+rvhQpmTIPtKd5f29AQNL7p6V+2+yRb2woRk
0i1HwOHGOhiCTxXZB9/nZykaT/T2+J9BAn8+DEWCRcfifyNEyuE54G6BvLvgGTgs
+kXWS7p0+wO9CFBDZARu/MXFEfcWt4ZTIj8HtMiKhxNbC1LiGtQnJoLV6AM75E5Q
toh/xyYOnHbhnbhsSNcpJk5iIdqQE6hWh+rYXFr1aJFMRZaWRkcUG8iIxWQQjRvw
qxLm9GQtEhF7An82hAlPCDs+6kT1otBEN8vGaW8qkxWYJf6kSd/I0/TEKRYpIwBa
Ist2BN5GrJTitKhzQIq2ZyT2byHxS0VIvInZJ6sFC+V6fHYpzWbS3zkBy2zswfAZ
UYrdjLVv16qZYsdjUnhkyUaBbBXnrTPlPzxXvgTeqJeJ5tbR6wgeqPUxAn8lcQxE
t00N/UBQE8jjPu4QNc59RVqjsYaQ8POcAZjY6fpdIC6Ytsm0yMl8mNRiuCimws28
4hOo/eVO8XeSBGgxIidJbdRgWjV2PbtWV85ZCO6v0Sic+TOVfe5AwMv1I2FwnBJ7
QlVjXB6podDkbnuNJOfkIPJ6QRFP8qu8ksmfAn8mttuZeYIBawLv4eC/IVSgIc3l
UTC7rPfKGgBHMWaYS4lGS2n7mMwektR7IiJVYPBjcIlRgaw5KbDUF50rS2Elissj
uVANDQgpJYoI5KcqRBmlhRCKGmNgdIWA2Ip5hTGNskp3YIymamif71t0SNUEhpgU
u2tqbjlON/e7NkdxAn8VdVYq+4sAWRdU4VJqqyf8dyBx68sysvY6HYlKS2bpfu3C
J3gbPximDZhzMvKx2/CAzMbAT3anyr/DiUImk+QdWSmht+1SLH7A14MDjzQ0D5xt
GgPqWn7PtcJojFMjc/o5/fKgFf4CYkJhv2KycX9UeldBxpqNpNigzFWBLdtu
-----END PRIVATE KEY-----

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDJjCCAhECFHuJXZO0JDPtCSc1/r0llpyc/j9TMA0GCSqGSIb3DQEBCwUAME8x
CzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl
ZmF1bHQgQ29tcGFueSBMdGQxCzAJBgNVBAMMAkNBMCAXDTIzMTAwNTA3Mjg0NVoY
DzIxMjMwOTExMDcyODQ1WjBRMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs
dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMQ0wCwYDVQQDDARr
ZXkxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoYU2afupPq/b6PIy
6MWDOMRdJk5uW51lrw6oudXpWlUQMXKdsaZT4sqbgjGLggfo7WWsPeCzQN3kIX3T
OqBog5EMkXnlQhAfP2Htj0uXPFj97leZ+FqJrzgPnZY8wSqDXfy9/ycR3PgWjRsS
GZJb05hTNVGTU2vpNQDDo+XBKgybB0afGU8Nk/InWfs1xd/Jv0YcVADQiQEmg41w
g18B3LMIBZPWIJUQ1b7wMlhxWaCNXHfB1bUTIYCUAUOZyEaxPaOOiJo32xKmqOlU
TCLM8zgWCBCEgHtQwSD0GMLhUarLPNE5GP3yo5qHBYqOque7BBjP4e58r6wAyBoe
7kMYRQIDAQABMA0GCSqGSIb3DQEBCwUAA4H/AAMIYpTDxgQwpfk+U1IhkqJjb+Uh
hj6KlT5TEdpn/saGYLZQECZAO21MWrUDTsV2Pax2Ee8ezarCg8Cthu4YOtPauPaL
XpyrIagUOgrDcmXr6QxMKUqifiMurLRFaAS7mWXp0TAFNgzDg3WvF9zMJgkjUp/O
gNSG9U7kXuFfxpVtoalyC2C3g3UeieVXSek3a28h5c/0/DomHqLbyqZh5rYwAJ7C
q1bqA5TnZNVvV731SVueycj9+5PKHKG6eeRRh7roZ34l54O9adNEeDAF0Lqn4sbn
a/h4GPK/u6J6Y3nwrdajipZ2DmfiQwoimxprMGNQKuKA0lc025SGHNno
-----END CERTIFICATE-----

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQChhTZp+6k+r9vo
8jLoxYM4xF0mTm5bnWWvDqi51elaVRAxcp2xplPiypuCMYuCB+jtZaw94LNA3eQh
fdM6oGiDkQyReeVCEB8/Ye2PS5c8WP3uV5n4WomvOA+dljzBKoNd/L3/JxHc+BaN
GxIZklvTmFM1UZNTa+k1AMOj5cEqDJsHRp8ZTw2T8idZ+zXF38m/RhxUANCJASaD
jXCDXwHcswgFk9YglRDVvvAyWHFZoI1cd8HVtRMhgJQBQ5nIRrE9o46ImjfbEqao
6VRMIszzOBYIEISAe1DBIPQYwuFRqss80TkY/fKjmocFio6q57sEGM/h7nyvrADI
Gh7uQxhFAgMBAAECggEABUfEGCHkjgqUv2BPnsF6QTxWKT7uJ6uVG+x4Qp8GInBe
d6deFWUxH9xsygxRmb4ldMFaqKk0Yv3+C8Q/yA5fbFGtHgJkpsy9IMbUS9d2ScBF
COovO+nFz4cfJ5E2SkBYDBYLphBCar1ni1RjupdIzjmQGtGgZd1EwflU7AJCVtwG
S7ltIs2nSOqUFGTfjb9j0NiATZvWTDRtavNMhyrZplKK6M6VoH1ZcnmcvEfF7j5L
oSmXrNKYs4iKn1qKypykfCQoEFK0/EEjj5EdnPaSeI9EERrZK1QnHafB2qK38LSr
8cGaWH24mPW6c/26bDQnHkN3SqKLCODXZMBGhPlLDwKBgQDdMqOzRR3SpTx7KPqp
h+P0diBZb1e6c+Ob0lXD/rfJEtkAqyFLqpi8hN9xodxw++JYbhC69kJE7VWtQLIt
Lc+DG72KTS/cbpnvERL1+AoM0TRbO9Ds9aFP4+Zmm/VDxi9rR5yTgl9iAHJ46VrE
BhnG8JQPBm4n5JU5/wJ9qCQCywKBgQC67uWchaewzDHCiefhTVgwTm1BmHiV/OR4
50Je2x3GPW6VJGFnBjVzlScKrNyFeOYwscvVS8pTmFP8c5laTbQMC3pVqiWs28Ip
6sy6cXfepVyc0njLFGbiek8ab0rjVYU27D0O9tucrxDx4pKOurilds1Gbm4HjfyE
R7pWn/AfLwKBgQC+5wJzKLaJYsQlAwP6pmYtSHm41ihfqb8Jb2lHwyD4r4SLWCZf
OHejVAXH+0rWU/1QFoXn5brh4/cqlIhyB3RtkdZucxlYZDgEJLc5g32g/Dj0eFZi
+8bhvS3O5tCxUm0AaIiQolcRrJMfGT6VqTI8CMuvf/w3/8ZujFCpBCE4KwKBgBiw
lQMnZA6l6ayYKlhHru4ybZvMV6D31fViFhIRPs2AL6rjMzo4R7cMbCusyTOX1E96
LEHv0LlZ1T3yxr52pOEyYuYNowxBulNu/7tgYUS28pSD+BBakXw4S1pieLGuCfpH
GYlwcXEwbjyEgHb5konINzSmQUIeLswJ7UKjvUNhAoGAXmXvyHqdL04SD99G3B/5
+azzzAVR1fvGYOvq+/hWZMG5PS0kx2V3txCVyY8E1/lCysp9BuUHtW+vOS8YGhAT
wkZ/X9igZteQvvdVw+E5CXS05b4EBI+7ZViL9ulXFZ4YC70lKcUE52bmaPM+onQJ
Y1s9JWTe2EAkxsuxm+hkjo0=
-----END PRIVATE KEY-----

@ -0,0 +1,38 @@
-----BEGIN CERTIFICATE-----
MIIDJjCCAhECFFjLlXVdTxDdLlCifzrA0dTHHJ2mMA0GCSqGSIb3DQEBCwUAME8x
CzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl
ZmF1bHQgQ29tcGFueSBMdGQxCzAJBgNVBAMMAkNBMCAXDTIzMTAwNTA3Mjg1MFoY
DzIxMjMwOTExMDcyODUwWjBRMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs
dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMQ0wCwYDVQQDDARr
ZXkyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAspCMUdFGyKkgpMbW
+UwSg4fdKM4qLSH7voTdsdVM9aAvLvYjBQ4gpORxDZNfUz67R0Ua0/oJt9jD49Wp
qcq+tDOnp0dPtn2hFluV5PxM6d+MCSx/frPsfvyt9234okLL1zdLDNFYEbLhSPjA
ku3vHw/OwlJOxCRwTkPqcElIV4+IvIbzAgSffyokzm/wKVKEhoT6NcfeU+6wCkTu
al1X8loJ+27N6jN13oGZfH7EveBqgR8rPs55+54S/OcVG/uqL9ggOGRJiIZ3jUBk
m5cN27wKkaNg/CQwa1UjcU4qshVpknHw1dpgJ2Gbs/yUphwpEZl/FTsZFcK1KCHD
rOp3PQIDAQABMA0GCSqGSIb3DQEBCwUAA4H/AAFmEq86broBFxs0cpImaM884PBT
bvJBSsFhsOg6mi4Gt01G/lPSj/ExNtH3G5bytCYAPaRxNx/dCs7uON3p86ta4zL8
2PxgyhX1oY/GG63ETwn5s3GKpRaGTNVDWvPIM9RX6+bvX/wOg8eYXVaQlG5XYadC
Ms9lWqHaM1C/iLGNmUTGcdbvhnmQDky2CwPNm+lXogSWbrsGpAmCkXJD1H+0Mx8I
wjDVtGLBwr/8oXI8WbhvISMnS9+dd7+GLm6mU+14Kswi5I7EmBmREvkswi2IVJ6M
GL7EY3qA6iqJWqsseYyLxiMr3nBT0SETphzoDanUQI1/jXQPrWIyjqvs
-----END CERTIFICATE-----
-----BEGIN TRUSTED CERTIFICATE-----
MIIDIDCCAgsCFH3lh1RXOEy2ESqUPyzb+9zxMYUnMA0GCSqGSIb3DQEBCwUAME8x
CzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl
ZmF1bHQgQ29tcGFueSBMdGQxCzAJBgNVBAMMAkNBMCAXDTIzMTAwNTA3MjU1M1oY
DzIxMjMwOTExMDcyNTUzWjBPMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs
dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMQswCQYDVQQDDAJD
QTCCAR4wDQYJKoZIhvcNAQEBBQADggELADCCAQYCgf4NNpc+6B3qvwKcRYgoXmJ4
3wyWktBK7BdShz/YnW1OlFZ+R845ZiDw0KdzElZWkYqn+BYJus6lPIS5dfLcrGSf
a1e8IK02RpBiY/WJvupetnSk8gKA7emF94NlV4gXr4ICJAhXvXUFyBLpdEUE/lcg
lgCbVJzs5jWUnffEF9mrClzzo0+iXw34zwmyYyBTFmlOEr+QUEdAb6Lr/klpTVit
as2Ddg1QT4EaSIdTEpkVRZp2dyYVdqSxpaBq21xg0viDHsYQrP96IfacmUB7kFFn
HsnptDHFvJj2WSQDX+PRS7tLl4mmfizZg80eGfLD22ShNspRSGnbJc0OzegPiwID
AQABMA0GCSqGSIb3DQEBCwUAA4H/AAnC+FQqdeJaG5I7R+pNjgKplL2UsxW983kA
CVVkv/Dt0+4rbPC67o9/8Tr+g4eo/wUntMNo2ghF3oBItGr7pJE16zPiLwIvha9c
8BDhCEZWyhz3vkamZUi19lOnkm3zTmmDE/nX4WYH6CL4UWjxvniZYwW8AdVSnFXY
ncriuvfliLa3dw1SJ7FtxdcBn4yfzrZWcY+psYNHpftLGYRmQF/VCDSB9EAIEggr
yBcP749u2y8s44WvKAnnwfLcALIrylY25zN0pao/l2X8HI6qHUeA/QbbEBpDoQvR
du/rgaHCVvFFxATefhBJ0CUA1Nn5nrGwyRTKnZWtR080qwUp
-----END TRUSTED CERTIFICATE-----

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDJjCCAhECFFjLlXVdTxDdLlCifzrA0dTHHJ2mMA0GCSqGSIb3DQEBCwUAME8x
CzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl
ZmF1bHQgQ29tcGFueSBMdGQxCzAJBgNVBAMMAkNBMCAXDTIzMTAwNTA3Mjg1MFoY
DzIxMjMwOTExMDcyODUwWjBRMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs
dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMQ0wCwYDVQQDDARr
ZXkyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAspCMUdFGyKkgpMbW
+UwSg4fdKM4qLSH7voTdsdVM9aAvLvYjBQ4gpORxDZNfUz67R0Ua0/oJt9jD49Wp
qcq+tDOnp0dPtn2hFluV5PxM6d+MCSx/frPsfvyt9234okLL1zdLDNFYEbLhSPjA
ku3vHw/OwlJOxCRwTkPqcElIV4+IvIbzAgSffyokzm/wKVKEhoT6NcfeU+6wCkTu
al1X8loJ+27N6jN13oGZfH7EveBqgR8rPs55+54S/OcVG/uqL9ggOGRJiIZ3jUBk
m5cN27wKkaNg/CQwa1UjcU4qshVpknHw1dpgJ2Gbs/yUphwpEZl/FTsZFcK1KCHD
rOp3PQIDAQABMA0GCSqGSIb3DQEBCwUAA4H/AAFmEq86broBFxs0cpImaM884PBT
bvJBSsFhsOg6mi4Gt01G/lPSj/ExNtH3G5bytCYAPaRxNx/dCs7uON3p86ta4zL8
2PxgyhX1oY/GG63ETwn5s3GKpRaGTNVDWvPIM9RX6+bvX/wOg8eYXVaQlG5XYadC
Ms9lWqHaM1C/iLGNmUTGcdbvhnmQDky2CwPNm+lXogSWbrsGpAmCkXJD1H+0Mx8I
wjDVtGLBwr/8oXI8WbhvISMnS9+dd7+GLm6mU+14Kswi5I7EmBmREvkswi2IVJ6M
GL7EY3qA6iqJWqsseYyLxiMr3nBT0SETphzoDanUQI1/jXQPrWIyjqvs
-----END CERTIFICATE-----

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCykIxR0UbIqSCk
xtb5TBKDh90oziotIfu+hN2x1Uz1oC8u9iMFDiCk5HENk19TPrtHRRrT+gm32MPj
1ampyr60M6enR0+2faEWW5Xk/Ezp34wJLH9+s+x+/K33bfiiQsvXN0sM0VgRsuFI
+MCS7e8fD87CUk7EJHBOQ+pwSUhXj4i8hvMCBJ9/KiTOb/ApUoSGhPo1x95T7rAK
RO5qXVfyWgn7bs3qM3XegZl8fsS94GqBHys+znn7nhL85xUb+6ov2CA4ZEmIhneN
QGSblw3bvAqRo2D8JDBrVSNxTiqyFWmScfDV2mAnYZuz/JSmHCkRmX8VOxkVwrUo
IcOs6nc9AgMBAAECggEAPN9dDolG1aIeYD3uzCa8Sv2WjdIWe7NRlEXMI9MgvL1i
SGKdVpxV0ZCU37llLkY85tNujWP4SyXIxdMxVxIoR9syJKsBSCd0sl//bgP6nmHY
Zco3HnTswu+VyLtDHuGhhtkxKwn0uXffKBaw44XcVhz38bPIaUI4zN2HPscks8BG
j2MEl0N8P/TVrTkhgdjfoRi73VAisrEe+1wCg74BT7cmR8fEr7iNFrv955sdPGdw
UTmx8U26++wbeYQs1ZE1713SYnRQuCUFs5GGjzOhNFi27zuhI6TafoVm9PO4j+ZC
JUKTyUTBUsRMvm9z1IoHdjM8yInAv2g0J1bAeCTY+wKBgQDuMNMbNVoiXRKsSUry
22T3W6HVLfLNKiYMNxsAkJjOiyyJcC+yg9BErn/haIHSafD2WmuWbW5ASViyl6fn
D8qMluTwEaSrTgHXWI4ahWyapDShDQYp1s4dB75Aa/LVcFCay54YEtyCPzCPlj1K
jz5OBV14NEVVA2cf59fIc/LXCwKBgQC/6m3TefUp5jnN/QUOx2OtZo8Y1pVrsuMB
AuTtb21Khxn/86ZpVzySzg79/DkSNf9/sZhzj0IkviWNP5S8iAAaFC1q08CYhdCX
d7tVnHlzpZmmoHUhG6dlJZayr1duZrURp2rP18+wIsKiFRImAyjc6yswVRpZgAiG
gOkHCB231wKBgGlwXZMWy/6YOtLfYvkcm5ZQDtSCkY+2j78qiZ53Y91SiHWSntqk
NQaiRGOw0n8lfJBhOG0PphV5InV0YtQLDnurtE59UOqwDmqYfddJpujRtaZxUIAm
4XjCW7rCzm0jWdscNbCscMaLWGDHffxKaqc5AsZaRTK73eOmysOmaCI/AoGAf/yd
RZ1dzJWHE0Kb7uE2LlvpLo1clLh1/ySo+1eGMV+sDS+2WSYedWEKSoO8o9JzE/ui
Sd7OI6bTcEFotdqVBs9SAp45IP6Mv5bPziZOMLvNnnv/4RaKKkBJId0hl7TTKHTY
HMg176ce2eznb4ZH6BzFbrQyoGFsThcGUPQurX0CgYBYtkDTp21TI1nuak7xpMIY
BJQpqF5ahBf/+QYWtL0f3ca9MO2++zv5/XXitvt48cY1bCHNrVvSHgRzwSrOorZA
5u7a5zyvfXjY3LY3k0VHddaVjU0mHsjx/1ux0wO2v8wQjOVZpT7XweB3WlUEGV7C
5T/p+rmGg5Y5dTKUVCyvbQ==
-----END PRIVATE KEY-----
Loading…
Cancel
Save