Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web server fails to start due to "Resource location must not be null" when attempting to use a PKCS 11 KeyStore #32179

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -19,6 +19,7 @@
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.Objects;

import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
import org.eclipse.jetty.http.HttpVersion;
Expand Down Expand Up @@ -51,6 +52,7 @@
* @author Brian Clozel
* @author Olivier Lamy
* @author Chris Bono
* @author Cyril Dangerville
*/
class SslServerCustomizer implements JettyServerCustomizer {

Expand Down Expand Up @@ -220,16 +222,25 @@ private void configureSslPasswords(SslContextFactory.Server factory, Ssl ssl) {
}

private void configureSslKeyStore(SslContextFactory.Server factory, Ssl ssl) {
try {
URL url = ResourceUtils.getURL(ssl.getKeyStore());
factory.setKeyStoreResource(Resource.newResource(url));
}
catch (Exception ex) {
throw new WebServerException("Could not load key store '" + ssl.getKeyStore() + "'", ex);
final String keystoreType = Objects.requireNonNullElse(ssl.getKeyStoreType(), "JKS");
final String keystoreLocation = ssl.getKeyStore();
if (keystoreType.equalsIgnoreCase("PKCS11")) {
if (keystoreLocation != null && !keystoreLocation.isBlank()) {
throw new IllegalArgumentException("Input keystore location is not valid for keystore type 'PKCS11': '"
+ keystoreLocation + "'. Must be undefined / null.");
}
}
if (ssl.getKeyStoreType() != null) {
factory.setKeyStoreType(ssl.getKeyStoreType());
else {
try {
URL url = ResourceUtils.getURL(keystoreLocation);
factory.setKeyStoreResource(Resource.newResource(url));
}
catch (Exception ex) {
throw new WebServerException("Could not load key store '" + keystoreLocation + "'", ex);
}
}

factory.setKeyStoreType(keystoreType);
if (ssl.getKeyStoreProvider() != null) {
factory.setKeyStoreProvider(ssl.getKeyStoreProvider());
}
Expand Down
Expand Up @@ -57,6 +57,7 @@
* @author Brian Clozel
* @author Raheela Aslam
* @author Chris Bono
* @author Cyril Dangerville
* @since 2.0.0
* @deprecated this class is meant for Spring Boot internal use only.
*/
Expand Down Expand Up @@ -171,17 +172,27 @@ private KeyStore loadTrustStore(String type, String provider, String resource, S
private KeyStore loadStore(String type, String provider, String resource, String password) throws Exception {
type = (type != null) ? type : "JKS";
KeyStore store = (provider != null) ? KeyStore.getInstance(type, provider) : KeyStore.getInstance(type);
try {
URL url = ResourceUtils.getURL(resource);
try (InputStream stream = url.openStream()) {
store.load(stream, (password != null) ? password.toCharArray() : null);
if (type.equalsIgnoreCase("PKCS11")) {
if (resource != null && !resource.isBlank()) {
throw new IllegalArgumentException("Input keystore location is not valid for keystore type 'PKCS11': '"
+ resource + "'. Must be undefined / null.");
}
return store;

store.load(null, (password != null) ? password.toCharArray() : null);
}
catch (Exception ex) {
throw new WebServerException("Could not load key store '" + resource + "'", ex);
else {
try {
URL url = ResourceUtils.getURL(resource);
try (InputStream stream = url.openStream()) {
store.load(stream, (password != null) ? password.toCharArray() : null);
}
}
catch (Exception ex) {
throw new WebServerException("Could not load key store '" + resource + "'", ex);
}
}

return store;
}

/**
Expand Down
Expand Up @@ -17,6 +17,7 @@
package org.springframework.boot.web.embedded.tomcat;

import java.io.FileNotFoundException;
import java.util.Objects;

import org.apache.catalina.connector.Connector;
import org.apache.coyote.ProtocolHandler;
Expand All @@ -39,6 +40,7 @@
* @author Brian Clozel
* @author Andy Wilkinson
* @author Scott Frederick
* @author Cyril Dangerville
*/
class SslConnectorCustomizer implements TomcatConnectorCustomizer {

Expand Down Expand Up @@ -139,15 +141,24 @@ protected void configureSslStoreProvider(AbstractHttp11JsseProtocol<?> protocol,
}

private void configureSslKeyStore(SSLHostConfigCertificate certificate, Ssl ssl) {
try {
certificate.setCertificateKeystoreFile(ResourceUtils.getURL(ssl.getKeyStore()).toString());
}
catch (Exception ex) {
throw new WebServerException("Could not load key store '" + ssl.getKeyStore() + "'", ex);
final String keystoreType = Objects.requireNonNullElse(ssl.getKeyStoreType(), "JKS");
final String keystoreLocation = ssl.getKeyStore();
if (keystoreType.equalsIgnoreCase("PKCS11")) {
if (keystoreLocation != null && !keystoreLocation.isBlank()) {
throw new IllegalArgumentException("Input keystore location is not valid for keystore type 'PKCS11': '"
+ keystoreLocation + "'. Must be undefined / null.");
}
}
if (ssl.getKeyStoreType() != null) {
certificate.setCertificateKeystoreType(ssl.getKeyStoreType());
else {
try {
certificate.setCertificateKeystoreFile(ResourceUtils.getURL(keystoreLocation).toString());
}
catch (Exception ex) {
throw new WebServerException("Could not load key store '" + keystoreLocation + "'", ex);
}
}

certificate.setCertificateKeystoreType(keystoreType);
if (ssl.getKeyStoreProvider() != null) {
certificate.setCertificateKeystoreProvider(ssl.getKeyStoreProvider());
}
Expand Down
Expand Up @@ -51,6 +51,7 @@
*
* @author Brian Clozel
* @author Raheela Aslam
* @author Cyril Dangerville
*/
class SslBuilderCustomizer implements UndertowBuilderCustomizer {

Expand Down Expand Up @@ -180,16 +181,27 @@ private KeyStore loadTrustStore(String type, String provider, String resource, S
private KeyStore loadStore(String type, String provider, String resource, String password) throws Exception {
type = (type != null) ? type : "JKS";
KeyStore store = (provider != null) ? KeyStore.getInstance(type, provider) : KeyStore.getInstance(type);
try {
URL url = ResourceUtils.getURL(resource);
try (InputStream stream = url.openStream()) {
store.load(stream, (password != null) ? password.toCharArray() : null);
if (type.equalsIgnoreCase("PKCS11")) {
if (resource != null && !resource.isBlank()) {
throw new IllegalArgumentException("Input keystore location is not valid for keystore type 'PKCS11': '"
+ resource + "'. Must be undefined / null.");
}
return store;

store.load(null, (password != null) ? password.toCharArray() : null);
}
catch (Exception ex) {
throw new WebServerException("Could not load key store '" + resource + "'", ex);
else {
try {
URL url = ResourceUtils.getURL(resource);
try (InputStream stream = url.openStream()) {
store.load(stream, (password != null) ? password.toCharArray() : null);
}
}
catch (Exception ex) {
throw new WebServerException("Could not load key store '" + resource + "'", ex);
}
}

return store;
}

/**
Expand Down
Expand Up @@ -17,6 +17,8 @@
package org.springframework.boot.web.embedded.jetty;

import java.net.InetSocketAddress;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -27,24 +29,47 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.OS;

import org.springframework.boot.testsupport.junit.DisabledOnOs;
import org.springframework.boot.web.embedded.netty.MockPkcs11SecurityProvider;
import org.springframework.boot.web.server.Http2;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.WebServerException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNoException;

/**
* Tests for {@link SslServerCustomizer}.
*
* @author Andy Wilkinson
* @author Cyril Dangerville
*/
class SslServerCustomizerTests {

private static final Provider PKCS11_PROVIDER = new MockPkcs11SecurityProvider();

@BeforeAll
static void beforeAllTests() {
/*
* Add the mock Java security provider for PKCS#11-related unit tests.
*
*/
Security.addProvider(PKCS11_PROVIDER);
}

@AfterAll
static void afterAllTests() {
// Remove the provider previously added in setup()
Security.removeProvider(PKCS11_PROVIDER.getName());
}

@Test
@SuppressWarnings("rawtypes")
void whenHttp2IsNotEnabledServerConnectorHasSslAndHttpConnectionFactories() {
Expand Down Expand Up @@ -82,8 +107,11 @@ void alpnConnectionFactoryHasNullDefaultProtocolToAllowNegotiationToHttp11() {
assertThat(((ALPNServerConnectionFactory) factories.get(1)).getDefaultProtocol()).isNull();
}

/**
* Null/undefined keystore is invalid unless keystore type is PKCS11.
*/
@Test
void configureSslWhenSslIsEnabledWithNoKeyStoreThrowsWebServerException() {
void configureSslWhenSslIsEnabledWithNoKeyStoreAndNotPkcs11ThrowsWebServerException() {
Ssl ssl = new Ssl();
SslServerCustomizer customizer = new SslServerCustomizer(null, ssl, null, null);
assertThatExceptionOfType(Exception.class)
Expand All @@ -94,6 +122,33 @@ void configureSslWhenSslIsEnabledWithNoKeyStoreThrowsWebServerException() {
});
}

/**
* No keystore path should be defined if keystore type is PKCS#11.
*/
@Test
void configureSslWhenSslIsEnabledWithPkcs11AndKeyStoreThrowsIllegalArgumentException() {
Ssl ssl = new Ssl();
ssl.setKeyStoreType("PKCS11");
ssl.setKeyStoreProvider(PKCS11_PROVIDER.getName());
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setKeyPassword("password");
SslServerCustomizer customizer = new SslServerCustomizer(null, ssl, null, null);
assertThatIllegalArgumentException()
.isThrownBy(() -> customizer.configureSsl(new SslContextFactory.Server(), ssl, null))
.withMessageContaining("Input keystore location is not valid for keystore type 'PKCS11'");
}

@Test
void customizeWhenSslIsEnabledWithPkcs11AndKeyStoreProvider() {
Ssl ssl = new Ssl();
ssl.setKeyStoreType("PKCS11");
ssl.setKeyStoreProvider(PKCS11_PROVIDER.getName());
ssl.setKeyStorePassword("1234");
SslServerCustomizer customizer = new SslServerCustomizer(null, ssl, null, null);
// Loading the KeyManagerFactory should be successful
assertThatNoException().isThrownBy(() -> customizer.configureSsl(new SslContextFactory.Server(), ssl, null));
}

private Server createCustomizedServer() {
return createCustomizedServer(new Http2());
}
Expand Down