From 9b71ef411441d7cb34d02fce61842b62870bae79 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 1 Nov 2023 12:49:44 -0700 Subject: [PATCH] Polish and refactor some SSL internals Polish and refactor some of the internal SSL code to make it easier to add additional functionality in the future. --- .../ssl/BundleContentProperty.java | 77 +++++++++++++++ .../ssl/PemSslBundleProperties.java | 2 +- .../ssl/PropertiesSslBundle.java | 64 ++++++------ .../ssl/SslPropertiesBundleRegistrar.java | 97 ++++++++----------- .../ssl/BundleContentPropertyTests.java | 87 +++++++++++++++++ .../SslPropertiesBundleRegistrarTests.java | 16 ++- .../boot/ssl/pem/PemContent.java | 4 +- .../boot/ssl/pem/PemSslStoreDetails.java | 45 +++++---- 8 files changed, 272 insertions(+), 120 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentProperty.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/BundleContentPropertyTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentProperty.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentProperty.java new file mode 100644 index 0000000000..abaace466f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentProperty.java @@ -0,0 +1,77 @@ +/* + * 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.autoconfigure.ssl; + +import java.io.FileNotFoundException; +import java.net.URL; +import java.nio.file.Path; + +import org.springframework.boot.ssl.pem.PemContent; +import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; +import org.springframework.util.StringUtils; + +/** + * Helper utility to manage a single bundle content configuration property. May possibly + * contain PEM content, a location or a directory search pattern. + * + * @param name the configuration property name (excluding any prefix) + * @param value the configuration property value + * @author Phillip Webb + */ +record BundleContentProperty(String name, String value) { + + /** + * Return if the property value is PEM content. + * @return if the value is PEM content + */ + boolean isPemContent() { + return PemContent.isPresentInText(this.value); + } + + /** + * Return if there is any property value present. + * @return if the value is present + */ + boolean hasValue() { + return StringUtils.hasText(this.value); + } + + private URL toUrl() throws FileNotFoundException { + Assert.state(!isPemContent(), "Value contains PEM content"); + return ResourceUtils.getURL(this.value); + } + + Path toWatchPath() { + return toPath(); + } + + private Path toPath() { + try { + URL url = toUrl(); + Assert.state(isFileUrl(url), () -> "Vaule '%s' is not a file URL".formatted(url)); + return Path.of(url.toURI()).toAbsolutePath(); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to convert '%s' property to a path".formatted(this.name), ex); + } + } + + private boolean isFileUrl(URL url) { + return "file".equalsIgnoreCase(url.getProtocol()); + } +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java index 7870c6b059..beb58d87dc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java @@ -58,7 +58,7 @@ public class PemSslBundleProperties extends SslBundleProperties { private String type; /** - * Location or content of the certificate in PEM format. + * Location or content of the certificate or certificate chain in PEM format. */ private String certificate; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java index 39512f2b3e..d8cfd084ab 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java @@ -99,7 +99,35 @@ public final class PropertiesSslBundle implements SslBundle { * @return an {@link SslBundle} instance */ public static SslBundle get(PemSslBundleProperties properties) { - return new PropertiesSslBundle(asSslStoreBundle(properties), properties); + try { + PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore()); + if (keyStore != null) { + keyStore = keyStore.withAlias(properties.getKey().getAlias()) + .withPassword(properties.getKey().getPassword()); + } + PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore()); + SslStoreBundle storeBundle = new PemSslStoreBundle(keyStore, trustStore); + return new PropertiesSslBundle(storeBundle, properties); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties) + throws IOException { + PemSslStore pemSslStore = PemSslStore.load(asPemSslStoreDetails(properties)); + if (properties.isVerifyKeys()) { + CertificateMatcher certificateMatcher = new CertificateMatcher(pemSslStore.privateKey()); + Assert.state(certificateMatcher.matchesAny(pemSslStore.certificates()), + "Private key matches none of the certificates in the chain"); + } + return pemSslStore; + } + + private static PemSslStoreDetails asPemSslStoreDetails(PemSslBundleProperties.Store properties) { + return new PemSslStoreDetails(properties.getType(), properties.getCertificate(), properties.getPrivateKey(), + properties.getPrivateKeyPassword()); } /** @@ -108,38 +136,8 @@ public final class PropertiesSslBundle implements SslBundle { * @return an {@link SslBundle} instance */ public static SslBundle get(JksSslBundleProperties properties) { - return new PropertiesSslBundle(asSslStoreBundle(properties), properties); - } - - private static SslStoreBundle asSslStoreBundle(PemSslBundleProperties properties) { - PemSslStore keyStore = asPemSslStore(properties.getKeystore()); - if (keyStore != null) { - keyStore = keyStore.withAlias(properties.getKey().getAlias()) - .withPassword(properties.getKey().getPassword()); - } - PemSslStore trustStore = asPemSslStore(properties.getTruststore()); - return new PemSslStoreBundle(keyStore, trustStore); - } - - private static PemSslStore asPemSslStore(PemSslBundleProperties.Store properties) { - try { - PemSslStoreDetails details = asStoreDetails(properties); - PemSslStore pemSslStore = PemSslStore.load(details); - if (properties.isVerifyKeys()) { - CertificateMatcher certificateMatcher = new CertificateMatcher(pemSslStore.privateKey()); - Assert.state(certificateMatcher.matchesAny(pemSslStore.certificates()), - "Private key matches none of the certificates in the chain"); - } - return pemSslStore; - } - catch (IOException ex) { - throw new UncheckedIOException(ex); - } - } - - private static PemSslStoreDetails asStoreDetails(PemSslBundleProperties.Store properties) { - return new PemSslStoreDetails(properties.getType(), properties.getCertificate(), properties.getPrivateKey(), - properties.getPrivateKeyPassword()); + SslStoreBundle storeBundle = asSslStoreBundle(properties); + return new PropertiesSslBundle(storeBundle, properties); } private static SslStoreBundle asSslStoreBundle(JksSslBundleProperties properties) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java index 810ff772a3..583702c82c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java @@ -16,20 +16,17 @@ package org.springframework.boot.autoconfigure.ssl; -import java.net.URL; import java.nio.file.Path; -import java.util.LinkedHashSet; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; -import java.util.regex.Pattern; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundleRegistry; -import org.springframework.util.Assert; -import org.springframework.util.ResourceUtils; -import org.springframework.util.StringUtils; /** * A {@link SslBundleRegistrar} that registers SSL bundles based @@ -41,8 +38,6 @@ import org.springframework.util.StringUtils; */ class SslPropertiesBundleRegistrar implements SslBundleRegistrar { - private static final Pattern PEM_CONTENT = Pattern.compile("-+BEGIN\\s+[^-]*-+", Pattern.CASE_INSENSITIVE); - private final SslProperties.Bundles properties; private final FileWatcher fileWatcher; @@ -54,70 +49,58 @@ class SslPropertiesBundleRegistrar implements SslBundleRegistrar { @Override public void registerBundles(SslBundleRegistry registry) { - registerBundles(registry, this.properties.getPem(), PropertiesSslBundle::get, this::getLocations); - registerBundles(registry, this.properties.getJks(), PropertiesSslBundle::get, this::getLocations); + registerBundles(registry, this.properties.getPem(), PropertiesSslBundle::get, this::watchedPemPaths); + registerBundles(registry, this.properties.getJks(), PropertiesSslBundle::get, this::watchedJksPaths); } private

void registerBundles(SslBundleRegistry registry, Map properties, - Function bundleFactory, Function> locationsSupplier) { + Function bundleFactory, Function> watchedPaths) { properties.forEach((bundleName, bundleProperties) -> { - SslBundle bundle = bundleFactory.apply(bundleProperties); - registry.registerBundle(bundleName, bundle); - if (bundleProperties.isReloadOnUpdate()) { - Set paths = locationsSupplier.apply(bundleProperties) - .stream() - .filter(Location::hasValue) - .map((location) -> toPath(bundleName, location)) - .collect(Collectors.toSet()); - this.fileWatcher.watch(paths, - () -> registry.updateBundle(bundleName, bundleFactory.apply(bundleProperties))); + Supplier bundleSupplier = () -> bundleFactory.apply(bundleProperties); + try { + registry.registerBundle(bundleName, bundleSupplier.get()); + if (bundleProperties.isReloadOnUpdate()) { + Supplier> pathsSupplier = () -> watchedPaths.apply(bundleProperties); + watchForUpdates(registry, bundleName, pathsSupplier, bundleSupplier); + } + } + catch (IllegalStateException ex) { + throw new IllegalStateException("Unable to register SSL bundle '%s'".formatted(bundleName), ex); } }); } - private Set getLocations(JksSslBundleProperties properties) { - JksSslBundleProperties.Store keystore = properties.getKeystore(); - JksSslBundleProperties.Store truststore = properties.getTruststore(); - Set locations = new LinkedHashSet<>(); - locations.add(new Location("keystore.location", keystore.getLocation())); - locations.add(new Location("truststore.location", truststore.getLocation())); - return locations; - } - - private Set getLocations(PemSslBundleProperties properties) { - PemSslBundleProperties.Store keystore = properties.getKeystore(); - PemSslBundleProperties.Store truststore = properties.getTruststore(); - Set locations = new LinkedHashSet<>(); - locations.add(new Location("keystore.private-key", keystore.getPrivateKey())); - locations.add(new Location("keystore.certificate", keystore.getCertificate())); - locations.add(new Location("truststore.private-key", truststore.getPrivateKey())); - locations.add(new Location("truststore.certificate", truststore.getCertificate())); - return locations; - } - - private Path toPath(String bundleName, Location watchableLocation) { - String value = watchableLocation.value(); - String field = watchableLocation.field(); - Assert.state(!PEM_CONTENT.matcher(value).find(), - () -> "SSL bundle '%s' '%s' is not a URL and can't be watched".formatted(bundleName, field)); + private void watchForUpdates(SslBundleRegistry registry, String bundleName, Supplier> pathsSupplier, + Supplier bundleSupplier) { try { - URL url = ResourceUtils.getURL(value); - Assert.state("file".equalsIgnoreCase(url.getProtocol()), - () -> "SSL bundle '%s' '%s' URL '%s' doesn't point to a file".formatted(bundleName, field, url)); - return Path.of(url.toURI()).toAbsolutePath(); + this.fileWatcher.watch(pathsSupplier.get(), () -> registry.updateBundle(bundleName, bundleSupplier.get())); } - catch (Exception ex) { - throw new RuntimeException( - "SSL bundle '%s' '%s' location '%s' cannot be watched".formatted(bundleName, field, value), ex); + catch (RuntimeException ex) { + throw new IllegalStateException("Unable to watch for reload on update", ex); } } - private record Location(String field, String value) { + private Set watchedJksPaths(JksSslBundleProperties properties) { + List watched = new ArrayList<>(); + watched.add(new BundleContentProperty("keystore.location", properties.getKeystore().getLocation())); + watched.add(new BundleContentProperty("truststore.location", properties.getTruststore().getLocation())); + return watchedPaths(watched); + } - boolean hasValue() { - return StringUtils.hasText(this.value); - } + private Set watchedPemPaths(PemSslBundleProperties properties) { + List watched = new ArrayList<>(); + watched.add(new BundleContentProperty("keystore.private-key", properties.getKeystore().getPrivateKey())); + watched.add(new BundleContentProperty("keystore.certificate", properties.getKeystore().getCertificate())); + watched.add(new BundleContentProperty("truststore.private-key", properties.getTruststore().getPrivateKey())); + watched.add(new BundleContentProperty("truststore.certificate", properties.getTruststore().getCertificate())); + return watchedPaths(watched); + } + private Set watchedPaths(List properties) { + return properties.stream() + .filter(BundleContentProperty::hasValue) + .map(BundleContentProperty::toWatchPath) + .collect(Collectors.toSet()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/BundleContentPropertyTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/BundleContentPropertyTests.java new file mode 100644 index 0000000000..7ff334f08d --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/BundleContentPropertyTests.java @@ -0,0 +1,87 @@ +/* + * 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.autoconfigure.ssl; + +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link BundleContentProperty}. + * + * @author Moritz Halbritter + * @author Phillip Webb + */ +class BundleContentPropertyTests { + + private static final String PEM_TEXT = """ + -----BEGIN CERTIFICATE----- + -----END CERTIFICATE----- + """; + + @TempDir + Path temp; + + @Test + void isPemContentWhenValueIsPemTextReturnsTrue() { + BundleContentProperty property = new BundleContentProperty("name", PEM_TEXT); + assertThat(property.isPemContent()).isTrue(); + } + + @Test + void isPemContentWhenValueIsNotPemTextReturnsFalse() { + BundleContentProperty property = new BundleContentProperty("name", "file.pem"); + assertThat(property.isPemContent()).isFalse(); + } + + @Test + void hasValueWhenHasValueReturnsTrue() { + BundleContentProperty property = new BundleContentProperty("name", "file.pem"); + assertThat(property.hasValue()).isTrue(); + } + + @Test + void hasValueWhenHasNullValueReturnsFalse() { + BundleContentProperty property = new BundleContentProperty("name", null); + assertThat(property.hasValue()).isFalse(); + } + + @Test + void hasValueWhenHasEmptyValueReturnsFalse() { + BundleContentProperty property = new BundleContentProperty("name", ""); + assertThat(property.hasValue()).isFalse(); + } + + @Test + void toWatchPathWhenNotPathThrowsException() { + BundleContentProperty property = new BundleContentProperty("name", PEM_TEXT); + assertThatIllegalStateException().isThrownBy(property::toWatchPath) + .withMessage("Unable to convert 'name' property to a path"); + } + + @Test + void toWatchPathWhenPathReturnsPath() { + Path file = this.temp.toAbsolutePath().resolve("file.txt"); + BundleContentProperty property = new BundleContentProperty("name", file.toString()); + assertThat(property.toWatchPath()).isEqualTo(file); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrarTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrarTests.java index 2410b8bbaa..759bb47460 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrarTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrarTests.java @@ -106,7 +106,9 @@ class SslPropertiesBundleRegistrarTests { """.strip()); this.properties.getBundle().getPem().put("bundle1", pem); assertThatIllegalStateException().isThrownBy(() -> this.registrar.registerBundles(this.registry)) - .withMessage("SSL bundle 'bundle1' 'keystore.certificate' is not a URL and can't be watched"); + .withMessageContaining("Unable to register SSL bundle 'bundle1'") + .havingCause() + .withMessage("Unable to watch for reload on update"); } @Test @@ -121,7 +123,9 @@ class SslPropertiesBundleRegistrarTests { """.strip()); this.properties.getBundle().getPem().put("bundle1", pem); assertThatIllegalStateException().isThrownBy(() -> this.registrar.registerBundles(this.registry)) - .withMessage("SSL bundle 'bundle1' 'keystore.private-key' is not a URL and can't be watched"); + .withMessageContaining("Unable to register SSL bundle 'bundle1'") + .havingCause() + .withMessage("Unable to watch for reload on update"); } @Test @@ -145,7 +149,9 @@ class SslPropertiesBundleRegistrarTests { """.strip()); this.properties.getBundle().getPem().put("bundle1", pem); assertThatIllegalStateException().isThrownBy(() -> this.registrar.registerBundles(this.registry)) - .withMessage("SSL bundle 'bundle1' 'truststore.certificate' is not a URL and can't be watched"); + .withMessageContaining("Unable to register SSL bundle 'bundle1'") + .havingCause() + .withMessage("Unable to watch for reload on update"); } @Test @@ -160,7 +166,9 @@ class SslPropertiesBundleRegistrarTests { """.strip()); this.properties.getBundle().getPem().put("bundle1", pem); assertThatIllegalStateException().isThrownBy(() -> this.registrar.registerBundles(this.registry)) - .withMessage("SSL bundle 'bundle1' 'truststore.private-key' is not a URL and can't be watched"); + .withMessageContaining("Unable to register SSL bundle 'bundle1'") + .havingCause() + .withMessage("Unable to watch for reload on update"); } private void pathEndingWith(Set paths, String... suffixes) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemContent.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemContent.java index 280e7094df..364a3f745a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemContent.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemContent.java @@ -105,8 +105,8 @@ public final class PemContent { } /** - * Load {@link PemContent} from the given content (either the PEM content itself or - * something that can be loaded by {@link ResourceUtils#getURL}). + * Load {@link PemContent} from the given content (either the PEM content itself or a + * reference to the resource to load). * @param content the content to load * @return a new {@link PemContent} instance * @throws IOException on IO error diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreDetails.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreDetails.java index 57d6d5db60..8f97b2b55a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreDetails.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreDetails.java @@ -18,7 +18,6 @@ package org.springframework.boot.ssl.pem; import java.security.KeyStore; -import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; /** @@ -30,12 +29,12 @@ import org.springframework.util.StringUtils; * @param password the password used * {@link KeyStore#setKeyEntry(String, java.security.Key, char[], java.security.cert.Certificate[]) * setting key entries} in the {@link KeyStore} - * @param certificates the certificates content (either the PEM content itself or - * something that can be loaded by {@link ResourceUtils#getURL}). When a - * {@link #privateKey() private key} is present this value is treated as a certificate - * chain, otherwise it is treated a list of certificates that should all be registered. - * @param privateKey the private key content (either the PEM content itself or something - * that can be loaded by {@link ResourceUtils#getURL}) + * @param certificates the certificates content (either the PEM content itself or or a + * reference to the resource to load). When a {@link #privateKey() private key} is present + * this value is treated as a certificate chain, otherwise it is treated a list of + * certificates that should all be registered. + * @param privateKey the private key content (either the PEM content itself or a reference + * to the resource to load) * @param privateKeyPassword a password used to decrypt an encrypted private key * @author Scott Frederick * @author Phillip Webb @@ -53,10 +52,10 @@ public record PemSslStoreDetails(String type, String alias, String password, Str * @param password the password used * {@link KeyStore#setKeyEntry(String, java.security.Key, char[], java.security.cert.Certificate[]) * setting key entries} in the {@link KeyStore} - * @param certificates the certificate content (either the PEM content itself or - * something that can be loaded by {@link ResourceUtils#getURL}) - * @param privateKey the private key content (either the PEM content itself or - * something that can be loaded by {@link ResourceUtils#getURL}) + * @param certificates the certificate content (either the PEM content itself or a + * reference to the resource to load) + * @param privateKey the private key content (either the PEM content itself or a + * reference to the resource to load) * @param privateKeyPassword a password used to decrypt an encrypted private key * @since 3.2.0 */ @@ -67,10 +66,10 @@ public record PemSslStoreDetails(String type, String alias, String password, Str * Create a new {@link PemSslStoreDetails} instance. * @param type the key store type, for example {@code JKS} or {@code PKCS11}. A * {@code null} value will use {@link KeyStore#getDefaultType()}). - * @param certificate the certificate content (either the PEM content itself or - * something that can be loaded by {@link ResourceUtils#getURL}) - * @param privateKey the private key content (either the PEM content itself or - * something that can be loaded by {@link ResourceUtils#getURL}) + * @param certificate the certificate content (either the PEM content itself or a + * reference to the resource to load) + * @param privateKey the private key content (either the PEM content itself or a + * reference to the resource to load) * @param privateKeyPassword a password used to decrypt an encrypted private key */ public PemSslStoreDetails(String type, String certificate, String privateKey, String privateKeyPassword) { @@ -81,10 +80,10 @@ public record PemSslStoreDetails(String type, String alias, String password, Str * Create a new {@link PemSslStoreDetails} instance. * @param type the key store type, for example {@code JKS} or {@code PKCS11}. A * {@code null} value will use {@link KeyStore#getDefaultType()}). - * @param certificate the certificate content (either the PEM content itself or - * something that can be loaded by {@link ResourceUtils#getURL}) - * @param privateKey the private key content (either the PEM content itself or - * something that can be loaded by {@link ResourceUtils#getURL}) + * @param certificate the certificate content (either the PEM content itself or a + * reference to the resource to load) + * @param privateKey the private key content (either the PEM content itself or a + * reference to the resource to load) */ public PemSslStoreDetails(String type, String certificate, String privateKey) { this(type, certificate, privateKey, null); @@ -155,8 +154,8 @@ public record PemSslStoreDetails(String type, String alias, String password, Str * certificate. Note: This method doesn't actually check if the provided value * only contains a single certificate. It is functionally equivalent to * {@link #forCertificates(String)}. - * @param certificate the certificate content (either the PEM content itself or - * something that can be loaded by {@link ResourceUtils#getURL}) + * @param certificate the certificate content (either the PEM content itself or a + * reference to the resource to load) * @return a new {@link PemSslStoreDetails} instance. */ public static PemSslStoreDetails forCertificate(String certificate) { @@ -166,8 +165,8 @@ public record PemSslStoreDetails(String type, String alias, String password, Str /** * Factory method to create a new {@link PemSslStoreDetails} instance for the given * certificates. - * @param certificates the certificates content (either the PEM content itself or - * something that can be loaded by {@link ResourceUtils#getURL}) + * @param certificates the certificates content (either the PEM content itself or a + * reference to the resource to load) * @return a new {@link PemSslStoreDetails} instance. * @since 3.2.0 */