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