Select Most Specific RDN in X500 Principal Extractor
SubjectX500PrincipalExtractor now returns the left-most, most-specific matching RDN value, matching both convention and SubjectDnX509PrincipalExtractor. Since LdapName#getRdns lists RDNs most-significant first, the extractor reads them in reverse so that a subject with more than one matching attribute (e.g. two CNs) resolves to the most specific value. Closes gh-19254 Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
This commit is contained in:
+6
-1
@@ -17,6 +17,8 @@
|
||||
package org.springframework.security.web.authentication.preauth.x509;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.naming.InvalidNameException;
|
||||
@@ -72,7 +74,10 @@ public final class SubjectX500PrincipalExtractor implements X509PrincipalExtract
|
||||
|
||||
private List<Rdn> getDns(String subjectDn) {
|
||||
try {
|
||||
return new LdapName(subjectDn).getRdns();
|
||||
// read most-specific first, see gh-19254
|
||||
List<Rdn> rdns = new ArrayList<>(new LdapName(subjectDn).getRdns());
|
||||
Collections.reverse(rdns);
|
||||
return rdns;
|
||||
}
|
||||
catch (InvalidNameException ex) {
|
||||
throw new BadCredentialsException("Failed to parse client certificate", ex);
|
||||
|
||||
+7
@@ -74,6 +74,13 @@ public class SubjectDnX509PrincipalExtractorTests {
|
||||
assertThat(principal).isEqualTo("Duke");
|
||||
}
|
||||
|
||||
// gh-19254
|
||||
@Test
|
||||
public void defaultCNPatternReturnsMostSpecificPrincipalWhenMultipleCns() throws Exception {
|
||||
Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertificateWithMultipleCns());
|
||||
assertThat(principal).isEqualTo("alice");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setMessageSourceWhenNullThenThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.extractor.setMessageSource(null));
|
||||
|
||||
+8
@@ -60,6 +60,14 @@ public class SubjectX500PrincipalExtractorTests {
|
||||
assertThat(principal).isEqualTo("luke");
|
||||
}
|
||||
|
||||
// gh-19254
|
||||
@Test
|
||||
void extractWhenMultipleCnsThenExtractsMostSpecificCn() throws Exception {
|
||||
Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertificateWithMultipleCns());
|
||||
|
||||
assertThat(principal).isEqualTo("alice");
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractWhenEmailDnEmbeddedInCnThenExtractsEmail() throws Exception {
|
||||
this.extractor.setExtractPrincipalNameFromEmail(true);
|
||||
|
||||
+34
@@ -185,4 +185,38 @@ public final class X509TestUtils {
|
||||
return (X509Certificate) cf.generateCertificate(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an X.509 certificate whose subject contains more than one CN. The subject DN
|
||||
* is:
|
||||
*
|
||||
* <pre>
|
||||
* CN=alice, CN=bob, O=Example Corp, C=US
|
||||
* </pre>
|
||||
*
|
||||
* where {@code CN=alice} is the most specific (left-most) RDN.
|
||||
*/
|
||||
public static X509Certificate buildTestCertificateWithMultipleCns() throws Exception {
|
||||
String cert = "-----BEGIN CERTIFICATE-----\n"
|
||||
+ "MIIDJzCCAg+gAwIBAgIIclOz1VulWC8wDQYJKoZIhvcNAQEMBQAwQjELMAkGA1UE\n"
|
||||
+ "BhMCVVMxFTATBgNVBAoTDEV4YW1wbGUgQ29ycDEMMAoGA1UEAxMDYm9iMQ4wDAYD\n"
|
||||
+ "VQQDEwVhbGljZTAeFw0yNjA2MDExOTQzMTVaFw0zNjA1MjkxOTQzMTVaMEIxCzAJ\n"
|
||||
+ "BgNVBAYTAlVTMRUwEwYDVQQKEwxFeGFtcGxlIENvcnAxDDAKBgNVBAMTA2JvYjEO\n"
|
||||
+ "MAwGA1UEAxMFYWxpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCX\n"
|
||||
+ "q8hZrTRHJEN7+D6yK65OKeCTVU+WccI6awz6g4T6O3aoC+1IiUljsEYn+xuDfx5L\n"
|
||||
+ "L/O1kejjmbYt+vzRmiILoJ9xKfW3ERcB4+gEar959Dkj6wmpsgOKRjmOvcOFOkEe\n"
|
||||
+ "gU1F7t04JHOou3DaAkHMNMQV+3jsWSh9Rry7XZBDkcT8XbbagoCgSIIef03Qtw31\n"
|
||||
+ "zOBwqmJmd6CQhFJva5cl8cCE2xZinOIiz/2j7VprZTjhkud2M4bRnK3T0WExyOCg\n"
|
||||
+ "jvjSf5ZoOKwC2Z9Q/Oyf8WKpren1+GfZhAKKmn7QZ2foHhdPPRtNjRGE6SqQyW2u\n"
|
||||
+ "8+1tOXl+aRCF19rR7gIpAgMBAAGjITAfMB0GA1UdDgQWBBRfLtnK5WKU0q3zxIrr\n"
|
||||
+ "y3GD+lYplzANBgkqhkiG9w0BAQwFAAOCAQEAe+/FHqErVPsF/sHrVHny8mIsn3ux\n"
|
||||
+ "qE9P24KNF0oIfmBrAqqge6hoVQ8PS+JialyqFf//osuDjiuYaBEKBw7GCoA6I8mr\n"
|
||||
+ "FA7wyFaGosfq7An5vxkJl7lap2u5oSVv3dCy13Bs0ziYmNlTkfHDLy9yh7jpH1wg\n"
|
||||
+ "TvylH9O4Vc7y9rzzpIjMCuQJ/wJ4MuJ2mSarYZsx3UQHIRfpKtR/9jbFMX1Rbv/A\n"
|
||||
+ "N0XD+NrtFjiikp71y3aCO1EHnGG7qPKCWh3PzaNoWFpyZKDBvud8ymW7RMiLPgv9\n"
|
||||
+ "eTa82KGgnCwtKCNzGkszIn7fza/6xnCuzvh4y9tYE5BWP2mcMRtRmDD07w==\n" + "-----END CERTIFICATE-----";
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(cert.getBytes());
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
return (X509Certificate) cf.generateCertificate(in);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user