Skip to content

Commit db70aa1

Browse files
Use Spring Boot PEM parser in SAML2 signing auto-configuration
Closes gh-41567
1 parent 05b6fae commit db70aa1

File tree

3 files changed

+51
-5
lines changed

3 files changed

+51
-5
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package org.springframework.boot.autoconfigure.security.saml2;
1818

1919
import java.io.InputStream;
20-
import java.security.cert.CertificateFactory;
20+
import java.security.PrivateKey;
2121
import java.security.cert.X509Certificate;
2222
import java.security.interfaces.RSAPrivateKey;
2323
import java.util.Collection;
@@ -32,11 +32,11 @@
3232
import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Registration;
3333
import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Registration.Signing;
3434
import org.springframework.boot.context.properties.PropertyMapper;
35+
import org.springframework.boot.ssl.pem.PemContent;
3536
import org.springframework.context.annotation.Bean;
3637
import org.springframework.context.annotation.Conditional;
3738
import org.springframework.context.annotation.Configuration;
3839
import org.springframework.core.io.Resource;
39-
import org.springframework.security.converter.RsaKeyConverters;
4040
import org.springframework.security.saml2.core.Saml2X509Credential;
4141
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
4242
import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata;
@@ -57,6 +57,7 @@
5757
* @author Moritz Halbritter
5858
* @author Lasse Lindqvist
5959
* @author Lasse Wulff
60+
* @author Scott Frederick
6061
*/
6162
@Configuration(proxyBeanMethods = false)
6263
@Conditional(RegistrationConfiguredCondition.class)
@@ -172,7 +173,11 @@ private RSAPrivateKey readPrivateKey(Resource location) {
172173
Assert.state(location != null, "No private key location specified");
173174
Assert.state(location.exists(), () -> "Private key location '" + location + "' does not exist");
174175
try (InputStream inputStream = location.getInputStream()) {
175-
return RsaKeyConverters.pkcs8().convert(inputStream);
176+
PemContent pemContent = PemContent.load(inputStream);
177+
PrivateKey privateKey = pemContent.getPrivateKey();
178+
Assert.isInstanceOf(RSAPrivateKey.class, privateKey,
179+
"PrivateKey in resource '" + location + "' must be an RSAPrivateKey");
180+
return (RSAPrivateKey) privateKey;
176181
}
177182
catch (Exception ex) {
178183
throw new IllegalArgumentException(ex);
@@ -183,7 +188,9 @@ private X509Certificate readCertificate(Resource location) {
183188
Assert.state(location != null, "No certificate location specified");
184189
Assert.state(location.exists(), () -> "Certificate location '" + location + "' does not exist");
185190
try (InputStream inputStream = location.getInputStream()) {
186-
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(inputStream);
191+
PemContent pemContent = PemContent.load(inputStream);
192+
List<X509Certificate> certificates = pemContent.getCertificates();
193+
return certificates.get(0);
187194
}
188195
catch (Exception ex) {
189196
throw new IllegalArgumentException(ex);

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
* @author Madhura Bhave
5959
* @author Moritz Halbritter
6060
* @author Lasse Lindqvist
61+
* @author Scott Frederick
6162
*/
6263
class Saml2RelyingPartyAutoConfigurationTests {
6364

@@ -273,6 +274,38 @@ void signRequestShouldApplyIfMetadataUriIsSet() throws Exception {
273274
}
274275
}
275276

277+
@Test
278+
void autoconfigurationWithInvalidPrivateKeyShouldFail() {
279+
this.contextRunner.withPropertyValues(
280+
PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:saml/certificate-location",
281+
PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:saml/certificate-location",
282+
PREFIX + ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
283+
PREFIX + ".foo.assertingparty.singlesignon.binding=post",
284+
PREFIX + ".foo.assertingparty.singlesignon.sign-request=false",
285+
PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
286+
PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:saml/certificate-location")
287+
.run((context) -> assertThat(context).hasFailed()
288+
.getFailure()
289+
.rootCause()
290+
.hasMessageContaining("Missing private key or unrecognized format"));
291+
}
292+
293+
@Test
294+
void autoconfigurationWithInvalidCertificateShouldFail() {
295+
this.contextRunner.withPropertyValues(
296+
PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:saml/private-key-location",
297+
PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:saml/private-key-location",
298+
PREFIX + ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
299+
PREFIX + ".foo.assertingparty.singlesignon.binding=post",
300+
PREFIX + ".foo.assertingparty.singlesignon.sign-request=false",
301+
PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
302+
PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:saml/certificate-location")
303+
.run((context) -> assertThat(context).hasFailed()
304+
.getFailure()
305+
.rootCause()
306+
.hasMessageContaining("Missing certificates or unrecognized format"));
307+
}
308+
276309
private void testMultipleProviders(String specifiedEntityId, String expected) throws Exception {
277310
try (MockWebServer server = new MockWebServer()) {
278311
server.start();

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,13 @@ public static PemContent load(Path path) throws IOException {
140140
}
141141
}
142142

143-
private static PemContent load(InputStream in) throws IOException {
143+
/**
144+
* Load {@link PemContent} from the given {@link InputStream}.
145+
* @param in an input stream to load the content from
146+
* @return the loaded PEM content
147+
* @throws IOException on IO error
148+
*/
149+
public static PemContent load(InputStream in) throws IOException {
144150
return of(StreamUtils.copyToString(in, StandardCharsets.UTF_8));
145151
}
146152

0 commit comments

Comments
 (0)