From 7f5d076ad3fd628611aaab70215b1c9615affa21 Mon Sep 17 00:00:00 2001 From: JiaLin Date: Sat, 1 Aug 2020 19:40:22 +0800 Subject: [PATCH 1/4] add common classes for TLS test --- spring-cloud-commons-dependencies/pom.xml | 6 + spring-cloud-test-support/pom.xml | 12 ++ .../springframework/cloud/test/AppRunner.java | 125 +++++++++++++++++ .../cloud/test/KeyAndCert.java | 94 +++++++++++++ .../springframework/cloud/test/KeyTool.java | 126 ++++++++++++++++++ 5 files changed, 363 insertions(+) create mode 100644 spring-cloud-test-support/src/main/java/org/springframework/cloud/test/AppRunner.java create mode 100644 spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyAndCert.java create mode 100644 spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyTool.java diff --git a/spring-cloud-commons-dependencies/pom.xml b/spring-cloud-commons-dependencies/pom.xml index a703aae1d..ac66edef3 100644 --- a/spring-cloud-commons-dependencies/pom.xml +++ b/spring-cloud-commons-dependencies/pom.xml @@ -16,6 +16,7 @@ Spring Cloud Commons Dependencies 1.0.9.RELEASE + 1.64 @@ -59,6 +60,11 @@ spring-cloud-starter-loadbalancer ${project.version} + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + diff --git a/spring-cloud-test-support/pom.xml b/spring-cloud-test-support/pom.xml index add887551..a3ffaf735 100644 --- a/spring-cloud-test-support/pom.xml +++ b/spring-cloud-test-support/pom.xml @@ -55,10 +55,22 @@ junit junit + + org.bouncycastle + bcpkix-jdk15on + org.springframework spring-core + + org.springframework + spring-context + + + org.springframework.boot + spring-boot + org.springframework.boot spring-boot-starter-test diff --git a/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/AppRunner.java b/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/AppRunner.java new file mode 100644 index 000000000..1fb6217a0 --- /dev/null +++ b/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/AppRunner.java @@ -0,0 +1,125 @@ +/* + * Copyright 2018-2019 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.cloud.test; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.SocketUtils; + +public class AppRunner implements AutoCloseable { + + private Class appClass; + + private Map props; + + private ConfigurableApplicationContext app; + + public AppRunner(Class appClass) { + this.appClass = appClass; + props = new LinkedHashMap<>(); + } + + public void property(String key, String value) { + props.put(key, value); + } + + public void start() { + if (app == null) { + SpringApplicationBuilder builder = new SpringApplicationBuilder(appClass); + builder.properties("spring.jmx.enabled=false"); + builder.properties(String.format("server.port=%d", availabeTcpPort())); + builder.properties(props()); + + app = builder.build().run(); + } + } + + private int availabeTcpPort() { + return SocketUtils.findAvailableTcpPort(); + } + + private String[] props() { + List result = new ArrayList<>(); + + for (String key : props.keySet()) { + String value = props.get(key); + result.add(String.format("%s=%s", key, value)); + } + + return result.toArray(new String[0]); + } + + public void stop() { + if (app != null) { + app.stop(); + app = null; + } + } + + public ConfigurableApplicationContext app() { + return app; + } + + public String getProperty(String key) { + return app.getEnvironment().getProperty(key); + } + + public T getBean(Class type) { + return app.getBean(type); + } + + public ApplicationContext parent() { + return app.getParent(); + } + + public Map getParentBeans(Class type) { + return parent().getBeansOfType(type); + } + + public int port() { + if (app == null) { + throw new RuntimeException("App is not running."); + } + return app.getEnvironment().getProperty("server.port", Integer.class, -1); + } + + public String root() { + if (app == null) { + throw new RuntimeException("App is not running."); + } + + String protocol = tlsEnabled() ? "https" : "http"; + return String.format("%s://localhost:%d/", protocol, port()); + } + + private boolean tlsEnabled() { + return app.getEnvironment().getProperty("server.ssl.enabled", Boolean.class, + false); + } + + @Override + public void close() { + stop(); + } + +} diff --git a/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyAndCert.java b/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyAndCert.java new file mode 100644 index 000000000..ae67aeb17 --- /dev/null +++ b/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyAndCert.java @@ -0,0 +1,94 @@ +/* + * Copyright 2018-2019 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.cloud.test; + +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +public class KeyAndCert { + + private KeyPair keyPair; + + private X509Certificate certificate; + + public KeyAndCert(KeyPair keyPair, X509Certificate certificate) { + this.keyPair = keyPair; + this.certificate = certificate; + } + + public KeyPair keyPair() { + return keyPair; + } + + public PublicKey publicKey() { + return keyPair.getPublic(); + } + + public PrivateKey privateKey() { + return keyPair.getPrivate(); + } + + public X509Certificate certificate() { + return certificate; + } + + public String subject() { + String dn = certificate.getSubjectDN().getName(); + int index = dn.indexOf('='); + return dn.substring(index + 1); + } + + public KeyAndCert sign(String subject) throws Exception { + KeyTool tool = new KeyTool(); + return tool.signCertificate(subject, this); + } + + public KeyAndCert sign(KeyPair keyPair, String subject) throws Exception { + KeyTool tool = new KeyTool(); + return tool.signCertificate(keyPair, subject, this); + } + + public KeyStore storeKeyAndCert(String keyPassword) throws Exception { + KeyStore result = KeyStore.getInstance("PKCS12"); + result.load(null); + + result.setKeyEntry(subject(), keyPair.getPrivate(), keyPassword.toCharArray(), + certChain()); + return result; + } + + private Certificate[] certChain() { + return new Certificate[] { certificate() }; + } + + public KeyStore storeCert() throws Exception { + return storeCert("PKCS12"); + } + + public KeyStore storeCert(String storeType) throws Exception { + KeyStore result = KeyStore.getInstance(storeType); + result.load(null); + + result.setCertificateEntry(subject(), certificate()); + return result; + } + +} diff --git a/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyTool.java b/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyTool.java new file mode 100644 index 000000000..dfb373d97 --- /dev/null +++ b/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyTool.java @@ -0,0 +1,126 @@ +/* + * Copyright 2018-2019 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.cloud.test; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.Date; + +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +public class KeyTool { + + private static final long ONE_DAY = 1000L * 60L * 60L * 24L; + + private static final long TEN_YEARS = ONE_DAY * 365L * 10L; + + public KeyAndCert createCA(String ca) throws Exception { + KeyPair keyPair = createKeyPair(); + X509Certificate certificate = createCert(keyPair, ca); + return new KeyAndCert(keyPair, certificate); + } + + public KeyAndCert signCertificate(String subject, KeyAndCert signer) + throws Exception { + return signCertificate(createKeyPair(), subject, signer); + } + + public KeyAndCert signCertificate(KeyPair keyPair, String subject, KeyAndCert signer) + throws Exception { + X509Certificate certificate = createCert(keyPair.getPublic(), signer.privateKey(), + signer.subject(), subject); + KeyAndCert result = new KeyAndCert(keyPair, certificate); + + return result; + } + + public KeyPair createKeyPair() throws Exception { + return createKeyPair(1024); + } + + public KeyPair createKeyPair(int keySize) throws Exception { + KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); + gen.initialize(keySize, new SecureRandom()); + return gen.generateKeyPair(); + } + + public X509Certificate createCert(KeyPair keyPair, String ca) throws Exception { + JcaX509v3CertificateBuilder builder = certBuilder(keyPair.getPublic(), ca, ca); + builder.addExtension(Extension.keyUsage, true, + new KeyUsage(KeyUsage.keyCertSign)); + builder.addExtension(Extension.basicConstraints, false, + new BasicConstraints(true)); + + return signCert(builder, keyPair.getPrivate()); + } + + public X509Certificate createCert(PublicKey publicKey, PrivateKey privateKey, + String issuer, String subject) throws Exception { + JcaX509v3CertificateBuilder builder = certBuilder(publicKey, issuer, subject); + builder.addExtension(Extension.keyUsage, true, + new KeyUsage(KeyUsage.digitalSignature)); + builder.addExtension(Extension.basicConstraints, false, + new BasicConstraints(false)); + + GeneralName[] names = new GeneralName[] { + new GeneralName(GeneralName.dNSName, "localhost") }; + builder.addExtension(Extension.subjectAlternativeName, false, + GeneralNames.getInstance(new DERSequence(names))); + + return signCert(builder, privateKey); + } + + private JcaX509v3CertificateBuilder certBuilder(PublicKey publicKey, String issuer, + String subject) { + X500Name issuerName = new X500Name(String.format("dc=%s", issuer)); + X500Name subjectName = new X500Name(String.format("dc=%s", subject)); + + long now = System.currentTimeMillis(); + BigInteger serialNum = BigInteger.valueOf(now); + Date notBefore = new Date(now - ONE_DAY); + Date notAfter = new Date(now + TEN_YEARS); + + return new JcaX509v3CertificateBuilder(issuerName, serialNum, notBefore, notAfter, + subjectName, publicKey); + } + + private X509Certificate signCert(JcaX509v3CertificateBuilder builder, + PrivateKey privateKey) throws Exception { + ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA") + .build(privateKey); + X509CertificateHolder holder = builder.build(signer); + + return new JcaX509CertificateConverter().getCertificate(holder); + } + +} From b0bccfaea1bb413110a192ff07a2ac89ce154773 Mon Sep 17 00:00:00 2001 From: JiaLin Date: Tue, 18 Aug 2020 17:39:46 +0800 Subject: [PATCH 2/4] Revert "add common classes for TLS test" This reverts commit 7f5d076ad3fd628611aaab70215b1c9615affa21. --- spring-cloud-commons-dependencies/pom.xml | 6 - spring-cloud-test-support/pom.xml | 12 -- .../springframework/cloud/test/AppRunner.java | 125 ----------------- .../cloud/test/KeyAndCert.java | 94 ------------- .../springframework/cloud/test/KeyTool.java | 126 ------------------ 5 files changed, 363 deletions(-) delete mode 100644 spring-cloud-test-support/src/main/java/org/springframework/cloud/test/AppRunner.java delete mode 100644 spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyAndCert.java delete mode 100644 spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyTool.java diff --git a/spring-cloud-commons-dependencies/pom.xml b/spring-cloud-commons-dependencies/pom.xml index ac66edef3..a703aae1d 100644 --- a/spring-cloud-commons-dependencies/pom.xml +++ b/spring-cloud-commons-dependencies/pom.xml @@ -16,7 +16,6 @@ Spring Cloud Commons Dependencies 1.0.9.RELEASE - 1.64 @@ -60,11 +59,6 @@ spring-cloud-starter-loadbalancer ${project.version} - - org.bouncycastle - bcpkix-jdk15on - ${bouncycastle.version} - diff --git a/spring-cloud-test-support/pom.xml b/spring-cloud-test-support/pom.xml index a3ffaf735..add887551 100644 --- a/spring-cloud-test-support/pom.xml +++ b/spring-cloud-test-support/pom.xml @@ -55,22 +55,10 @@ junit junit - - org.bouncycastle - bcpkix-jdk15on - org.springframework spring-core - - org.springframework - spring-context - - - org.springframework.boot - spring-boot - org.springframework.boot spring-boot-starter-test diff --git a/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/AppRunner.java b/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/AppRunner.java deleted file mode 100644 index 1fb6217a0..000000000 --- a/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/AppRunner.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2018-2019 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.cloud.test; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.util.SocketUtils; - -public class AppRunner implements AutoCloseable { - - private Class appClass; - - private Map props; - - private ConfigurableApplicationContext app; - - public AppRunner(Class appClass) { - this.appClass = appClass; - props = new LinkedHashMap<>(); - } - - public void property(String key, String value) { - props.put(key, value); - } - - public void start() { - if (app == null) { - SpringApplicationBuilder builder = new SpringApplicationBuilder(appClass); - builder.properties("spring.jmx.enabled=false"); - builder.properties(String.format("server.port=%d", availabeTcpPort())); - builder.properties(props()); - - app = builder.build().run(); - } - } - - private int availabeTcpPort() { - return SocketUtils.findAvailableTcpPort(); - } - - private String[] props() { - List result = new ArrayList<>(); - - for (String key : props.keySet()) { - String value = props.get(key); - result.add(String.format("%s=%s", key, value)); - } - - return result.toArray(new String[0]); - } - - public void stop() { - if (app != null) { - app.stop(); - app = null; - } - } - - public ConfigurableApplicationContext app() { - return app; - } - - public String getProperty(String key) { - return app.getEnvironment().getProperty(key); - } - - public T getBean(Class type) { - return app.getBean(type); - } - - public ApplicationContext parent() { - return app.getParent(); - } - - public Map getParentBeans(Class type) { - return parent().getBeansOfType(type); - } - - public int port() { - if (app == null) { - throw new RuntimeException("App is not running."); - } - return app.getEnvironment().getProperty("server.port", Integer.class, -1); - } - - public String root() { - if (app == null) { - throw new RuntimeException("App is not running."); - } - - String protocol = tlsEnabled() ? "https" : "http"; - return String.format("%s://localhost:%d/", protocol, port()); - } - - private boolean tlsEnabled() { - return app.getEnvironment().getProperty("server.ssl.enabled", Boolean.class, - false); - } - - @Override - public void close() { - stop(); - } - -} diff --git a/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyAndCert.java b/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyAndCert.java deleted file mode 100644 index ae67aeb17..000000000 --- a/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyAndCert.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2018-2019 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.cloud.test; - -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; - -public class KeyAndCert { - - private KeyPair keyPair; - - private X509Certificate certificate; - - public KeyAndCert(KeyPair keyPair, X509Certificate certificate) { - this.keyPair = keyPair; - this.certificate = certificate; - } - - public KeyPair keyPair() { - return keyPair; - } - - public PublicKey publicKey() { - return keyPair.getPublic(); - } - - public PrivateKey privateKey() { - return keyPair.getPrivate(); - } - - public X509Certificate certificate() { - return certificate; - } - - public String subject() { - String dn = certificate.getSubjectDN().getName(); - int index = dn.indexOf('='); - return dn.substring(index + 1); - } - - public KeyAndCert sign(String subject) throws Exception { - KeyTool tool = new KeyTool(); - return tool.signCertificate(subject, this); - } - - public KeyAndCert sign(KeyPair keyPair, String subject) throws Exception { - KeyTool tool = new KeyTool(); - return tool.signCertificate(keyPair, subject, this); - } - - public KeyStore storeKeyAndCert(String keyPassword) throws Exception { - KeyStore result = KeyStore.getInstance("PKCS12"); - result.load(null); - - result.setKeyEntry(subject(), keyPair.getPrivate(), keyPassword.toCharArray(), - certChain()); - return result; - } - - private Certificate[] certChain() { - return new Certificate[] { certificate() }; - } - - public KeyStore storeCert() throws Exception { - return storeCert("PKCS12"); - } - - public KeyStore storeCert(String storeType) throws Exception { - KeyStore result = KeyStore.getInstance(storeType); - result.load(null); - - result.setCertificateEntry(subject(), certificate()); - return result; - } - -} diff --git a/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyTool.java b/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyTool.java deleted file mode 100644 index dfb373d97..000000000 --- a/spring-cloud-test-support/src/main/java/org/springframework/cloud/test/KeyTool.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2018-2019 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.cloud.test; - -import java.math.BigInteger; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.cert.X509Certificate; -import java.util.Date; - -import org.bouncycastle.asn1.DERSequence; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x509.BasicConstraints; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.asn1.x509.GeneralName; -import org.bouncycastle.asn1.x509.GeneralNames; -import org.bouncycastle.asn1.x509.KeyUsage; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; - -public class KeyTool { - - private static final long ONE_DAY = 1000L * 60L * 60L * 24L; - - private static final long TEN_YEARS = ONE_DAY * 365L * 10L; - - public KeyAndCert createCA(String ca) throws Exception { - KeyPair keyPair = createKeyPair(); - X509Certificate certificate = createCert(keyPair, ca); - return new KeyAndCert(keyPair, certificate); - } - - public KeyAndCert signCertificate(String subject, KeyAndCert signer) - throws Exception { - return signCertificate(createKeyPair(), subject, signer); - } - - public KeyAndCert signCertificate(KeyPair keyPair, String subject, KeyAndCert signer) - throws Exception { - X509Certificate certificate = createCert(keyPair.getPublic(), signer.privateKey(), - signer.subject(), subject); - KeyAndCert result = new KeyAndCert(keyPair, certificate); - - return result; - } - - public KeyPair createKeyPair() throws Exception { - return createKeyPair(1024); - } - - public KeyPair createKeyPair(int keySize) throws Exception { - KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); - gen.initialize(keySize, new SecureRandom()); - return gen.generateKeyPair(); - } - - public X509Certificate createCert(KeyPair keyPair, String ca) throws Exception { - JcaX509v3CertificateBuilder builder = certBuilder(keyPair.getPublic(), ca, ca); - builder.addExtension(Extension.keyUsage, true, - new KeyUsage(KeyUsage.keyCertSign)); - builder.addExtension(Extension.basicConstraints, false, - new BasicConstraints(true)); - - return signCert(builder, keyPair.getPrivate()); - } - - public X509Certificate createCert(PublicKey publicKey, PrivateKey privateKey, - String issuer, String subject) throws Exception { - JcaX509v3CertificateBuilder builder = certBuilder(publicKey, issuer, subject); - builder.addExtension(Extension.keyUsage, true, - new KeyUsage(KeyUsage.digitalSignature)); - builder.addExtension(Extension.basicConstraints, false, - new BasicConstraints(false)); - - GeneralName[] names = new GeneralName[] { - new GeneralName(GeneralName.dNSName, "localhost") }; - builder.addExtension(Extension.subjectAlternativeName, false, - GeneralNames.getInstance(new DERSequence(names))); - - return signCert(builder, privateKey); - } - - private JcaX509v3CertificateBuilder certBuilder(PublicKey publicKey, String issuer, - String subject) { - X500Name issuerName = new X500Name(String.format("dc=%s", issuer)); - X500Name subjectName = new X500Name(String.format("dc=%s", subject)); - - long now = System.currentTimeMillis(); - BigInteger serialNum = BigInteger.valueOf(now); - Date notBefore = new Date(now - ONE_DAY); - Date notAfter = new Date(now + TEN_YEARS); - - return new JcaX509v3CertificateBuilder(issuerName, serialNum, notBefore, notAfter, - subjectName, publicKey); - } - - private X509Certificate signCert(JcaX509v3CertificateBuilder builder, - PrivateKey privateKey) throws Exception { - ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA") - .build(privateKey); - X509CertificateHolder holder = builder.build(signer); - - return new JcaX509CertificateConverter().getCertificate(holder); - } - -} From 5d0492f9c2af3699e31b607d62f7d443513e7c04 Mon Sep 17 00:00:00 2001 From: JiaLin Date: Wed, 19 Aug 2020 20:55:48 +0800 Subject: [PATCH 3/4] add common tls properties --- .../cloud/configuration/TlsProperties.java | 248 ++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/TlsProperties.java diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/TlsProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/TlsProperties.java new file mode 100644 index 000000000..c1d1cf649 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/TlsProperties.java @@ -0,0 +1,248 @@ +/* + * Copyright 2017-2020 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.cloud.configuration; + +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.UnrecoverableKeyException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.net.ssl.SSLContext; + +import org.apache.http.ssl.SSLContextBuilder; + +import org.springframework.core.io.Resource; + +/** + * Common client TLS properties. + */ +public class TlsProperties { + + private static final String DEFAULT_STORE_TYPE = "PKCS12"; + + private static final Map EXTENSION_STORE_TYPES = extTypes(); + + private boolean enabled; + + private Resource keyStore; + + private String keyStoreType; + + private String keyStorePassword = ""; + + private String keyPassword = ""; + + private Resource trustStore; + + private String trustStoreType; + + private String trustStorePassword = ""; + + private static Map extTypes() { + Map result = new HashMap<>(); + + result.put("p12", "PKCS12"); + result.put("pfx", "PKCS12"); + result.put("jks", "JKS"); + + return Collections.unmodifiableMap(result); + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Resource getKeyStore() { + return keyStore; + } + + public void setKeyStore(Resource keyStore) { + this.keyStore = keyStore; + } + + public String getKeyStoreType() { + return keyStoreType; + } + + public void setKeyStoreType(String keyStoreType) { + this.keyStoreType = keyStoreType; + } + + public String getKeyStorePassword() { + return keyStorePassword; + } + + public void setKeyStorePassword(String keyStorePassword) { + this.keyStorePassword = keyStorePassword; + } + + public char[] keyStorePassword() { + return keyStorePassword.toCharArray(); + } + + public String getKeyPassword() { + return keyPassword; + } + + public void setKeyPassword(String keyPassword) { + this.keyPassword = keyPassword; + } + + public char[] keyPassword() { + return keyPassword.toCharArray(); + } + + public Resource getTrustStore() { + return trustStore; + } + + public void setTrustStore(Resource trustStore) { + this.trustStore = trustStore; + } + + public String getTrustStoreType() { + return trustStoreType; + } + + public void setTrustStoreType(String trustStoreType) { + this.trustStoreType = trustStoreType; + } + + public String getTrustStorePassword() { + return trustStorePassword; + } + + public void setTrustStorePassword(String trustStorePassword) { + this.trustStorePassword = trustStorePassword; + } + + public char[] trustStorePassword() { + return trustStorePassword.toCharArray(); + } + + @PostConstruct + public void postConstruct() { + if (keyStore != null && keyStoreType == null) { + keyStoreType = storeTypeOf(keyStore); + } + if (trustStore != null && trustStoreType == null) { + trustStoreType = storeTypeOf(trustStore); + } + } + + private String storeTypeOf(Resource resource) { + String extension = fileExtensionOf(resource); + String type = EXTENSION_STORE_TYPES.get(extension); + + return (type == null) ? DEFAULT_STORE_TYPE : type; + } + + private String fileExtensionOf(Resource resource) { + String name = resource.getFilename(); + int index = name.lastIndexOf('.'); + + return index < 0 ? "" : name.substring(index + 1).toLowerCase(); + } + + public SSLContext createSSLContext() throws GeneralSecurityException, IOException { + SSLContextBuilder builder = new SSLContextBuilder(); + char[] keyPassword = keyPassword(); + KeyStore keyStore = createKeyStore(); + + try { + builder.loadKeyMaterial(keyStore, keyPassword); + } + catch (UnrecoverableKeyException e) { + if (keyPassword.length == 0) { + // Retry if empty password, see + // https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest + builder.loadKeyMaterial(keyStore, new char[] { '\0' }); + } + else { + throw e; + } + } + + KeyStore trust = createTrustStore(); + if (trust != null) { + builder.loadTrustMaterial(trust, null); + } + + return builder.build(); + } + + private KeyStore createKeyStore() throws GeneralSecurityException, IOException { + if (keyStore == null) { + throw new KeyStoreException("Keystore not specified."); + } + if (!keyStore.exists()) { + throw new KeyStoreException("Keystore not exists: " + keyStore); + } + + KeyStore result = KeyStore.getInstance(keyStoreType); + char[] keyStorePassword = keyStorePassword(); + + try { + loadKeyStore(result, keyStore, keyStorePassword); + } + catch (IOException e) { + // Retry if empty password, see + // https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest + if (keyStorePassword.length == 0) { + loadKeyStore(result, keyStore, new char[] { '\0' }); + } + else { + throw e; + } + } + + return result; + } + + private static void loadKeyStore(KeyStore keyStore, Resource keyStoreResource, + char[] keyStorePassword) throws IOException, GeneralSecurityException { + try (InputStream inputStream = keyStoreResource.getInputStream()) { + keyStore.load(inputStream, keyStorePassword); + } + } + + private KeyStore createTrustStore() throws GeneralSecurityException, IOException { + if (trustStore == null) { + return null; + } + if (!trustStore.exists()) { + throw new KeyStoreException("KeyStore not exists: " + trustStore); + } + + KeyStore result = KeyStore.getInstance(trustStoreType); + try (InputStream input = trustStore.getInputStream()) { + result.load(input, trustStorePassword()); + } + return result; + } + +} From 2b7710748b8312e2ee01310e488f83ee8ffe6713 Mon Sep 17 00:00:00 2001 From: JiaLin Date: Thu, 20 Aug 2020 00:45:51 +0800 Subject: [PATCH 4/4] add unit tests for tls properties --- .../configuration/SSLContextFactory.java | 119 +++++++++++++++ .../cloud/configuration/TlsProperties.java | 86 ----------- .../cloud/configuration/KeyAndCert.java | 94 ++++++++++++ .../cloud/configuration/KeyTool.java | 126 ++++++++++++++++ .../configuration/SSHContextFactoryTests.java | 142 ++++++++++++++++++ 5 files changed, 481 insertions(+), 86 deletions(-) create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SSLContextFactory.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/KeyAndCert.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/KeyTool.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/SSHContextFactoryTests.java diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SSLContextFactory.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SSLContextFactory.java new file mode 100644 index 000000000..381c59d34 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SSLContextFactory.java @@ -0,0 +1,119 @@ +/* + * Copyright 2017-2020 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.cloud.configuration; + +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.UnrecoverableKeyException; + +import javax.net.ssl.SSLContext; + +import org.apache.http.ssl.SSLContextBuilder; + +import org.springframework.core.io.Resource; + +public class SSLContextFactory { + + private TlsProperties properties; + + public SSLContextFactory(TlsProperties properties) { + this.properties = properties; + } + + public SSLContext createSSLContext() throws GeneralSecurityException, IOException { + SSLContextBuilder builder = new SSLContextBuilder(); + char[] keyPassword = properties.keyPassword(); + KeyStore keyStore = createKeyStore(); + + try { + builder.loadKeyMaterial(keyStore, keyPassword); + } + catch (UnrecoverableKeyException e) { + if (keyPassword.length == 0) { + // Retry if empty password, see + // https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest + builder.loadKeyMaterial(keyStore, new char[] { '\0' }); + } + else { + throw e; + } + } + + KeyStore trust = createTrustStore(); + if (trust != null) { + builder.loadTrustMaterial(trust, null); + } + + return builder.build(); + } + + public KeyStore createKeyStore() throws GeneralSecurityException, IOException { + if (properties.getKeyStore() == null) { + throw new KeyStoreException("Keystore not specified."); + } + if (!properties.getKeyStore().exists()) { + throw new KeyStoreException( + "Keystore not exists: " + properties.getKeyStore()); + } + + KeyStore result = KeyStore.getInstance(properties.getKeyStoreType()); + char[] keyStorePassword = properties.keyStorePassword(); + + try { + loadKeyStore(result, properties.getKeyStore(), keyStorePassword); + } + catch (IOException e) { + // Retry if empty password, see + // https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest + if (keyStorePassword.length == 0) { + loadKeyStore(result, properties.getKeyStore(), new char[] { '\0' }); + } + else { + throw e; + } + } + + return result; + } + + private static void loadKeyStore(KeyStore keyStore, Resource keyStoreResource, + char[] keyStorePassword) throws IOException, GeneralSecurityException { + try (InputStream inputStream = keyStoreResource.getInputStream()) { + keyStore.load(inputStream, keyStorePassword); + } + } + + public KeyStore createTrustStore() throws GeneralSecurityException, IOException { + if (properties.getTrustStore() == null) { + return null; + } + if (!properties.getTrustStore().exists()) { + throw new KeyStoreException( + "KeyStore not exists: " + properties.getTrustStore()); + } + + KeyStore result = KeyStore.getInstance(properties.getTrustStoreType()); + try (InputStream input = properties.getTrustStore().getInputStream()) { + result.load(input, properties.trustStorePassword()); + } + return result; + } + +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/TlsProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/TlsProperties.java index c1d1cf649..43aba0e32 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/TlsProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/TlsProperties.java @@ -16,20 +16,11 @@ package org.springframework.cloud.configuration; -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.UnrecoverableKeyException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.annotation.PostConstruct; -import javax.net.ssl.SSLContext; - -import org.apache.http.ssl.SSLContextBuilder; import org.springframework.core.io.Resource; @@ -168,81 +159,4 @@ private String fileExtensionOf(Resource resource) { return index < 0 ? "" : name.substring(index + 1).toLowerCase(); } - public SSLContext createSSLContext() throws GeneralSecurityException, IOException { - SSLContextBuilder builder = new SSLContextBuilder(); - char[] keyPassword = keyPassword(); - KeyStore keyStore = createKeyStore(); - - try { - builder.loadKeyMaterial(keyStore, keyPassword); - } - catch (UnrecoverableKeyException e) { - if (keyPassword.length == 0) { - // Retry if empty password, see - // https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest - builder.loadKeyMaterial(keyStore, new char[] { '\0' }); - } - else { - throw e; - } - } - - KeyStore trust = createTrustStore(); - if (trust != null) { - builder.loadTrustMaterial(trust, null); - } - - return builder.build(); - } - - private KeyStore createKeyStore() throws GeneralSecurityException, IOException { - if (keyStore == null) { - throw new KeyStoreException("Keystore not specified."); - } - if (!keyStore.exists()) { - throw new KeyStoreException("Keystore not exists: " + keyStore); - } - - KeyStore result = KeyStore.getInstance(keyStoreType); - char[] keyStorePassword = keyStorePassword(); - - try { - loadKeyStore(result, keyStore, keyStorePassword); - } - catch (IOException e) { - // Retry if empty password, see - // https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest - if (keyStorePassword.length == 0) { - loadKeyStore(result, keyStore, new char[] { '\0' }); - } - else { - throw e; - } - } - - return result; - } - - private static void loadKeyStore(KeyStore keyStore, Resource keyStoreResource, - char[] keyStorePassword) throws IOException, GeneralSecurityException { - try (InputStream inputStream = keyStoreResource.getInputStream()) { - keyStore.load(inputStream, keyStorePassword); - } - } - - private KeyStore createTrustStore() throws GeneralSecurityException, IOException { - if (trustStore == null) { - return null; - } - if (!trustStore.exists()) { - throw new KeyStoreException("KeyStore not exists: " + trustStore); - } - - KeyStore result = KeyStore.getInstance(trustStoreType); - try (InputStream input = trustStore.getInputStream()) { - result.load(input, trustStorePassword()); - } - return result; - } - } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/KeyAndCert.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/KeyAndCert.java new file mode 100644 index 000000000..e2c7e0066 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/KeyAndCert.java @@ -0,0 +1,94 @@ +/* + * Copyright 2018-2019 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.cloud.configuration; + +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +public class KeyAndCert { + + private KeyPair keyPair; + + private X509Certificate certificate; + + public KeyAndCert(KeyPair keyPair, X509Certificate certificate) { + this.keyPair = keyPair; + this.certificate = certificate; + } + + public KeyPair keyPair() { + return keyPair; + } + + public PublicKey publicKey() { + return keyPair.getPublic(); + } + + public PrivateKey privateKey() { + return keyPair.getPrivate(); + } + + public X509Certificate certificate() { + return certificate; + } + + public String subject() { + String dn = certificate.getSubjectDN().getName(); + int index = dn.indexOf('='); + return dn.substring(index + 1); + } + + public KeyAndCert sign(String subject) throws Exception { + KeyTool tool = new KeyTool(); + return tool.signCertificate(subject, this); + } + + public KeyAndCert sign(KeyPair keyPair, String subject) throws Exception { + KeyTool tool = new KeyTool(); + return tool.signCertificate(keyPair, subject, this); + } + + public KeyStore storeKeyAndCert(String keyPassword) throws Exception { + KeyStore result = KeyStore.getInstance("PKCS12"); + result.load(null); + + result.setKeyEntry(subject(), keyPair.getPrivate(), keyPassword.toCharArray(), + certChain()); + return result; + } + + private Certificate[] certChain() { + return new Certificate[] { certificate() }; + } + + public KeyStore storeCert() throws Exception { + return storeCert("PKCS12"); + } + + public KeyStore storeCert(String storeType) throws Exception { + KeyStore result = KeyStore.getInstance(storeType); + result.load(null); + + result.setCertificateEntry(subject(), certificate()); + return result; + } + +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/KeyTool.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/KeyTool.java new file mode 100644 index 000000000..48d4c888c --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/KeyTool.java @@ -0,0 +1,126 @@ +/* + * Copyright 2018-2019 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.cloud.configuration; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.Date; + +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +public class KeyTool { + + private static final long ONE_DAY = 1000L * 60L * 60L * 24L; + + private static final long TEN_YEARS = ONE_DAY * 365L * 10L; + + public KeyAndCert createCA(String ca) throws Exception { + KeyPair keyPair = createKeyPair(); + X509Certificate certificate = createCert(keyPair, ca); + return new KeyAndCert(keyPair, certificate); + } + + public KeyAndCert signCertificate(String subject, KeyAndCert signer) + throws Exception { + return signCertificate(createKeyPair(), subject, signer); + } + + public KeyAndCert signCertificate(KeyPair keyPair, String subject, KeyAndCert signer) + throws Exception { + X509Certificate certificate = createCert(keyPair.getPublic(), signer.privateKey(), + signer.subject(), subject); + KeyAndCert result = new KeyAndCert(keyPair, certificate); + + return result; + } + + public KeyPair createKeyPair() throws Exception { + return createKeyPair(1024); + } + + public KeyPair createKeyPair(int keySize) throws Exception { + KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); + gen.initialize(keySize, new SecureRandom()); + return gen.generateKeyPair(); + } + + public X509Certificate createCert(KeyPair keyPair, String ca) throws Exception { + JcaX509v3CertificateBuilder builder = certBuilder(keyPair.getPublic(), ca, ca); + builder.addExtension(Extension.keyUsage, true, + new KeyUsage(KeyUsage.keyCertSign)); + builder.addExtension(Extension.basicConstraints, false, + new BasicConstraints(true)); + + return signCert(builder, keyPair.getPrivate()); + } + + public X509Certificate createCert(PublicKey publicKey, PrivateKey privateKey, + String issuer, String subject) throws Exception { + JcaX509v3CertificateBuilder builder = certBuilder(publicKey, issuer, subject); + builder.addExtension(Extension.keyUsage, true, + new KeyUsage(KeyUsage.digitalSignature)); + builder.addExtension(Extension.basicConstraints, false, + new BasicConstraints(false)); + + GeneralName[] names = new GeneralName[] { + new GeneralName(GeneralName.dNSName, "localhost") }; + builder.addExtension(Extension.subjectAlternativeName, false, + GeneralNames.getInstance(new DERSequence(names))); + + return signCert(builder, privateKey); + } + + private JcaX509v3CertificateBuilder certBuilder(PublicKey publicKey, String issuer, + String subject) { + X500Name issuerName = new X500Name(String.format("dc=%s", issuer)); + X500Name subjectName = new X500Name(String.format("dc=%s", subject)); + + long now = System.currentTimeMillis(); + BigInteger serialNum = BigInteger.valueOf(now); + Date notBefore = new Date(now - ONE_DAY); + Date notAfter = new Date(now + TEN_YEARS); + + return new JcaX509v3CertificateBuilder(issuerName, serialNum, notBefore, notAfter, + subjectName, publicKey); + } + + private X509Certificate signCert(JcaX509v3CertificateBuilder builder, + PrivateKey privateKey) throws Exception { + ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA") + .build(privateKey); + X509CertificateHolder holder = builder.build(signer); + + return new JcaX509CertificateConverter().getCertificate(holder); + } + +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/SSHContextFactoryTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/SSHContextFactoryTests.java new file mode 100644 index 000000000..049a64810 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/SSHContextFactoryTests.java @@ -0,0 +1,142 @@ +/* + * Copyright 2012-2020 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.cloud.configuration; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyStore; +import java.security.cert.Certificate; + +import javax.net.ssl.SSLContext; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SSHContextFactoryTests { + + private static final String KEY_STORE_PASSWORD = "test-key-store-password"; + + private static final String KEY_PASSWORD = "test-key-password"; + + private static KeyAndCert ca; + + private static KeyAndCert cert; + + private static File keyStore; + + private static File trustStore; + + private TlsProperties properties; + + @BeforeClass + public static void createKeyStoreAndTrustStore() throws Exception { + KeyTool tool = new KeyTool(); + + ca = tool.createCA("MyCA"); + cert = ca.sign("MyCert"); + + keyStore = saveKeyAndCert(cert); + trustStore = saveCert(ca); + } + + private static File saveKeyAndCert(KeyAndCert keyCert) throws Exception { + return saveKeyStore(keyCert.subject(), + () -> keyCert.storeKeyAndCert(KEY_PASSWORD)); + } + + private static File saveCert(KeyAndCert keyCert) throws Exception { + return saveKeyStore(keyCert.subject(), () -> keyCert.storeCert()); + } + + private static File saveKeyStore(String prefix, KeyStoreSupplier func) + throws Exception { + File result = File.createTempFile(prefix, ".p12"); + result.deleteOnExit(); + + try (OutputStream output = new FileOutputStream(result)) { + KeyStore store = func.createKeyStore(); + store.store(output, KEY_STORE_PASSWORD.toCharArray()); + } + return result; + } + + @Before + public void createProperties() { + properties = new TlsProperties(); + + properties.setEnabled(true); + properties.setKeyStore(resourceOf(keyStore)); + properties.setKeyStorePassword(KEY_STORE_PASSWORD); + properties.setKeyPassword(KEY_PASSWORD); + properties.setTrustStore(resourceOf(trustStore)); + properties.setTrustStorePassword(KEY_STORE_PASSWORD); + + properties.postConstruct(); + } + + private Resource resourceOf(File file) { + return new FileSystemResource(file); + } + + @Test + public void createKeyStoreFromProperties() + throws GeneralSecurityException, IOException { + SSLContextFactory factory = new SSLContextFactory(properties); + KeyStore store = factory.createKeyStore(); + + Certificate c = store.getCertificate("MyCert"); + assertThat(c).isEqualTo(cert.certificate()); + + Key key = store.getKey("MyCert", KEY_PASSWORD.toCharArray()); + assertThat(key).isEqualTo(cert.privateKey()); + } + + @Test + public void createTrustStoreFromProperties() + throws GeneralSecurityException, IOException { + SSLContextFactory factory = new SSLContextFactory(properties); + KeyStore store = factory.createTrustStore(); + + Certificate c = store.getCertificate("MyCA"); + assertThat(c).isEqualTo(ca.certificate()); + } + + @Test + public void createSSLContextFromProperties() + throws GeneralSecurityException, IOException { + SSLContextFactory factory = new SSLContextFactory(properties); + SSLContext context = factory.createSSLContext(); + assertThat(context).isNotNull(); + } + + interface KeyStoreSupplier { + + KeyStore createKeyStore() throws Exception; + + } + +}