diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/WebServerSslBundle.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/WebServerSslBundle.java index e722830aa4..0e10ace7fd 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/WebServerSslBundle.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/WebServerSslBundle.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -68,12 +68,28 @@ public final class WebServerSslBundle implements SslBundle { return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, ssl.getKeyAlias()); } - private static SslStoreBundle createJksStoreBundle(Ssl ssl) { + private static SslStoreBundle createPemKeyStoreBundle(Ssl ssl) { + PemSslStoreDetails keyStoreDetails = new PemSslStoreDetails(ssl.getKeyStoreType(), ssl.getCertificate(), + ssl.getCertificatePrivateKey()); + return new PemSslStoreBundle(keyStoreDetails, null, ssl.getKeyAlias()); + } + + private static SslStoreBundle createPemTrustStoreBundle(Ssl ssl) { + PemSslStoreDetails trustStoreDetails = new PemSslStoreDetails(ssl.getTrustStoreType(), + ssl.getTrustCertificate(), ssl.getTrustCertificatePrivateKey()); + return new PemSslStoreBundle(null, trustStoreDetails, ssl.getKeyAlias()); + } + + private static SslStoreBundle createJksKeyStoreBundle(Ssl ssl) { JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails(ssl.getKeyStoreType(), ssl.getKeyStoreProvider(), ssl.getKeyStore(), ssl.getKeyStorePassword()); + return new JksSslStoreBundle(keyStoreDetails, null); + } + + private static SslStoreBundle createJksTrustStoreBundle(Ssl ssl) { JksSslStoreDetails trustStoreDetails = new JksSslStoreDetails(ssl.getTrustStoreType(), ssl.getTrustStoreProvider(), ssl.getTrustStore(), ssl.getTrustStorePassword()); - return new JksSslStoreBundle(keyStoreDetails, trustStoreDetails); + return new JksSslStoreBundle(null, trustStoreDetails); } @Override @@ -156,30 +172,55 @@ public final class WebServerSslBundle implements SslBundle { } private static SslStoreBundle createStoreBundle(Ssl ssl) { - if (hasCertificateProperties(ssl)) { - return createPemStoreBundle(ssl); + KeyStore keyStore = createKeyStore(ssl); + KeyStore trustStore = createTrustStore(ssl); + return new WebServerSslStoreBundle(keyStore, trustStore, ssl.getKeyStorePassword()); + } + + private static KeyStore createKeyStore(Ssl ssl) { + if (hasPemKeyStoreProperties(ssl)) { + return createPemKeyStoreBundle(ssl).getKeyStore(); } - if (hasJavaKeyStoreProperties(ssl)) { - return createJksStoreBundle(ssl); + else if (hasJksKeyStoreProperties(ssl)) { + return createJksKeyStoreBundle(ssl).getKeyStore(); } - throw new IllegalStateException("SSL is enabled but no trust material is configured"); + return null; + } + + private static KeyStore createTrustStore(Ssl ssl) { + if (hasPemTrustStoreProperties(ssl)) { + return createPemTrustStoreBundle(ssl).getTrustStore(); + } + else if (hasJksTrustStoreProperties(ssl)) { + return createJksTrustStoreBundle(ssl).getTrustStore(); + } + return null; } static SslBundle createCertificateFileSslStoreProviderDelegate(Ssl ssl) { - if (!hasCertificateProperties(ssl)) { + if (!hasPemKeyStoreProperties(ssl)) { return null; } SslStoreBundle stores = createPemStoreBundle(ssl); return new WebServerSslBundle(stores, ssl.getKeyPassword(), ssl); } - private static boolean hasCertificateProperties(Ssl ssl) { + private static boolean hasPemKeyStoreProperties(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")); + private static boolean hasPemTrustStoreProperties(Ssl ssl) { + return Ssl.isEnabled(ssl) && ssl.getTrustCertificate() != null; + } + + private static boolean hasJksKeyStoreProperties(Ssl ssl) { + return Ssl.isEnabled(ssl) && (ssl.getKeyStore() != null + || (ssl.getKeyStoreType() != null && ssl.getKeyStoreType().equals("PKCS11"))); + } + + private static boolean hasJksTrustStoreProperties(Ssl ssl) { + return Ssl.isEnabled(ssl) && (ssl.getTrustStore() != null + || (ssl.getTrustStoreType() != null && ssl.getTrustStoreType().equals("PKCS11"))); } /** @@ -211,4 +252,36 @@ public final class WebServerSslBundle implements SslBundle { } + private static final class WebServerSslStoreBundle implements SslStoreBundle { + + private final KeyStore keyStore; + + private final KeyStore trustStore; + + private final String keyStorePassword; + + private WebServerSslStoreBundle(KeyStore keyStore, KeyStore trustStore, String keyStorePassword) { + Assert.state(keyStore != null || trustStore != null, "SSL is enabled but no trust material is configured"); + this.keyStore = keyStore; + this.trustStore = trustStore; + this.keyStorePassword = keyStorePassword; + } + + @Override + public KeyStore getKeyStore() { + return this.keyStore; + } + + @Override + public KeyStore getTrustStore() { + return this.trustStore; + } + + @Override + public String getKeyStorePassword() { + return this.keyStorePassword; + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/WebServerSslBundleTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/WebServerSslBundleTests.java index c6a22a22fa..5a8a18c622 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/WebServerSslBundleTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/WebServerSslBundleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -95,6 +95,25 @@ class WebServerSslBundleTests { .withMessageContaining("must be empty or null for PKCS11 hardware key stores"); } + @Test + void whenFromPkcs11Properties() { + Ssl ssl = new Ssl(); + ssl.setKeyStoreType("PKCS11"); + ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME); + ssl.setTrustStoreType("PKCS11"); + ssl.setTrustStoreProvider(MockPkcs11SecurityProvider.NAME); + ssl.setKeyPassword("password"); + ssl.setClientAuth(Ssl.ClientAuth.NONE); + SslBundle bundle = WebServerSslBundle.get(ssl); + assertThat(bundle).isNotNull(); + assertThat(bundle.getProtocol()).isEqualTo("TLS"); + SslBundleKey key = bundle.getKey(); + assertThat(key.getPassword()).isEqualTo("password"); + SslStoreBundle stores = bundle.getStores(); + assertThat(stores.getKeyStore()).isNotNull(); + assertThat(stores.getTrustStore()).isNotNull(); + } + @Test void whenFromPemProperties() { Ssl ssl = new Ssl(); @@ -122,6 +141,61 @@ class WebServerSslBundleTests { assertThat(options.getEnabledProtocols()).containsExactly("TLSv1.1", "TLSv1.2"); } + @Test + void whenPemKeyStoreAndJksTrustStoreProperties() { + Ssl ssl = new Ssl(); + ssl.setCertificate("classpath:test-cert.pem"); + ssl.setCertificatePrivateKey("classpath:test-key.pem"); + ssl.setKeyStoreType("PKCS12"); + ssl.setKeyPassword("password"); + ssl.setTrustStore("classpath:test.p12"); + ssl.setTrustStorePassword("secret"); + ssl.setTrustStoreType("PKCS12"); + 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 + void whenJksKeyStoreAndPemTrustStoreProperties() { + Ssl ssl = new Ssl(); + ssl.setKeyStore("classpath:test.p12"); + ssl.setKeyStoreType("PKCS12"); + ssl.setKeyPassword("password"); + ssl.setTrustCertificate("classpath:test-cert-chain.pem"); + ssl.setTrustStorePassword("secret"); + ssl.setTrustStoreType("PKCS12"); + 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")