Browse Source
Update Tomcat, Jetty, Undertow and Netty servers so that an SslBundle is used to apply SSL configuration. Existing `Ssl` properties are internally adapted to an `SslBundle` using the `WebServerSslBundle` class. Additionally, if `Ssl.getBundle()` returns a non-null value the the `SslBundles` bean will be used to find a registered bundle by name. See gh-34814pull/35107/head
48 changed files with 1025 additions and 1759 deletions
@ -1,109 +0,0 @@
@@ -1,109 +0,0 @@
|
||||
/* |
||||
* 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.web.server; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStreamReader; |
||||
import java.io.Reader; |
||||
import java.net.URL; |
||||
import java.security.cert.CertificateException; |
||||
import java.security.cert.CertificateFactory; |
||||
import java.security.cert.X509Certificate; |
||||
import java.util.ArrayList; |
||||
import java.util.Base64; |
||||
import java.util.List; |
||||
import java.util.function.Consumer; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import org.springframework.util.FileCopyUtils; |
||||
import org.springframework.util.ResourceUtils; |
||||
|
||||
/** |
||||
* Parser for X.509 certificates in PEM format. |
||||
* |
||||
* @author Scott Frederick |
||||
* @author Phillip Webb |
||||
*/ |
||||
final class CertificateParser { |
||||
|
||||
private static final String HEADER = "-+BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+"; |
||||
|
||||
private static final String BASE64_TEXT = "([a-z0-9+/=\\r\\n]+)"; |
||||
|
||||
private static final String FOOTER = "-+END\\s+.*CERTIFICATE[^-]*-+"; |
||||
|
||||
private static final Pattern PATTERN = Pattern.compile(HEADER + BASE64_TEXT + FOOTER, Pattern.CASE_INSENSITIVE); |
||||
|
||||
private CertificateParser() { |
||||
} |
||||
|
||||
/** |
||||
* Load certificates from the specified resource. |
||||
* @param path the certificate to parse |
||||
* @return the parsed certificates |
||||
*/ |
||||
static X509Certificate[] parse(String path) { |
||||
CertificateFactory factory = getCertificateFactory(); |
||||
List<X509Certificate> certificates = new ArrayList<>(); |
||||
readCertificates(path, factory, certificates::add); |
||||
return certificates.toArray(new X509Certificate[0]); |
||||
} |
||||
|
||||
private static CertificateFactory getCertificateFactory() { |
||||
try { |
||||
return CertificateFactory.getInstance("X.509"); |
||||
} |
||||
catch (CertificateException ex) { |
||||
throw new IllegalStateException("Unable to get X.509 certificate factory", ex); |
||||
} |
||||
} |
||||
|
||||
private static void readCertificates(String resource, CertificateFactory factory, |
||||
Consumer<X509Certificate> consumer) { |
||||
try { |
||||
String text = readText(resource); |
||||
Matcher matcher = PATTERN.matcher(text); |
||||
while (matcher.find()) { |
||||
String encodedText = matcher.group(1); |
||||
byte[] decodedBytes = decodeBase64(encodedText); |
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(decodedBytes); |
||||
while (inputStream.available() > 0) { |
||||
consumer.accept((X509Certificate) factory.generateCertificate(inputStream)); |
||||
} |
||||
} |
||||
} |
||||
catch (CertificateException | IOException ex) { |
||||
throw new IllegalStateException("Error reading certificate from '" + resource + "' : " + ex.getMessage(), |
||||
ex); |
||||
} |
||||
} |
||||
|
||||
private static String readText(String resource) throws IOException { |
||||
URL url = ResourceUtils.getURL(resource); |
||||
try (Reader reader = new InputStreamReader(url.openStream())) { |
||||
return FileCopyUtils.copyToString(reader); |
||||
} |
||||
} |
||||
|
||||
private static byte[] decodeBase64(String content) { |
||||
byte[] bytes = content.replaceAll("\r", "").replaceAll("\n", "").getBytes(); |
||||
return Base64.getDecoder().decode(bytes); |
||||
} |
||||
|
||||
} |
||||
@ -1,97 +0,0 @@
@@ -1,97 +0,0 @@
|
||||
/* |
||||
* 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.web.server; |
||||
|
||||
import java.io.InputStream; |
||||
import java.net.URL; |
||||
import java.security.KeyStore; |
||||
|
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ResourceUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* An {@link SslStoreProvider} that creates key and trust stores from Java keystore files. |
||||
* |
||||
* @author Scott Frederick |
||||
*/ |
||||
final class JavaKeyStoreSslStoreProvider implements SslStoreProvider { |
||||
|
||||
private final Ssl ssl; |
||||
|
||||
private JavaKeyStoreSslStoreProvider(Ssl ssl) { |
||||
this.ssl = ssl; |
||||
} |
||||
|
||||
@Override |
||||
public KeyStore getKeyStore() throws Exception { |
||||
return createKeyStore(this.ssl.getKeyStoreType(), this.ssl.getKeyStoreProvider(), this.ssl.getKeyStore(), |
||||
this.ssl.getKeyStorePassword()); |
||||
} |
||||
|
||||
@Override |
||||
public KeyStore getTrustStore() throws Exception { |
||||
if (this.ssl.getTrustStore() == null) { |
||||
return null; |
||||
} |
||||
return createKeyStore(this.ssl.getTrustStoreType(), this.ssl.getTrustStoreProvider(), this.ssl.getTrustStore(), |
||||
this.ssl.getTrustStorePassword()); |
||||
} |
||||
|
||||
@Override |
||||
public String getKeyPassword() { |
||||
return this.ssl.getKeyPassword(); |
||||
} |
||||
|
||||
private KeyStore createKeyStore(String type, String provider, String location, String password) throws Exception { |
||||
type = (type != null) ? type : "JKS"; |
||||
char[] passwordChars = (password != null) ? password.toCharArray() : null; |
||||
KeyStore store = (provider != null) ? KeyStore.getInstance(type, provider) : KeyStore.getInstance(type); |
||||
if (type.equalsIgnoreCase("PKCS11")) { |
||||
Assert.state(!StringUtils.hasText(location), |
||||
() -> "KeyStore location is '" + location + "', but must be empty or null for PKCS11 key stores"); |
||||
store.load(null, passwordChars); |
||||
} |
||||
else { |
||||
Assert.state(StringUtils.hasText(location), () -> "KeyStore location must not be empty or null"); |
||||
try { |
||||
URL url = ResourceUtils.getURL(location); |
||||
try (InputStream stream = url.openStream()) { |
||||
store.load(stream, passwordChars); |
||||
} |
||||
} |
||||
catch (Exception ex) { |
||||
throw new IllegalStateException("Could not load key store '" + location + "'", ex); |
||||
} |
||||
} |
||||
return store; |
||||
} |
||||
|
||||
/** |
||||
* Create an {@link SslStoreProvider} if the appropriate SSL properties are |
||||
* configured. |
||||
* @param ssl the SSL properties |
||||
* @return an {@code SslStoreProvider} or {@code null} |
||||
*/ |
||||
static SslStoreProvider from(Ssl ssl) { |
||||
if (ssl != null && ssl.isEnabled()) { |
||||
return new JavaKeyStoreSslStoreProvider(ssl); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
@ -1,256 +0,0 @@
@@ -1,256 +0,0 @@
|
||||
/* |
||||
* 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.web.server; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStreamReader; |
||||
import java.io.Reader; |
||||
import java.net.URL; |
||||
import java.security.GeneralSecurityException; |
||||
import java.security.KeyFactory; |
||||
import java.security.PrivateKey; |
||||
import java.security.spec.PKCS8EncodedKeySpec; |
||||
import java.util.ArrayList; |
||||
import java.util.Base64; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.function.Function; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import org.springframework.util.FileCopyUtils; |
||||
import org.springframework.util.ResourceUtils; |
||||
|
||||
/** |
||||
* Parser for PKCS private key files in PEM format. |
||||
* |
||||
* @author Scott Frederick |
||||
* @author Phillip Webb |
||||
*/ |
||||
final class PrivateKeyParser { |
||||
|
||||
private static final String PKCS1_HEADER = "-+BEGIN\\s+RSA\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+"; |
||||
|
||||
private static final String PKCS1_FOOTER = "-+END\\s+RSA\\s+PRIVATE\\s+KEY[^-]*-+"; |
||||
|
||||
private static final String PKCS8_HEADER = "-+BEGIN\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+"; |
||||
|
||||
private static final String PKCS8_FOOTER = "-+END\\s+PRIVATE\\s+KEY[^-]*-+"; |
||||
|
||||
private static final String EC_HEADER = "-+BEGIN\\s+EC\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+"; |
||||
|
||||
private static final String EC_FOOTER = "-+END\\s+EC\\s+PRIVATE\\s+KEY[^-]*-+"; |
||||
|
||||
private static final String BASE64_TEXT = "([a-z0-9+/=\\r\\n]+)"; |
||||
|
||||
private static final List<PemParser> PEM_PARSERS; |
||||
static { |
||||
List<PemParser> parsers = new ArrayList<>(); |
||||
parsers.add(new PemParser(PKCS1_HEADER, PKCS1_FOOTER, "RSA", PrivateKeyParser::createKeySpecForPkcs1)); |
||||
parsers.add(new PemParser(EC_HEADER, EC_FOOTER, "EC", PrivateKeyParser::createKeySpecForEc)); |
||||
parsers.add(new PemParser(PKCS8_HEADER, PKCS8_FOOTER, "RSA", PKCS8EncodedKeySpec::new)); |
||||
PEM_PARSERS = Collections.unmodifiableList(parsers); |
||||
} |
||||
|
||||
/** |
||||
* ASN.1 encoded object identifier {@literal 1.2.840.113549.1.1.1}. |
||||
*/ |
||||
private static final int[] RSA_ALGORITHM = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }; |
||||
|
||||
/** |
||||
* ASN.1 encoded object identifier {@literal 1.2.840.10045.2.1}. |
||||
*/ |
||||
private static final int[] EC_ALGORITHM = { 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 }; |
||||
|
||||
/** |
||||
* ASN.1 encoded object identifier {@literal 1.3.132.0.34}. |
||||
*/ |
||||
private static final int[] EC_PARAMETERS = { 0x2b, 0x81, 0x04, 0x00, 0x22 }; |
||||
|
||||
private PrivateKeyParser() { |
||||
} |
||||
|
||||
private static PKCS8EncodedKeySpec createKeySpecForPkcs1(byte[] bytes) { |
||||
return createKeySpecForAlgorithm(bytes, RSA_ALGORITHM, null); |
||||
} |
||||
|
||||
private static PKCS8EncodedKeySpec createKeySpecForEc(byte[] bytes) { |
||||
return createKeySpecForAlgorithm(bytes, EC_ALGORITHM, EC_PARAMETERS); |
||||
} |
||||
|
||||
private static PKCS8EncodedKeySpec createKeySpecForAlgorithm(byte[] bytes, int[] algorithm, int[] parameters) { |
||||
try { |
||||
DerEncoder encoder = new DerEncoder(); |
||||
encoder.integer(0x00); // Version 0
|
||||
DerEncoder algorithmIdentifier = new DerEncoder(); |
||||
algorithmIdentifier.objectIdentifier(algorithm); |
||||
algorithmIdentifier.objectIdentifier(parameters); |
||||
byte[] byteArray = algorithmIdentifier.toByteArray(); |
||||
encoder.sequence(byteArray); |
||||
encoder.octetString(bytes); |
||||
return new PKCS8EncodedKeySpec(encoder.toSequence()); |
||||
} |
||||
catch (IOException ex) { |
||||
throw new IllegalStateException(ex); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Load a private key from the specified resource. |
||||
* @param resource the private key to parse |
||||
* @return the parsed private key |
||||
*/ |
||||
static PrivateKey parse(String resource) { |
||||
try { |
||||
String text = readText(resource); |
||||
for (PemParser pemParser : PEM_PARSERS) { |
||||
PrivateKey privateKey = pemParser.parse(text); |
||||
if (privateKey != null) { |
||||
return privateKey; |
||||
} |
||||
} |
||||
throw new IllegalStateException("Unrecognized private key format"); |
||||
} |
||||
catch (Exception ex) { |
||||
throw new IllegalStateException("Error loading private key file " + resource, ex); |
||||
} |
||||
} |
||||
|
||||
private static String readText(String resource) throws IOException { |
||||
URL url = ResourceUtils.getURL(resource); |
||||
try (Reader reader = new InputStreamReader(url.openStream())) { |
||||
return FileCopyUtils.copyToString(reader); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Parser for a specific PEM format. |
||||
*/ |
||||
private static class PemParser { |
||||
|
||||
private final Pattern pattern; |
||||
|
||||
private final String algorithm; |
||||
|
||||
private final Function<byte[], PKCS8EncodedKeySpec> keySpecFactory; |
||||
|
||||
PemParser(String header, String footer, String algorithm, |
||||
Function<byte[], PKCS8EncodedKeySpec> keySpecFactory) { |
||||
this.pattern = Pattern.compile(header + BASE64_TEXT + footer, Pattern.CASE_INSENSITIVE); |
||||
this.algorithm = algorithm; |
||||
this.keySpecFactory = keySpecFactory; |
||||
} |
||||
|
||||
PrivateKey parse(String text) { |
||||
Matcher matcher = this.pattern.matcher(text); |
||||
return (!matcher.find()) ? null : parse(decodeBase64(matcher.group(1))); |
||||
} |
||||
|
||||
private static byte[] decodeBase64(String content) { |
||||
byte[] contentBytes = content.replaceAll("\r", "").replaceAll("\n", "").getBytes(); |
||||
return Base64.getDecoder().decode(contentBytes); |
||||
} |
||||
|
||||
private PrivateKey parse(byte[] bytes) { |
||||
try { |
||||
PKCS8EncodedKeySpec keySpec = this.keySpecFactory.apply(bytes); |
||||
KeyFactory keyFactory = KeyFactory.getInstance(this.algorithm); |
||||
return keyFactory.generatePrivate(keySpec); |
||||
} |
||||
catch (GeneralSecurityException ex) { |
||||
throw new IllegalArgumentException("Unexpected key format", ex); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Simple ASN.1 DER encoder. |
||||
*/ |
||||
static class DerEncoder { |
||||
|
||||
private final ByteArrayOutputStream stream = new ByteArrayOutputStream(); |
||||
|
||||
void objectIdentifier(int... encodedObjectIdentifier) throws IOException { |
||||
int code = (encodedObjectIdentifier != null) ? 0x06 : 0x05; |
||||
codeLengthBytes(code, bytes(encodedObjectIdentifier)); |
||||
} |
||||
|
||||
void integer(int... encodedInteger) throws IOException { |
||||
codeLengthBytes(0x02, bytes(encodedInteger)); |
||||
} |
||||
|
||||
void octetString(byte[] bytes) throws IOException { |
||||
codeLengthBytes(0x04, bytes); |
||||
} |
||||
|
||||
void sequence(int... elements) throws IOException { |
||||
sequence(bytes(elements)); |
||||
} |
||||
|
||||
void sequence(byte[] bytes) throws IOException { |
||||
codeLengthBytes(0x30, bytes); |
||||
} |
||||
|
||||
void codeLengthBytes(int code, byte[] bytes) throws IOException { |
||||
this.stream.write(code); |
||||
int length = (bytes != null) ? bytes.length : 0; |
||||
if (length <= 127) { |
||||
this.stream.write(length & 0xFF); |
||||
} |
||||
else { |
||||
ByteArrayOutputStream lengthStream = new ByteArrayOutputStream(); |
||||
while (length != 0) { |
||||
lengthStream.write(length & 0xFF); |
||||
length = length >> 8; |
||||
} |
||||
byte[] lengthBytes = lengthStream.toByteArray(); |
||||
this.stream.write(0x80 | lengthBytes.length); |
||||
for (int i = lengthBytes.length - 1; i >= 0; i--) { |
||||
this.stream.write(lengthBytes[i]); |
||||
} |
||||
} |
||||
if (bytes != null) { |
||||
this.stream.write(bytes); |
||||
} |
||||
} |
||||
|
||||
private static byte[] bytes(int... elements) { |
||||
if (elements == null) { |
||||
return null; |
||||
} |
||||
byte[] result = new byte[elements.length]; |
||||
for (int i = 0; i < elements.length; i++) { |
||||
result[i] = (byte) elements[i]; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
byte[] toSequence() throws IOException { |
||||
DerEncoder sequenceEncoder = new DerEncoder(); |
||||
sequenceEncoder.sequence(toByteArray()); |
||||
return sequenceEncoder.toByteArray(); |
||||
} |
||||
|
||||
byte[] toByteArray() { |
||||
return this.stream.toByteArray(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -1,41 +0,0 @@
@@ -1,41 +0,0 @@
|
||||
/* |
||||
* 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.web.server; |
||||
|
||||
/** |
||||
* Creates an {@link SslStoreProvider} based on SSL configuration properties. |
||||
* |
||||
* @author Scott Frederick |
||||
* @since 3.1.0 |
||||
*/ |
||||
public final class SslStoreProviderFactory { |
||||
|
||||
private SslStoreProviderFactory() { |
||||
} |
||||
|
||||
/** |
||||
* Create an {@link SslStoreProvider} if the appropriate SSL properties are |
||||
* configured. |
||||
* @param ssl the SSL properties |
||||
* @return an {@code SslStoreProvider} or {@code null} |
||||
*/ |
||||
public static SslStoreProvider from(Ssl ssl) { |
||||
SslStoreProvider sslStoreProvider = CertificateFileSslStoreProvider.from(ssl); |
||||
return ((sslStoreProvider != null) ? sslStoreProvider : JavaKeyStoreSslStoreProvider.from(ssl)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,214 @@
@@ -0,0 +1,214 @@
|
||||
/* |
||||
* 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.web.server; |
||||
|
||||
import java.security.KeyStore; |
||||
|
||||
import org.springframework.boot.ssl.NoSuchSslBundleException; |
||||
import org.springframework.boot.ssl.SslBundle; |
||||
import org.springframework.boot.ssl.SslBundleKey; |
||||
import org.springframework.boot.ssl.SslBundles; |
||||
import org.springframework.boot.ssl.SslManagerBundle; |
||||
import org.springframework.boot.ssl.SslOptions; |
||||
import org.springframework.boot.ssl.SslStoreBundle; |
||||
import org.springframework.boot.ssl.jks.JksSslStoreBundle; |
||||
import org.springframework.boot.ssl.jks.JksSslStoreDetails; |
||||
import org.springframework.boot.ssl.pem.PemSslStoreBundle; |
||||
import org.springframework.boot.ssl.pem.PemSslStoreDetails; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
import org.springframework.util.function.ThrowingSupplier; |
||||
|
||||
/** |
||||
* {@link SslBundle} backed by {@link Ssl} or an {@link SslStoreProvider}. |
||||
* |
||||
* @author Scott Frederick |
||||
* @author Phillip Webb |
||||
* @since 3.1.0 |
||||
*/ |
||||
public final class WebServerSslBundle implements SslBundle { |
||||
|
||||
private final SslStoreBundle stores; |
||||
|
||||
private final SslBundleKey key; |
||||
|
||||
private final SslOptions options; |
||||
|
||||
private final String protocol; |
||||
|
||||
private final SslManagerBundle managers; |
||||
|
||||
private WebServerSslBundle(SslStoreBundle stores, String keyPassword, Ssl ssl) { |
||||
this.stores = stores; |
||||
this.key = SslBundleKey.of(keyPassword, ssl.getKeyAlias()); |
||||
this.protocol = ssl.getProtocol(); |
||||
this.options = SslOptions.of(ssl.getCiphers(), ssl.getEnabledProtocols()); |
||||
this.managers = SslManagerBundle.from(this.stores, this.key); |
||||
} |
||||
|
||||
private static SslStoreBundle createPemStoreBundle(Ssl ssl) { |
||||
PemSslStoreDetails keyStoreDetails = new PemSslStoreDetails(ssl.getKeyStoreType(), ssl.getCertificate(), |
||||
ssl.getCertificatePrivateKey()); |
||||
PemSslStoreDetails trustStoreDetails = new PemSslStoreDetails(ssl.getTrustStoreType(), |
||||
ssl.getTrustCertificate(), ssl.getTrustCertificatePrivateKey()); |
||||
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, ssl.getKeyAlias()); |
||||
} |
||||
|
||||
private static SslStoreBundle createJksStoreBundle(Ssl ssl) { |
||||
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails(ssl.getKeyStoreType(), ssl.getKeyStoreProvider(), |
||||
ssl.getKeyStore(), ssl.getKeyStorePassword()); |
||||
JksSslStoreDetails trustStoreDetails = new JksSslStoreDetails(ssl.getTrustStoreType(), |
||||
ssl.getTrustStoreProvider(), ssl.getTrustStore(), ssl.getTrustStorePassword()); |
||||
return new JksSslStoreBundle(keyStoreDetails, trustStoreDetails); |
||||
} |
||||
|
||||
@Override |
||||
public SslStoreBundle getStores() { |
||||
return this.stores; |
||||
} |
||||
|
||||
@Override |
||||
public SslBundleKey getKey() { |
||||
return this.key; |
||||
} |
||||
|
||||
@Override |
||||
public SslOptions getOptions() { |
||||
return this.options; |
||||
} |
||||
|
||||
@Override |
||||
public String getProtocol() { |
||||
return this.protocol; |
||||
} |
||||
|
||||
@Override |
||||
public SslManagerBundle getManagers() { |
||||
return this.managers; |
||||
} |
||||
|
||||
/** |
||||
* Get the {@link SslBundle} that should be used for the given {@link Ssl} instance. |
||||
* @param ssl the source ssl instance |
||||
* @return a {@link SslBundle} instance |
||||
* @throws NoSuchSslBundleException if a bundle lookup fails |
||||
*/ |
||||
public static SslBundle get(Ssl ssl) throws NoSuchSslBundleException { |
||||
return get(ssl, null, null); |
||||
} |
||||
|
||||
/** |
||||
* Get the {@link SslBundle} that should be used for the given {@link Ssl} instance. |
||||
* @param ssl the source ssl instance |
||||
* @param sslBundles the bundles that should be used when {@link Ssl#getBundle()} is |
||||
* set |
||||
* @return a {@link SslBundle} instance |
||||
* @throws NoSuchSslBundleException if a bundle lookup fails |
||||
*/ |
||||
public static SslBundle get(Ssl ssl, SslBundles sslBundles) throws NoSuchSslBundleException { |
||||
return get(ssl, sslBundles, null); |
||||
} |
||||
|
||||
/** |
||||
* Get the {@link SslBundle} that should be used for the given {@link Ssl} and |
||||
* {@link SslStoreProvider} instances. |
||||
* @param ssl the source {@link Ssl} instance |
||||
* @param sslBundles the bundles that should be used when {@link Ssl#getBundle()} is |
||||
* set |
||||
* @param sslStoreProvider the {@link SslStoreProvider} to use or {@code null} |
||||
* @return a {@link SslBundle} instance |
||||
* @throws NoSuchSslBundleException if a bundle lookup fails |
||||
* @deprecated since 3.1.0 for removal in 3.3.0 along with {@link SslStoreProvider} |
||||
*/ |
||||
@Deprecated(since = "3.1.0", forRemoval = true) |
||||
@SuppressWarnings("removal") |
||||
public static SslBundle get(Ssl ssl, SslBundles sslBundles, SslStoreProvider sslStoreProvider) { |
||||
Assert.state(Ssl.isEnabled(ssl), "SSL is not enabled"); |
||||
String keyPassword = (sslStoreProvider != null) ? sslStoreProvider.getKeyPassword() : null; |
||||
keyPassword = (keyPassword != null) ? keyPassword : ssl.getKeyPassword(); |
||||
if (sslStoreProvider != null) { |
||||
SslStoreBundle stores = new SslStoreProviderBundleAdapter(sslStoreProvider); |
||||
return new WebServerSslBundle(stores, keyPassword, ssl); |
||||
} |
||||
String bundleName = ssl.getBundle(); |
||||
if (StringUtils.hasText(bundleName)) { |
||||
Assert.state(sslBundles != null, |
||||
() -> "SSL bundle '%s' was requested but no SslBundles instance was provided" |
||||
.formatted(bundleName)); |
||||
return sslBundles.getBundle(bundleName); |
||||
} |
||||
SslStoreBundle stores = createStoreBundle(ssl); |
||||
return new WebServerSslBundle(stores, keyPassword, ssl); |
||||
} |
||||
|
||||
private static SslStoreBundle createStoreBundle(Ssl ssl) { |
||||
if (hasCertificateProperties(ssl)) { |
||||
return createPemStoreBundle(ssl); |
||||
} |
||||
if (hasJavaKeyStoreProperties(ssl)) { |
||||
return createJksStoreBundle(ssl); |
||||
} |
||||
throw new IllegalStateException("SSL is enabled but no trust material is configured"); |
||||
} |
||||
|
||||
static SslBundle createCertificateFileSslStoreProviderDelegate(Ssl ssl) { |
||||
if (!hasCertificateProperties(ssl)) { |
||||
return null; |
||||
} |
||||
SslStoreBundle stores = createPemStoreBundle(ssl); |
||||
return new WebServerSslBundle(stores, ssl.getKeyPassword(), ssl); |
||||
} |
||||
|
||||
private static boolean hasCertificateProperties(Ssl ssl) { |
||||
return Ssl.isEnabled(ssl) && ssl.getCertificate() != null && ssl.getCertificatePrivateKey() != null; |
||||
} |
||||
|
||||
private static boolean hasJavaKeyStoreProperties(Ssl ssl) { |
||||
return Ssl.isEnabled(ssl) && ssl.getKeyStore() != null |
||||
|| (ssl.getKeyStoreType() != null && ssl.getKeyStoreType().equals("PKCS11")); |
||||
} |
||||
|
||||
/** |
||||
* Class to adapt a {@link SslStoreProvider} into a {@link SslStoreBundle}. |
||||
*/ |
||||
@SuppressWarnings("removal") |
||||
private static class SslStoreProviderBundleAdapter implements SslStoreBundle { |
||||
|
||||
private final SslStoreProvider sslStoreProvider; |
||||
|
||||
SslStoreProviderBundleAdapter(SslStoreProvider sslStoreProvider) { |
||||
this.sslStoreProvider = sslStoreProvider; |
||||
} |
||||
|
||||
@Override |
||||
public KeyStore getKeyStore() { |
||||
return ThrowingSupplier.of(this.sslStoreProvider::getKeyStore).get(); |
||||
} |
||||
|
||||
@Override |
||||
public String getKeyStorePassword() { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public KeyStore getTrustStore() { |
||||
return ThrowingSupplier.of(this.sslStoreProvider::getTrustStore).get(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -1,104 +0,0 @@
@@ -1,104 +0,0 @@
|
||||
/* |
||||
* 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.web.embedded.netty; |
||||
|
||||
import java.security.NoSuchProviderException; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.boot.web.embedded.test.MockPkcs11Security; |
||||
import org.springframework.boot.web.embedded.test.MockPkcs11SecurityProvider; |
||||
import org.springframework.boot.web.server.Ssl; |
||||
import org.springframework.boot.web.server.SslStoreProviderFactory; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
import static org.assertj.core.api.Assertions.assertThatNoException; |
||||
|
||||
/** |
||||
* Tests for {@link SslServerCustomizer}. |
||||
* |
||||
* @author Andy Wilkinson |
||||
* @author Raheela Aslam |
||||
* @author Cyril Dangerville |
||||
* @author Scott Frederick |
||||
*/ |
||||
@SuppressWarnings("deprecation") |
||||
@MockPkcs11Security |
||||
class SslServerCustomizerTests { |
||||
|
||||
@Test |
||||
void keyStoreProviderIsUsedWhenCreatingKeyStore() { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyPassword("password"); |
||||
ssl.setKeyStore("src/test/resources/test.jks"); |
||||
ssl.setKeyStoreProvider("com.example.KeyStoreProvider"); |
||||
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null); |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> customizer.getKeyManagerFactory(ssl, SslStoreProviderFactory.from(ssl))) |
||||
.withCauseInstanceOf(NoSuchProviderException.class) |
||||
.withMessageContaining("com.example.KeyStoreProvider"); |
||||
} |
||||
|
||||
@Test |
||||
void trustStoreProviderIsUsedWhenCreatingTrustStore() { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setTrustStorePassword("password"); |
||||
ssl.setTrustStore("src/test/resources/test.jks"); |
||||
ssl.setTrustStoreProvider("com.example.TrustStoreProvider"); |
||||
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null); |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> customizer.getTrustManagerFactory(SslStoreProviderFactory.from(ssl))) |
||||
.withCauseInstanceOf(NoSuchProviderException.class) |
||||
.withMessageContaining("com.example.TrustStoreProvider"); |
||||
} |
||||
|
||||
@Test |
||||
void getKeyManagerFactoryWhenSslIsEnabledWithNoKeyStoreAndNotPkcs11ThrowsException() { |
||||
Ssl ssl = new Ssl(); |
||||
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null); |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> customizer.getKeyManagerFactory(ssl, SslStoreProviderFactory.from(ssl))) |
||||
.withCauseInstanceOf(IllegalStateException.class) |
||||
.withMessageContaining("KeyStore location must not be empty or null"); |
||||
} |
||||
|
||||
@Test |
||||
void getKeyManagerFactoryWhenSslIsEnabledWithPkcs11AndKeyStoreThrowsException() { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyStoreType("PKCS11"); |
||||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME); |
||||
ssl.setKeyStore("src/test/resources/test.jks"); |
||||
ssl.setKeyPassword("password"); |
||||
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null); |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> customizer.getKeyManagerFactory(ssl, SslStoreProviderFactory.from(ssl))) |
||||
.withCauseInstanceOf(IllegalStateException.class) |
||||
.withMessageContaining("must be empty or null for PKCS11 key stores"); |
||||
} |
||||
|
||||
@Test |
||||
void getKeyManagerFactoryWhenSslIsEnabledWithPkcs11AndKeyStoreProvider() { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyStoreType("PKCS11"); |
||||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME); |
||||
ssl.setKeyStorePassword("1234"); |
||||
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null); |
||||
assertThatNoException() |
||||
.isThrownBy(() -> customizer.getKeyManagerFactory(ssl, SslStoreProviderFactory.from(ssl))); |
||||
} |
||||
|
||||
} |
||||
@ -1,116 +0,0 @@
@@ -1,116 +0,0 @@
|
||||
/* |
||||
* 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.web.embedded.undertow; |
||||
|
||||
import java.net.InetAddress; |
||||
import java.security.NoSuchProviderException; |
||||
|
||||
import javax.net.ssl.KeyManager; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.boot.web.embedded.test.MockPkcs11Security; |
||||
import org.springframework.boot.web.embedded.test.MockPkcs11SecurityProvider; |
||||
import org.springframework.boot.web.server.Ssl; |
||||
import org.springframework.boot.web.server.SslStoreProviderFactory; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
import static org.assertj.core.api.Assertions.assertThatNoException; |
||||
|
||||
/** |
||||
* Tests for {@link SslBuilderCustomizer} |
||||
* |
||||
* @author Brian Clozel |
||||
* @author Raheela Aslam |
||||
* @author Cyril Dangerville |
||||
*/ |
||||
@MockPkcs11Security |
||||
class SslBuilderCustomizerTests { |
||||
|
||||
@Test |
||||
void getKeyManagersWhenAliasIsNullShouldNotDecorate() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyPassword("password"); |
||||
ssl.setKeyStore("src/test/resources/test.jks"); |
||||
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null); |
||||
KeyManager[] keyManagers = customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl)); |
||||
Class<?> name = Class |
||||
.forName("org.springframework.boot.web.embedded.undertow.SslBuilderCustomizer$ConfigurableAliasKeyManager"); |
||||
assertThat(keyManagers[0]).isNotInstanceOf(name); |
||||
} |
||||
|
||||
@Test |
||||
void keyStoreProviderIsUsedWhenCreatingKeyStore() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyPassword("password"); |
||||
ssl.setKeyStore("src/test/resources/test.jks"); |
||||
ssl.setKeyStoreProvider("com.example.KeyStoreProvider"); |
||||
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null); |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl))) |
||||
.withCauseInstanceOf(NoSuchProviderException.class) |
||||
.withMessageContaining("com.example.KeyStoreProvider"); |
||||
} |
||||
|
||||
@Test |
||||
void trustStoreProviderIsUsedWhenCreatingTrustStore() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setTrustStorePassword("password"); |
||||
ssl.setTrustStore("src/test/resources/test.jks"); |
||||
ssl.setTrustStoreProvider("com.example.TrustStoreProvider"); |
||||
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null); |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> customizer.getTrustManagers(SslStoreProviderFactory.from(ssl))) |
||||
.withMessageContaining("com.example.TrustStoreProvider"); |
||||
} |
||||
|
||||
@Test |
||||
void getKeyManagersWhenSslIsEnabledWithNoKeyStoreAndNotPkcs11ThrowsException() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null); |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl))) |
||||
.withCauseInstanceOf(IllegalStateException.class) |
||||
.withMessageContaining("KeyStore location must not be empty or null"); |
||||
} |
||||
|
||||
@Test |
||||
void configureSslWhenSslIsEnabledWithPkcs11AndKeyStoreThrowsException() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyStoreType("PKCS11"); |
||||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME); |
||||
ssl.setKeyStore("src/test/resources/test.jks"); |
||||
ssl.setKeyPassword("password"); |
||||
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null); |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl))) |
||||
.withCauseInstanceOf(IllegalStateException.class) |
||||
.withMessageContaining("must be empty or null for PKCS11 key stores"); |
||||
} |
||||
|
||||
@Test |
||||
void customizeWhenSslIsEnabledWithPkcs11AndKeyStoreProvider() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyStoreType("PKCS11"); |
||||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME); |
||||
ssl.setKeyStorePassword("1234"); |
||||
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null); |
||||
assertThatNoException().isThrownBy(() -> customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl))); |
||||
} |
||||
|
||||
} |
||||
@ -1,133 +0,0 @@
@@ -1,133 +0,0 @@
|
||||
/* |
||||
* Copyright 2012-2022 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.web.server; |
||||
|
||||
import java.security.KeyStore; |
||||
import java.security.KeyStoreException; |
||||
import java.security.NoSuchAlgorithmException; |
||||
import java.security.UnrecoverableKeyException; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Tests for {@link CertificateFileSslStoreProvider}. |
||||
* |
||||
* @author Scott Frederick |
||||
*/ |
||||
class CertificateFileSslStoreProviderTests { |
||||
|
||||
@Test |
||||
void fromSslWhenNullReturnsNull() { |
||||
assertThat(CertificateFileSslStoreProvider.from(null)).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void fromSslWhenDisabledReturnsNull() { |
||||
assertThat(CertificateFileSslStoreProvider.from(new Ssl())).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void fromSslWithCertAndKeyReturnsStoreProvider() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setEnabled(true); |
||||
ssl.setCertificate("classpath:test-cert.pem"); |
||||
ssl.setCertificatePrivateKey("classpath:test-key.pem"); |
||||
SslStoreProvider storeProvider = CertificateFileSslStoreProvider.from(ssl); |
||||
assertThat(storeProvider).isNotNull(); |
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), KeyStore.getDefaultType(), "spring-boot-web"); |
||||
assertThat(storeProvider.getTrustStore()).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void fromSslWithCertAndKeyAndTrustCertReturnsStoreProvider() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setEnabled(true); |
||||
ssl.setCertificate("classpath:test-cert.pem"); |
||||
ssl.setCertificatePrivateKey("classpath:test-key.pem"); |
||||
ssl.setTrustCertificate("classpath:test-cert.pem"); |
||||
SslStoreProvider storeProvider = CertificateFileSslStoreProvider.from(ssl); |
||||
assertThat(storeProvider).isNotNull(); |
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), KeyStore.getDefaultType(), "spring-boot-web"); |
||||
assertStoreContainsCert(storeProvider.getTrustStore(), KeyStore.getDefaultType(), "spring-boot-web-0"); |
||||
} |
||||
|
||||
@Test |
||||
void fromSslWithCertAndKeyAndTrustCertAndTrustKeyReturnsStoreProvider() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setEnabled(true); |
||||
ssl.setCertificate("classpath:test-cert.pem"); |
||||
ssl.setCertificatePrivateKey("classpath:test-key.pem"); |
||||
ssl.setTrustCertificate("classpath:test-cert.pem"); |
||||
ssl.setTrustCertificatePrivateKey("classpath:test-key.pem"); |
||||
SslStoreProvider storeProvider = CertificateFileSslStoreProvider.from(ssl); |
||||
assertThat(storeProvider).isNotNull(); |
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), KeyStore.getDefaultType(), "spring-boot-web"); |
||||
assertStoreContainsCertAndKey(storeProvider.getTrustStore(), KeyStore.getDefaultType(), "spring-boot-web"); |
||||
} |
||||
|
||||
@Test |
||||
void fromSslWithKeyAliasReturnsStoreProvider() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setEnabled(true); |
||||
ssl.setKeyAlias("test-alias"); |
||||
ssl.setCertificate("classpath:test-cert.pem"); |
||||
ssl.setCertificatePrivateKey("classpath:test-key.pem"); |
||||
ssl.setTrustCertificate("classpath:test-cert.pem"); |
||||
ssl.setTrustCertificatePrivateKey("classpath:test-key.pem"); |
||||
SslStoreProvider storeProvider = CertificateFileSslStoreProvider.from(ssl); |
||||
assertThat(storeProvider).isNotNull(); |
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), KeyStore.getDefaultType(), "test-alias"); |
||||
assertStoreContainsCertAndKey(storeProvider.getTrustStore(), KeyStore.getDefaultType(), "test-alias"); |
||||
} |
||||
|
||||
@Test |
||||
void fromSslWithStoreTypeReturnsStoreProvider() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setEnabled(true); |
||||
ssl.setKeyStoreType("PKCS12"); |
||||
ssl.setTrustStoreType("PKCS12"); |
||||
ssl.setCertificate("classpath:test-cert.pem"); |
||||
ssl.setCertificatePrivateKey("classpath:test-key.pem"); |
||||
ssl.setTrustCertificate("classpath:test-cert.pem"); |
||||
ssl.setTrustCertificatePrivateKey("classpath:test-key.pem"); |
||||
SslStoreProvider storeProvider = CertificateFileSslStoreProvider.from(ssl); |
||||
assertThat(storeProvider).isNotNull(); |
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), "PKCS12", "spring-boot-web"); |
||||
assertStoreContainsCertAndKey(storeProvider.getTrustStore(), "PKCS12", "spring-boot-web"); |
||||
} |
||||
|
||||
private void assertStoreContainsCertAndKey(KeyStore keyStore, String keyStoreType, String keyAlias) |
||||
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { |
||||
assertThat(keyStore).isNotNull(); |
||||
assertThat(keyStore.getType()).isEqualTo(keyStoreType); |
||||
assertThat(keyStore.containsAlias(keyAlias)).isTrue(); |
||||
assertThat(keyStore.getCertificate(keyAlias)).isNotNull(); |
||||
assertThat(keyStore.getKey(keyAlias, new char[] {})).isNotNull(); |
||||
} |
||||
|
||||
private void assertStoreContainsCert(KeyStore keyStore, String keyStoreType, String keyAlias) |
||||
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { |
||||
assertThat(keyStore).isNotNull(); |
||||
assertThat(keyStore.getType()).isEqualTo(keyStoreType); |
||||
assertThat(keyStore.containsAlias(keyAlias)).isTrue(); |
||||
assertThat(keyStore.getCertificate(keyAlias)).isNotNull(); |
||||
assertThat(keyStore.getKey(keyAlias, new char[] {})).isNull(); |
||||
} |
||||
|
||||
} |
||||
@ -1,57 +0,0 @@
@@ -1,57 +0,0 @@
|
||||
/* |
||||
* 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.web.server; |
||||
|
||||
import java.security.cert.X509Certificate; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
|
||||
/** |
||||
* Tests for {@link CertificateParser}. |
||||
* |
||||
* @author Scott Frederick |
||||
*/ |
||||
class CertificateParserTests { |
||||
|
||||
@Test |
||||
void parseCertificate() { |
||||
X509Certificate[] certificates = CertificateParser.parse("classpath:test-cert.pem"); |
||||
assertThat(certificates).isNotNull(); |
||||
assertThat(certificates).hasSize(1); |
||||
assertThat(certificates[0].getType()).isEqualTo("X.509"); |
||||
} |
||||
|
||||
@Test |
||||
void parseCertificateChain() { |
||||
X509Certificate[] certificates = CertificateParser.parse("classpath:test-cert-chain.pem"); |
||||
assertThat(certificates).isNotNull(); |
||||
assertThat(certificates).hasSize(2); |
||||
assertThat(certificates[0].getType()).isEqualTo("X.509"); |
||||
assertThat(certificates[1].getType()).isEqualTo("X.509"); |
||||
} |
||||
|
||||
@Test |
||||
void parseWithInvalidPathWillThrowException() { |
||||
String path = "file:///bad/path/cert.pem"; |
||||
assertThatIllegalStateException().isThrownBy(() -> CertificateParser.parse("file:///bad/path/cert.pem")) |
||||
.withMessageContaining(path); |
||||
} |
||||
|
||||
} |
||||
@ -1,142 +0,0 @@
@@ -1,142 +0,0 @@
|
||||
/* |
||||
* 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.web.server; |
||||
|
||||
import java.security.KeyStore; |
||||
import java.security.KeyStoreException; |
||||
import java.security.NoSuchAlgorithmException; |
||||
import java.security.NoSuchProviderException; |
||||
import java.security.UnrecoverableKeyException; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.boot.web.embedded.test.MockPkcs11Security; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
|
||||
/** |
||||
* Tests for {@link JavaKeyStoreSslStoreProvider}. |
||||
* |
||||
* @author Scott Frederick |
||||
*/ |
||||
@MockPkcs11Security |
||||
class JavaKeyStoreSslStoreProviderTests { |
||||
|
||||
@Test |
||||
void fromSslWhenNullReturnsNull() { |
||||
assertThat(JavaKeyStoreSslStoreProvider.from(null)).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void fromSslWhenDisabledReturnsNull() { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setEnabled(false); |
||||
assertThat(JavaKeyStoreSslStoreProvider.from(ssl)).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void getKeyStoreWithNoLocationThrowsException() { |
||||
Ssl ssl = new Ssl(); |
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl); |
||||
assertThatIllegalStateException().isThrownBy(storeProvider::getKeyStore) |
||||
.withMessageContaining("KeyStore location must not be empty or null"); |
||||
} |
||||
|
||||
@Test |
||||
void getKeyStoreWithTypePKCS11AndLocationThrowsException() { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyStore("test.jks"); |
||||
ssl.setKeyStoreType("PKCS11"); |
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl); |
||||
assertThatIllegalStateException().isThrownBy(storeProvider::getKeyStore) |
||||
.withMessageContaining("KeyStore location is 'test.jks', but must be empty or null for PKCS11 key stores"); |
||||
} |
||||
|
||||
@Test |
||||
void getKeyStoreWithLocationReturnsKeyStore() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyStore("classpath:test.jks"); |
||||
ssl.setKeyStorePassword("secret"); |
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl); |
||||
assertThat(storeProvider).isNotNull(); |
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), "JKS", "test-alias", "password"); |
||||
} |
||||
|
||||
@Test |
||||
void getTrustStoreWithLocationsReturnsTrustStore() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setTrustStore("classpath:test.jks"); |
||||
ssl.setKeyStorePassword("secret"); |
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl); |
||||
assertThat(storeProvider).isNotNull(); |
||||
assertStoreContainsCertAndKey(storeProvider.getTrustStore(), "JKS", "test-alias", "password"); |
||||
} |
||||
|
||||
@Test |
||||
void getKeyStoreWithTypeUsesType() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyStore("classpath:test.jks"); |
||||
ssl.setKeyStorePassword("secret"); |
||||
ssl.setKeyStoreType("PKCS12"); |
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl); |
||||
assertThat(storeProvider).isNotNull(); |
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), "PKCS12", "test-alias", "password"); |
||||
} |
||||
|
||||
@Test |
||||
void getTrustStoreWithTypeUsesType() throws Exception { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setTrustStore("classpath:test.jks"); |
||||
ssl.setKeyStorePassword("secret"); |
||||
ssl.setTrustStoreType("PKCS12"); |
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl); |
||||
assertThat(storeProvider).isNotNull(); |
||||
assertStoreContainsCertAndKey(storeProvider.getTrustStore(), "PKCS12", "test-alias", "password"); |
||||
} |
||||
|
||||
@Test |
||||
void getKeyStoreWithProviderUsesProvider() { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyStore("classpath:test.jks"); |
||||
ssl.setKeyStoreProvider("com.example.KeyStoreProvider"); |
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl); |
||||
assertThatExceptionOfType(NoSuchProviderException.class).isThrownBy(storeProvider::getKeyStore) |
||||
.withMessageContaining("com.example.KeyStoreProvider"); |
||||
} |
||||
|
||||
@Test |
||||
void getTrustStoreWithProviderUsesProvider() { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setTrustStore("classpath:test.jks"); |
||||
ssl.setTrustStoreProvider("com.example.TrustStoreProvider"); |
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl); |
||||
assertThatExceptionOfType(NoSuchProviderException.class).isThrownBy(storeProvider::getTrustStore) |
||||
.withMessageContaining("com.example.TrustStoreProvider"); |
||||
} |
||||
|
||||
private void assertStoreContainsCertAndKey(KeyStore keyStore, String keyStoreType, String keyAlias, |
||||
String keyPassword) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { |
||||
assertThat(keyStore).isNotNull(); |
||||
assertThat(keyStore.getType()).isEqualTo(keyStoreType); |
||||
assertThat(keyStore.containsAlias(keyAlias)).isTrue(); |
||||
assertThat(keyStore.getCertificate(keyAlias)).isNotNull(); |
||||
assertThat(keyStore.getKey(keyAlias, keyPassword.toCharArray())).isNotNull(); |
||||
} |
||||
|
||||
} |
||||
@ -1,62 +0,0 @@
@@ -1,62 +0,0 @@
|
||||
/* |
||||
* 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.web.server; |
||||
|
||||
import java.security.PrivateKey; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
|
||||
/** |
||||
* Tests for {@link PrivateKeyParser}. |
||||
* |
||||
* @author Scott Frederick |
||||
*/ |
||||
class PrivateKeyParserTests { |
||||
|
||||
@Test |
||||
void parsePkcs8KeyFile() { |
||||
PrivateKey privateKey = PrivateKeyParser.parse("classpath:test-key.pem"); |
||||
assertThat(privateKey).isNotNull(); |
||||
assertThat(privateKey.getFormat()).isEqualTo("PKCS#8"); |
||||
assertThat(privateKey.getAlgorithm()).isEqualTo("RSA"); |
||||
} |
||||
|
||||
@Test |
||||
void parsePkcs8KeyFileWithEcdsa() { |
||||
PrivateKey privateKey = PrivateKeyParser.parse("classpath:test-ec-key.pem"); |
||||
assertThat(privateKey).isNotNull(); |
||||
assertThat(privateKey.getFormat()).isEqualTo("PKCS#8"); |
||||
assertThat(privateKey.getAlgorithm()).isEqualTo("EC"); |
||||
} |
||||
|
||||
@Test |
||||
void parseWithNonKeyFileWillThrowException() { |
||||
String path = "classpath:test-banner.txt"; |
||||
assertThatIllegalStateException().isThrownBy(() -> PrivateKeyParser.parse("file://" + path)) |
||||
.withMessageContaining(path); |
||||
} |
||||
|
||||
@Test |
||||
void parseWithInvalidPathWillThrowException() { |
||||
String path = "file:///bad/path/key.pem"; |
||||
assertThatIllegalStateException().isThrownBy(() -> PrivateKeyParser.parse(path)).withMessageContaining(path); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,167 @@
@@ -0,0 +1,167 @@
|
||||
/* |
||||
* 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.web.server; |
||||
|
||||
import java.io.InputStream; |
||||
import java.security.KeyStore; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.boot.ssl.SslBundle; |
||||
import org.springframework.boot.ssl.SslBundleKey; |
||||
import org.springframework.boot.ssl.SslOptions; |
||||
import org.springframework.boot.ssl.SslStoreBundle; |
||||
import org.springframework.core.io.ClassPathResource; |
||||
import org.springframework.core.io.Resource; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link WebServerSslBundle}. |
||||
* |
||||
* @author Scott Frederick |
||||
* @author Phillip Webb |
||||
*/ |
||||
class WebServerSslBundleTests { |
||||
|
||||
@Test |
||||
void whenSslDisabledThrowsException() { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setEnabled(false); |
||||
assertThatIllegalStateException().isThrownBy(() -> WebServerSslBundle.get(ssl)) |
||||
.withMessage("SSL is not enabled"); |
||||
} |
||||
|
||||
@Test |
||||
void whenFromJksProperties() { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyStore("classpath:test.p12"); |
||||
ssl.setKeyStorePassword("secret"); |
||||
ssl.setKeyStoreType("PKCS12"); |
||||
ssl.setTrustStore("classpath:test.p12"); |
||||
ssl.setTrustStorePassword("secret"); |
||||
ssl.setTrustStoreType("PKCS12"); |
||||
ssl.setKeyPassword("password"); |
||||
ssl.setKeyAlias("alias"); |
||||
ssl.setClientAuth(Ssl.ClientAuth.NONE); |
||||
ssl.setCiphers(new String[] { "ONE", "TWO", "THREE" }); |
||||
ssl.setEnabledProtocols(new String[] { "TLSv1.1", "TLSv1.2" }); |
||||
ssl.setProtocol("TestProtocol"); |
||||
SslBundle bundle = WebServerSslBundle.get(ssl); |
||||
assertThat(bundle).isNotNull(); |
||||
assertThat(bundle.getProtocol()).isEqualTo("TestProtocol"); |
||||
SslBundleKey key = bundle.getKey(); |
||||
assertThat(key.getPassword()).isEqualTo("password"); |
||||
assertThat(key.getAlias()).isEqualTo("alias"); |
||||
SslStoreBundle stores = bundle.getStores(); |
||||
assertThat(stores.getKeyStorePassword()).isEqualTo("secret"); |
||||
assertThat(stores.getKeyStore()).isNotNull(); |
||||
assertThat(stores.getTrustStore()).isNotNull(); |
||||
SslOptions options = bundle.getOptions(); |
||||
assertThat(options.getCiphers()).containsExactly("ONE", "TWO", "THREE"); |
||||
assertThat(options.getEnabledProtocols()).containsExactly("TLSv1.1", "TLSv1.2"); |
||||
} |
||||
|
||||
@Test |
||||
void whenFromJksPropertiesWithPkcs11StoreType() { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyStorePassword("secret"); |
||||
ssl.setKeyStoreType("PKCS11"); |
||||
ssl.setKeyPassword("password"); |
||||
ssl.setClientAuth(Ssl.ClientAuth.NONE); |
||||
SslBundle bundle = WebServerSslBundle.get(ssl); |
||||
assertThat(bundle).isNotNull(); |
||||
assertThat(bundle.getStores().getKeyStorePassword()).isEqualTo("secret"); |
||||
assertThat(bundle.getKey().getPassword()).isEqualTo("password"); |
||||
} |
||||
|
||||
@Test |
||||
void whenFromPemProperties() { |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setCertificate("classpath:test-cert.pem"); |
||||
ssl.setCertificatePrivateKey("classpath:test-key.pem"); |
||||
ssl.setTrustCertificate("classpath:test-cert-chain.pem"); |
||||
ssl.setKeyStoreType("PKCS12"); |
||||
ssl.setTrustStoreType("PKCS12"); |
||||
ssl.setKeyPassword("password"); |
||||
ssl.setClientAuth(Ssl.ClientAuth.NONE); |
||||
ssl.setCiphers(new String[] { "ONE", "TWO", "THREE" }); |
||||
ssl.setEnabledProtocols(new String[] { "TLSv1.1", "TLSv1.2" }); |
||||
ssl.setProtocol("TLSv1.1"); |
||||
SslBundle bundle = WebServerSslBundle.get(ssl); |
||||
assertThat(bundle).isNotNull(); |
||||
SslBundleKey key = bundle.getKey(); |
||||
assertThat(key.getAlias()).isNull(); |
||||
assertThat(key.getPassword()).isEqualTo("password"); |
||||
SslStoreBundle stores = bundle.getStores(); |
||||
assertThat(stores.getKeyStorePassword()).isNull(); |
||||
assertThat(stores.getKeyStore()).isNotNull(); |
||||
assertThat(stores.getTrustStore()).isNotNull(); |
||||
SslOptions options = bundle.getOptions(); |
||||
assertThat(options.getCiphers()).containsExactly("ONE", "TWO", "THREE"); |
||||
assertThat(options.getEnabledProtocols()).containsExactly("TLSv1.1", "TLSv1.2"); |
||||
} |
||||
|
||||
@Test |
||||
@Deprecated(since = "3.1.0", forRemoval = true) |
||||
@SuppressWarnings("removal") |
||||
void whenFromCustomSslStoreProvider() throws Exception { |
||||
SslStoreProvider sslStoreProvider = mock(SslStoreProvider.class); |
||||
KeyStore keyStore = loadStore(); |
||||
given(sslStoreProvider.getKeyStore()).willReturn(keyStore); |
||||
given(sslStoreProvider.getTrustStore()).willReturn(keyStore); |
||||
Ssl ssl = new Ssl(); |
||||
ssl.setKeyStoreType("PKCS12"); |
||||
ssl.setTrustStoreType("PKCS12"); |
||||
ssl.setKeyPassword("password"); |
||||
ssl.setClientAuth(Ssl.ClientAuth.NONE); |
||||
ssl.setCiphers(new String[] { "ONE", "TWO", "THREE" }); |
||||
ssl.setEnabledProtocols(new String[] { "TLSv1.1", "TLSv1.2" }); |
||||
ssl.setProtocol("TLSv1.1"); |
||||
SslBundle bundle = WebServerSslBundle.get(ssl, null, sslStoreProvider); |
||||
assertThat(bundle).isNotNull(); |
||||
SslBundleKey key = bundle.getKey(); |
||||
assertThat(key.getPassword()).isEqualTo("password"); |
||||
assertThat(key.getAlias()).isNull(); |
||||
SslStoreBundle stores = bundle.getStores(); |
||||
assertThat(stores.getKeyStore()).isNotNull(); |
||||
assertThat(stores.getTrustStore()).isNotNull(); |
||||
SslOptions options = bundle.getOptions(); |
||||
assertThat(options.getCiphers()).containsExactly("ONE", "TWO", "THREE"); |
||||
assertThat(options.getEnabledProtocols()).containsExactly("TLSv1.1", "TLSv1.2"); |
||||
} |
||||
|
||||
@Test |
||||
void whenMissingPropertiesThrowsException() { |
||||
Ssl ssl = new Ssl(); |
||||
assertThatIllegalStateException().isThrownBy(() -> WebServerSslBundle.get(ssl)) |
||||
.withMessageContaining("SSL is enabled but no trust material is configured"); |
||||
} |
||||
|
||||
private KeyStore loadStore() throws Exception { |
||||
Resource resource = new ClassPathResource("test.p12"); |
||||
try (InputStream stream = resource.getInputStream()) { |
||||
KeyStore keyStore = KeyStore.getInstance("PKCS12"); |
||||
keyStore.load(stream, "secret".toCharArray()); |
||||
return keyStore; |
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue