Consolidate web server SSL configuration
parent
0b23ffd73a
commit
9f0108496c
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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,142 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue