Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ae68967

Browse files
Add support for multiple/optional certificates
1 parent c3efeaf commit ae68967

File tree

26 files changed

+593
-85
lines changed

26 files changed

+593
-85
lines changed

‎spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.InputStream;
2121
import java.security.GeneralSecurityException;
2222
import java.security.KeyStore;
23+
import java.util.Set;
2324

2425
import javax.net.ssl.TrustManagerFactory;
2526

@@ -53,6 +54,7 @@
5354
import org.springframework.boot.io.ApplicationResourceLoader;
5455
import org.springframework.boot.ssl.SslBundle;
5556
import org.springframework.boot.ssl.SslBundles;
57+
import org.springframework.boot.ssl.pem.PemCertificate;
5658
import org.springframework.boot.ssl.pem.PemSslStore;
5759
import org.springframework.boot.ssl.pem.PemSslStoreDetails;
5860
import org.springframework.context.annotation.Bean;
@@ -110,7 +112,7 @@ public Authenticator couchbaseAuthenticator(CouchbaseConnectionDetails connectio
110112
}
111113
Pem pem = this.properties.getAuthentication().getPem();
112114
if (pem.getCertificates() != null) {
113-
PemSslStoreDetails details = new PemSslStoreDetails(null, pem.getCertificates(), pem.getPrivateKey());
115+
PemSslStoreDetails details = new PemSslStoreDetails(null, Set.of(newPemCertificate(pem.getCertificates())), pem.getPrivateKey());
114116
PemSslStore store = PemSslStore.load(details);
115117
return CertificateAuthenticator.fromKey(store.privateKey(), pem.getPrivateKeyPassword(),
116118
store.certificates());

‎spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentProperty.java

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import org.springframework.boot.io.ApplicationResourceLoader;
2222
import org.springframework.boot.ssl.pem.PemContent;
2323
import org.springframework.core.io.Resource;
24-
import org.springframework.util.Assert;
2524
import org.springframework.util.StringUtils;
2625

2726
/**
@@ -33,9 +32,12 @@
3332
* @author Phillip Webb
3433
* @author Moritz Halbritter
3534
*/
36-
record BundleContentProperty(String name, String value) {
35+
record BundleContentProperty(String name, String value, booleanoptional) {
3736

38-
private static final String OPTIONAL_URL_PREFIX = "optional:";
37+
BundleContentProperty(String name, String value)
38+
{
39+
this(name, value,false);
40+
}
3941

4042
/**
4143
* Return if the property value is PEM content.
@@ -53,24 +55,16 @@ boolean hasValue() {
5355
return StringUtils.hasText(this.value);
5456
}
5557

56-
boolean isOptional() {
57-
return this.value.startsWith(OPTIONAL_URL_PREFIX);
58-
}
59-
60-
String getRawValue() {
61-
if (isOptional()) {
62-
return this.value.substring(OPTIONAL_URL_PREFIX.length());
63-
}
64-
return this.value;
65-
}
66-
6758
WatchablePath toWatchPath() {
6859
try {
69-
Resource resource = getResource(getRawValue());
60+
if (isPemContent()) {
61+
return null;
62+
}
63+
Resource resource = getResource();
7064
if (!resource.isFile()) {
7165
throw new BundleContentNotWatchableException(this);
7266
}
73-
return new WatchablePath(Path.of(resource.getFile().getAbsolutePath()), isOptional());
67+
return new WatchablePath(this.optional, Path.of(resource.getFile().getAbsolutePath()));
7468
}
7569
catch (Exception ex) {
7670
if (ex instanceof BundleContentNotWatchableException bundleContentNotWatchableException) {
@@ -81,9 +75,8 @@ WatchablePath toWatchPath() {
8175
}
8276
}
8377

84-
private Resource getResource(String value) {
85-
Assert.state(!isPemContent(), "Value contains PEM content");
86-
return new ApplicationResourceLoader().getResource(value);
78+
private Resource getResource() {
79+
return new ApplicationResourceLoader().getResource(this.value);
8780
}
8881

8982
}

‎spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/FileWatcher.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
/*
23
* Copyright 2012-2023 the original author or authors.
34
*
@@ -217,8 +218,7 @@ public void close() throws IOException {
217218
private record Registration(Set<WatchablePath> paths, Runnable action) {
218219

219220
Registration {
220-
paths = paths.stream().map(watchablePath ->
221-
new WatchablePath(watchablePath.path().toAbsolutePath(), watchablePath.optional()))
221+
paths = paths.stream().map(watchablePath -> new WatchablePath(watchablePath.optional(), watchablePath.path().toAbsolutePath()))
222222
.collect(Collectors.toSet());
223223
}
224224

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.ssl;
18+
19+
import org.springframework.boot.ssl.pem.PemCertificate;
20+
21+
class PemCertificateParser {
22+
23+
public static final String OPTIONAL_PREFIX = "optional:";
24+
25+
public PemCertificate parse(String source) {
26+
boolean optional = source.startsWith(OPTIONAL_PREFIX);
27+
String location = optional ? source.substring(OPTIONAL_PREFIX.length()) : source;
28+
return new PemCertificate(location, optional);
29+
}
30+
}

‎spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.boot.autoconfigure.ssl;
1818

19+
import java.util.HashSet;
20+
import java.util.Set;
21+
1922
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
2023

2124
/**
@@ -60,8 +63,14 @@ public static class Store {
6063
/**
6164
* Location or content of the certificate or certificate chain in PEM format.
6265
*/
66+
@Deprecated
6367
private String certificate;
6468

69+
/**
70+
* Set with location or content of the certificate or certificate chain in PEM format.
71+
*/
72+
private Set<String> certificates = new HashSet<>();
73+
6574
/**
6675
* Location or content of the private key in PEM format.
6776
*/
@@ -85,14 +94,29 @@ public void setType(String type) {
8594
this.type = type;
8695
}
8796

97+
@Deprecated
8898
public String getCertificate() {
8999
return this.certificate;
90100
}
91101

102+
@Deprecated
92103
public void setCertificate(String certificate) {
93104
this.certificate = certificate;
94105
}
95106

107+
public Set<String> getCertificates() {
108+
if (this.certificate != null) {
109+
Set<String> allCertificates = new HashSet<>(this.certificates);
110+
allCertificates.add(this.certificate);
111+
return allCertificates;
112+
}
113+
return this.certificates;
114+
}
115+
116+
public void setCertificates(Set<String> certificates) {
117+
this.certificates = certificates;
118+
}
119+
96120
public String getPrivateKey() {
97121
return this.privateKey;
98122
}

‎spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.boot.autoconfigure.ssl;
1818

19+
import java.util.Set;
20+
import java.util.stream.Collectors;
21+
1922
import org.springframework.boot.autoconfigure.ssl.SslBundleProperties.Key;
2023
import org.springframework.boot.ssl.SslBundle;
2124
import org.springframework.boot.ssl.SslBundleKey;
@@ -24,12 +27,12 @@
2427
import org.springframework.boot.ssl.SslStoreBundle;
2528
import org.springframework.boot.ssl.jks.JksSslStoreBundle;
2629
import org.springframework.boot.ssl.jks.JksSslStoreDetails;
30+
import org.springframework.boot.ssl.pem.PemCertificate;
2731
import org.springframework.boot.ssl.pem.PemSslStore;
2832
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
2933
import org.springframework.boot.ssl.pem.PemSslStoreDetails;
3034
import org.springframework.core.style.ToStringCreator;
3135
import org.springframework.util.Assert;
32-
import org.springframework.util.StringUtils;
3336

3437
/**
3538
* {@link SslBundle} backed by {@link JksSslBundleProperties} or
@@ -41,8 +44,6 @@
4144
*/
4245
public final class PropertiesSslBundle implements SslBundle {
4346

44-
private static final String OPTIONAL_URL_PREFIX = "optional:";
45-
4647
private final SslStoreBundle stores;
4748

4849
private final SslBundleKey key;
@@ -121,19 +122,12 @@ private static PemSslStore getPemSslStore(String propertyName, PemSslBundlePrope
121122
}
122123

123124
private static PemSslStoreDetails asPemSslStoreDetails(PemSslBundleProperties.Store properties) {
124-
return new PemSslStoreDetails(properties.getType(), getRawCertificate(properties.getCertificate()), properties.getPrivateKey(),
125-
properties.getPrivateKeyPassword(), isCertificateOptional(properties.getCertificate()));
126-
}
127-
128-
private static boolean isCertificateOptional(String certificate) {
129-
return StringUtils.hasText(certificate) && certificate.startsWith(OPTIONAL_URL_PREFIX);
130-
}
131-
132-
private static String getRawCertificate(String certificate) {
133-
if (isCertificateOptional(certificate)) {
134-
return certificate.substring(OPTIONAL_URL_PREFIX.length());
135-
}
136-
return certificate;
125+
PemCertificateParser converter = new PemCertificateParser();
126+
Set<PemCertificate> pemCertificates = properties.getCertificates().stream()
127+
.map(converter::parse)
128+
.collect(Collectors.toSet());
129+
return new PemSslStoreDetails(properties.getType(), pemCertificates, properties.getPrivateKey(),
130+
properties.getPrivateKeyPassword());
137131
}
138132

139133
/**

‎spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020
import java.util.List;
2121
import java.util.Map;
2222
import java.util.Set;
23+
import java.util.function.BiFunction;
2324
import java.util.function.Function;
25+
import java.util.function.Predicate;
2426
import java.util.function.Supplier;
2527
import java.util.stream.Collectors;
2628

29+
import org.springframework.boot.ssl.pem.PemCertificate;
2730
import org.springframework.boot.ssl.SslBundle;
2831
import org.springframework.boot.ssl.SslBundleRegistry;
2932

@@ -90,21 +93,33 @@ private Set<WatchablePath> watchedJksPaths(Bundle<JksSslBundleProperties> bundle
9093

9194
private Set<WatchablePath> watchedPemPaths(Bundle<PemSslBundleProperties> bundle) {
9295
List<BundleContentProperty> watched = new ArrayList<>();
96+
BiFunction<String, String, BundleContentProperty> contentKeyStoreCertificateProperty = locationToBundleContentProperty();
9397
watched
9498
.add(new BundleContentProperty("keystore.private-key", bundle.properties().getKeystore().getPrivateKey()));
95-
watched
96-
.add(new BundleContentProperty("keystore.certificate", bundle.properties().getKeystore().getCertificate()));
99+
bundle.properties().getKeystore().getCertificates().stream()
100+
.map(location -> contentKeyStoreCertificateProperty.apply(location, "keystore.certificate"))
101+
.forEach(watched::add);
97102
watched.add(new BundleContentProperty("truststore.private-key",
98103
bundle.properties().getTruststore().getPrivateKey()));
99-
watched.add(new BundleContentProperty("truststore.certificate",
100-
bundle.properties().getTruststore().getCertificate()));
104+
bundle.properties().getTruststore().getCertificates().stream()
105+
.map(location -> contentKeyStoreCertificateProperty.apply(location, "truststore.certificate"))
106+
.forEach(watched::add);
101107
return watchedPaths(bundle.name(), watched);
102108
}
103109

110+
private BiFunction<String, String, BundleContentProperty> locationToBundleContentProperty() {
111+
PemCertificateParser certificateParser = new PemCertificateParser();
112+
return (location, name) -> {
113+
PemCertificate certificate = certificateParser.parse(location);
114+
return new BundleContentProperty(name, certificate.location(), certificate.optional());
115+
};
116+
}
117+
104118
private Set<WatchablePath> watchedPaths(String bundleName, List<BundleContentProperty> properties) {
105119
try {
106120
return properties.stream()
107121
.filter(BundleContentProperty::hasValue)
122+
.filter(Predicate.not(BundleContentProperty::isPemContent))
108123
.map(BundleContentProperty::toWatchPath)
109124
.collect(Collectors.toSet());
110125
}

‎spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/WatchablePath.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@
1818

1919
import java.nio.file.Path;
2020

21-
record WatchablePath(Pathpath, Booleanoptional) {
22-
}
21+
record WatchablePath(booleanoptional, Pathpath) {
22+
}

‎spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/LoadedPemSslStore.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.UncheckedIOException;
2121
import java.security.PrivateKey;
2222
import java.security.cert.X509Certificate;
23+
import java.util.ArrayList;
2324
import java.util.List;
2425
import java.util.function.Supplier;
2526

@@ -58,11 +59,13 @@ private static UncheckedIOException asUncheckedIOException(String message, Excep
5859
}
5960

6061
private static List<X509Certificate> loadCertificates(PemSslStoreDetails details) throws IOException {
61-
PemContent pemContent = PemContent.load(details.certificates(), details.optional());
62-
if (pemContent == null) {
63-
return null;
62+
List<X509Certificate> certificates = new ArrayList<>();
63+
for (PemCertificate certificate : details.certificateSet()) {
64+
PemContent pemContent = PemContent.load(certificate.location(), certificate.optional());
65+
if (pemContent != null) {
66+
certificates.addAll(pemContent.getCertificates());
67+
}
6468
}
65-
List<X509Certificate> certificates = pemContent.getCertificates();
6669
Assert.state(!CollectionUtils.isEmpty(certificates), "Loaded certificates are empty");
6770
return certificates;
6871
}
@@ -72,11 +75,6 @@ private static PrivateKey loadPrivateKey(PemSslStoreDetails details) throws IOEx
7275
return (pemContent != null) ? pemContent.getPrivateKey(details.privateKeyPassword()) : null;
7376
}
7477

75-
@Override
76-
public boolean optional() {
77-
return this.details.optional();
78-
}
79-
8078
@Override
8179
public String type() {
8280
return this.details.type();
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.ssl.pem;
18+
19+
public record PemCertificate (String location, boolean optional) {
20+
21+
public PemCertificate(String location) {
22+
this(location, false);
23+
}
24+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /