Skip to content

Commit 56928f6

Browse files
committed
Separate RP and AP Credentials
Closes gh-8788
1 parent 87cd1d7 commit 56928f6

File tree

12 files changed

+812
-71
lines changed

12 files changed

+812
-71
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
* Copyright 2002-2020 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+
package org.springframework.security.saml2.core;
17+
18+
import java.security.PrivateKey;
19+
import java.security.cert.X509Certificate;
20+
import java.util.LinkedHashSet;
21+
import java.util.Objects;
22+
import java.util.Set;
23+
24+
import org.springframework.util.Assert;
25+
26+
import static java.util.Arrays.asList;
27+
import static org.springframework.util.Assert.notEmpty;
28+
import static org.springframework.util.Assert.notNull;
29+
import static org.springframework.util.Assert.state;
30+
31+
/**
32+
* An object for holding a public certificate, any associated private key, and its intended
33+
* <a href="https://www.oasis-open.org/committees/download.php/8958/sstc-saml-implementation-guidelines-draft-01.pdf">
34+
* usages
35+
* </a>
36+
* (Line 584, Section 4.3 Credentials).
37+
*
38+
* @since 5.4
39+
* @author Filip Hanik
40+
* @author Josh Cummings
41+
*/
42+
public final class Saml2X509Credential {
43+
public enum Saml2X509CredentialType {
44+
VERIFICATION,
45+
ENCRYPTION,
46+
SIGNING,
47+
DECRYPTION,
48+
}
49+
50+
private final PrivateKey privateKey;
51+
private final X509Certificate certificate;
52+
private final Set<Saml2X509CredentialType> credentialTypes;
53+
54+
/**
55+
* Creates a {@link Saml2X509Credential} using the provided parameters
56+
*
57+
* @param certificate the credential's public certificiate
58+
* @param types the credential's intended usages, must be one of {@link Saml2X509CredentialType#VERIFICATION} or
59+
* {@link Saml2X509CredentialType#ENCRYPTION} or both.
60+
*/
61+
public Saml2X509Credential(X509Certificate certificate, Saml2X509CredentialType... types) {
62+
this(null, false, certificate, types);
63+
validateUsages(types, Saml2X509CredentialType.VERIFICATION, Saml2X509CredentialType.ENCRYPTION);
64+
}
65+
66+
/**
67+
* Creates a {@link Saml2X509Credential} using the provided parameters
68+
*
69+
* @param privateKey the credential's private key
70+
* @param certificate the credential's public certificate
71+
* @param types the credential's intended usages, must be one of {@link Saml2X509CredentialType#SIGNING} or
72+
* {@link Saml2X509CredentialType#DECRYPTION} or both.
73+
*/
74+
public Saml2X509Credential(PrivateKey privateKey, X509Certificate certificate, Saml2X509CredentialType... types) {
75+
this(privateKey, true, certificate, types);
76+
validateUsages(types, Saml2X509CredentialType.SIGNING, Saml2X509CredentialType.DECRYPTION);
77+
}
78+
79+
/**
80+
* Creates a {@link Saml2X509Credential} using the provided parameters
81+
*
82+
* @param privateKey the credential's private key
83+
* @param certificate the credential's public certificate
84+
* @param types the credential's intended usages
85+
*/
86+
public Saml2X509Credential(PrivateKey privateKey, X509Certificate certificate, Set<Saml2X509CredentialType> types) {
87+
Assert.notNull(certificate, "certificate cannot be null");
88+
Assert.notNull(types, "credentialTypes cannot be null");
89+
this.privateKey = privateKey;
90+
this.certificate = certificate;
91+
this.credentialTypes = types;
92+
}
93+
94+
private Saml2X509Credential(
95+
PrivateKey privateKey,
96+
boolean keyRequired,
97+
X509Certificate certificate,
98+
Saml2X509CredentialType... types) {
99+
notNull(certificate, "certificate cannot be null");
100+
notEmpty(types, "credentials types cannot be empty");
101+
if (keyRequired) {
102+
notNull(privateKey, "privateKey cannot be null");
103+
}
104+
this.privateKey = privateKey;
105+
this.certificate = certificate;
106+
this.credentialTypes = new LinkedHashSet<>(asList(types));
107+
}
108+
109+
/**
110+
* Get the private key for this credential
111+
*
112+
* @return the private key, may be null
113+
* @see {@link #Saml2X509Credential(PrivateKey, X509Certificate, Saml2X509CredentialType...)}
114+
*/
115+
public PrivateKey getPrivateKey() {
116+
return this.privateKey;
117+
}
118+
119+
/**
120+
* Get the public certificate for this credential
121+
*
122+
* @return the public certificate
123+
*/
124+
public X509Certificate getCertificate() {
125+
return this.certificate;
126+
}
127+
128+
/**
129+
* Indicate whether this credential can be used for signing
130+
*
131+
* @return true if the credential has a {@link Saml2X509CredentialType#SIGNING} type
132+
*/
133+
public boolean isSigningCredential() {
134+
return getCredentialTypes().contains(Saml2X509CredentialType.SIGNING);
135+
}
136+
137+
/**
138+
* Indicate whether this credential can be used for decryption
139+
*
140+
* @return true if the credential has a {@link Saml2X509CredentialType#DECRYPTION} type
141+
*/
142+
public boolean isDecryptionCredential() {
143+
return getCredentialTypes().contains(Saml2X509CredentialType.DECRYPTION);
144+
}
145+
146+
/**
147+
* Indicate whether this credential can be used for verification
148+
*
149+
* @return true if the credential has a {@link Saml2X509CredentialType#VERIFICATION} type
150+
*/
151+
public boolean isVerificationCredential() {
152+
return getCredentialTypes().contains(Saml2X509CredentialType.VERIFICATION);
153+
}
154+
155+
/**
156+
* Indicate whether this credential can be used for encryption
157+
*
158+
* @return true if the credential has a {@link Saml2X509CredentialType#ENCRYPTION} type
159+
*/
160+
public boolean isEncryptionCredential() {
161+
return getCredentialTypes().contains(Saml2X509CredentialType.ENCRYPTION);
162+
}
163+
164+
/**
165+
* List all this credential's intended usages
166+
*
167+
* @return the set of this credential's intended usages
168+
*/
169+
public Set<Saml2X509CredentialType> getCredentialTypes() {
170+
return this.credentialTypes;
171+
}
172+
173+
@Override
174+
public boolean equals(Object o) {
175+
if (this == o) return true;
176+
if (o == null || getClass() != o.getClass()) return false;
177+
Saml2X509Credential that = (Saml2X509Credential) o;
178+
return Objects.equals(this.privateKey, that.privateKey) &&
179+
this.certificate.equals(that.certificate) &&
180+
this.credentialTypes.equals(that.credentialTypes);
181+
}
182+
183+
@Override
184+
public int hashCode() {
185+
return Objects.hash(this.privateKey, this.certificate, this.credentialTypes);
186+
}
187+
188+
private void validateUsages(Saml2X509CredentialType[] usages, Saml2X509CredentialType... validUsages) {
189+
for (Saml2X509CredentialType usage : usages) {
190+
boolean valid = false;
191+
for (Saml2X509CredentialType validUsage : validUsages) {
192+
if (usage == validUsage) {
193+
valid = true;
194+
break;
195+
}
196+
}
197+
state(valid, () -> usage +" is not a valid usage for this credential");
198+
}
199+
}
200+
}

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/credentials/Saml2X509Credential.java

+33-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,8 +18,11 @@
1818
import java.security.PrivateKey;
1919
import java.security.cert.X509Certificate;
2020
import java.util.LinkedHashSet;
21+
import java.util.Objects;
2122
import java.util.Set;
2223

24+
import org.springframework.util.Assert;
25+
2326
import static java.util.Arrays.asList;
2427
import static org.springframework.util.Assert.notEmpty;
2528
import static org.springframework.util.Assert.notNull;
@@ -32,8 +35,14 @@
3235
* Line: 584, Section 4.3 Credentials Used for both signing, signature verification and encryption/decryption
3336
*
3437
* @since 5.2
38+
* @deprecated Use {@link org.springframework.security.saml2.core.Saml2X509Credential} instead
3539
*/
40+
@Deprecated
3641
public class Saml2X509Credential {
42+
/**
43+
* @deprecated Use {@link org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType} instead
44+
*/
45+
@Deprecated
3746
public enum Saml2X509CredentialType {
3847
VERIFICATION,
3948
ENCRYPTION,
@@ -70,6 +79,14 @@ public Saml2X509Credential(PrivateKey privateKey, X509Certificate certificate, S
7079
validateUsages(types, Saml2X509CredentialType.SIGNING, Saml2X509CredentialType.DECRYPTION);
7180
}
7281

82+
public Saml2X509Credential(PrivateKey privateKey, X509Certificate certificate, Set<Saml2X509CredentialType> types) {
83+
Assert.notNull(certificate, "certificate cannot be null");
84+
Assert.notEmpty(types, "credentialTypes cannot be empty");
85+
this.privateKey = privateKey;
86+
this.certificate = certificate;
87+
this.credentialTypes = types;
88+
}
89+
7390
private Saml2X509Credential(
7491
PrivateKey privateKey,
7592
boolean keyRequired,
@@ -147,6 +164,21 @@ public X509Certificate getCertificate() {
147164
return this.certificate;
148165
}
149166

167+
@Override
168+
public boolean equals(Object o) {
169+
if (this == o) return true;
170+
if (o == null || getClass() != o.getClass()) return false;
171+
Saml2X509Credential that = (Saml2X509Credential) o;
172+
return Objects.equals(this.privateKey, that.privateKey) &&
173+
this.certificate.equals(that.certificate) &&
174+
this.credentialTypes.equals(that.credentialTypes);
175+
}
176+
177+
@Override
178+
public int hashCode() {
179+
return Objects.hash(this.privateKey, this.certificate, this.credentialTypes);
180+
}
181+
150182
private void validateUsages(Saml2X509CredentialType[] usages, Saml2X509CredentialType... validUsages) {
151183
for (Saml2X509CredentialType usage : usages) {
152184
boolean valid = false;

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java

+9-7
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
100100
import org.springframework.security.saml2.Saml2Exception;
101101
import org.springframework.security.saml2.core.Saml2Error;
102-
import org.springframework.security.saml2.credentials.Saml2X509Credential;
102+
import org.springframework.security.saml2.core.Saml2X509Credential;
103103
import org.springframework.util.Assert;
104104
import org.springframework.util.CollectionUtils;
105105
import org.springframework.util.StringUtils;
@@ -480,7 +480,8 @@ public Converter<Response, Map<String, Saml2AuthenticationException>> apply(Saml
480480

481481
private SignatureTrustEngine buildSignatureTrustEngine(Saml2AuthenticationToken token) {
482482
Set<Credential> credentials = new HashSet<>();
483-
for (Saml2X509Credential key : token.getRelyingPartyRegistration().getVerificationCredentials()) {
483+
Collection<Saml2X509Credential> keys = token.getRelyingPartyRegistration().getAssertingPartyDetails().getVerificationX509Credentials();
484+
for (Saml2X509Credential key : keys) {
484485
BasicX509Credential cred = new BasicX509Credential(key.getCertificate());
485486
cred.setUsageType(UsageType.SIGNING);
486487
cred.setEntityId(token.getRelyingPartyRegistration().getAssertingPartyDetails().getEntityId());
@@ -536,8 +537,8 @@ public Converter<EncryptedAssertion, Assertion> apply(Saml2AuthenticationToken t
536537
return encrypted -> {
537538
Saml2AuthenticationException last =
538539
authException(DECRYPTION_ERROR, "No valid decryption credentials found.");
539-
List<Saml2X509Credential> decryptionCredentials = token.getRelyingPartyRegistration().getDecryptionCredentials();
540-
for (Saml2X509Credential key : decryptionCredentials) {
540+
Collection<Saml2X509Credential> keys = token.getRelyingPartyRegistration().getDecryptionX509Credentials();
541+
for (Saml2X509Credential key : keys) {
541542
Decrypter decrypter = getDecrypter(key);
542543
try {
543544
return decrypter.decrypt(encrypted);
@@ -618,7 +619,8 @@ protected ValidationResult validateStatements(@Nonnull Assertion assertion, @Non
618619

619620
private SignatureTrustEngine buildSignatureTrustEngine(Saml2AuthenticationToken token) {
620621
Set<Credential> credentials = new HashSet<>();
621-
for (Saml2X509Credential key : token.getRelyingPartyRegistration().getVerificationCredentials()) {
622+
Collection<Saml2X509Credential> keys = token.getRelyingPartyRegistration().getAssertingPartyDetails().getVerificationX509Credentials();
623+
for (Saml2X509Credential key : keys) {
622624
BasicX509Credential cred = new BasicX509Credential(key.getCertificate());
623625
cred.setUsageType(UsageType.SIGNING);
624626
cred.setEntityId(token.getRelyingPartyRegistration().getAssertingPartyDetails().getEntityId());
@@ -730,8 +732,8 @@ public Converter<EncryptedID, NameID> apply(Saml2AuthenticationToken token) {
730732
return encrypted -> {
731733
Saml2AuthenticationException last =
732734
authException(DECRYPTION_ERROR, "No valid decryption credentials found.");
733-
List<Saml2X509Credential> decryptionCredentials = token.getRelyingPartyRegistration().getDecryptionCredentials();
734-
for (Saml2X509Credential key : decryptionCredentials) {
735+
Collection<Saml2X509Credential> keys = token.getRelyingPartyRegistration().getDecryptionX509Credentials();
736+
for (Saml2X509Credential key : keys) {
735737
Decrypter decrypter = getDecrypter(key);
736738
try {
737739
return (NameID) decrypter.decrypt(encrypted);

0 commit comments

Comments
 (0)