Skip to content

Commit 88118af

Browse files
committed
Use RDN Parsing
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
1 parent 9ad7981 commit 88118af

3 files changed

Lines changed: 96 additions & 14 deletions

File tree

web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
package org.springframework.security.web.authentication.preauth.x509;
1818

1919
import java.security.cert.X509Certificate;
20-
import java.util.regex.Matcher;
21-
import java.util.regex.Pattern;
20+
import java.util.List;
2221

22+
import javax.naming.InvalidNameException;
23+
import javax.naming.ldap.LdapName;
24+
import javax.naming.ldap.Rdn;
2325
import javax.security.auth.x500.X500Principal;
2426

2527
import org.apache.commons.logging.Log;
@@ -47,14 +49,13 @@ public final class SubjectX500PrincipalExtractor implements X509PrincipalExtract
4749

4850
private final Log logger = LogFactory.getLog(getClass());
4951

50-
private static final Pattern EMAIL_SUBJECT_DN_PATTERN = Pattern.compile("OID.1.2.840.113549.1.9.1=(.*?)(?:,|$)",
51-
Pattern.CASE_INSENSITIVE);
52+
private static final String EMAIL_SUBJECT_DN_TYPE = "OID.1.2.840.113549.1.9.1";
5253

53-
private static final Pattern CN_SUBJECT_DN_PATTERN = Pattern.compile("CN=(.*?)(?:,|$)", Pattern.CASE_INSENSITIVE);
54+
private static final String CN_SUBJECT_DN_TYPE = "CN";
5455

5556
private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
5657

57-
private Pattern subjectDnPattern = CN_SUBJECT_DN_PATTERN;
58+
private String subjectDnType = CN_SUBJECT_DN_TYPE;
5859

5960
private String x500PrincipalFormat = X500Principal.RFC2253;
6061

@@ -64,16 +65,31 @@ public Object extractPrincipal(X509Certificate clientCert) {
6465
X500Principal principal = clientCert.getSubjectX500Principal();
6566
String subjectDN = principal.getName(this.x500PrincipalFormat);
6667
this.logger.debug(LogMessage.format("Subject DN is '%s'", subjectDN));
67-
Matcher matcher = this.subjectDnPattern.matcher(subjectDN);
68-
if (!matcher.find()) {
69-
throw new BadCredentialsException(this.messages.getMessage("SubjectX500PrincipalExtractor.noMatching",
70-
new Object[] { subjectDN }, "No matching pattern was found in subject DN: {0}"));
71-
}
72-
String principalName = matcher.group(1);
68+
String principalName = getSubject(subjectDN);
7369
this.logger.debug(LogMessage.format("Extracted Principal name is '%s'", principalName));
7470
return principalName;
7571
}
7672

73+
private List<Rdn> getDns(String subjectDn) {
74+
try {
75+
return new LdapName(subjectDn).getRdns();
76+
}
77+
catch (InvalidNameException ex) {
78+
throw new BadCredentialsException("Failed to parse client certificate", ex);
79+
}
80+
}
81+
82+
private String getSubject(String subjectDn) {
83+
for (Rdn rdn : getDns(subjectDn)) {
84+
String type = rdn.getType();
85+
if (this.subjectDnType.equals(type)) {
86+
return String.valueOf(rdn.getValue());
87+
}
88+
}
89+
throw new BadCredentialsException(this.messages.getMessage("SubjectX500PrincipalExtractor.noMatching",
90+
new Object[] { subjectDn }, "No matching pattern was found in subject DN: {0}"));
91+
}
92+
7793
@Override
7894
public void setMessageSource(MessageSource messageSource) {
7995
Assert.notNull(messageSource, "messageSource cannot be null");
@@ -104,11 +120,11 @@ public void setMessageSource(MessageSource messageSource) {
104120
*/
105121
public void setExtractPrincipalNameFromEmail(boolean extractPrincipalNameFromEmail) {
106122
if (extractPrincipalNameFromEmail) {
107-
this.subjectDnPattern = EMAIL_SUBJECT_DN_PATTERN;
123+
this.subjectDnType = EMAIL_SUBJECT_DN_TYPE;
108124
this.x500PrincipalFormat = X500Principal.RFC1779;
109125
}
110126
else {
111-
this.subjectDnPattern = CN_SUBJECT_DN_PATTERN;
127+
this.subjectDnType = CN_SUBJECT_DN_TYPE;
112128
this.x500PrincipalFormat = X500Principal.RFC2253;
113129
}
114130
}

web/src/test/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractorTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,22 @@ void extractWhenCnAtEndThenExtractsPrincipalName() throws Exception {
5353
assertThat(principal).isEqualTo("Duke");
5454
}
5555

56+
@Test
57+
void extractWhenDnEmbeddedInCnThenExtractsPrincipalName() throws Exception {
58+
Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertficateWithEmbeddedDn());
59+
60+
assertThat(principal).isEqualTo("luke");
61+
}
62+
63+
@Test
64+
void extractWhenEmailDnEmbeddedInCnThenExtractsEmail() throws Exception {
65+
this.extractor.setExtractPrincipalNameFromEmail(true);
66+
67+
Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertficateWithEmbeddedEmailDn());
68+
69+
assertThat(principal).isEqualTo("luke@monkeymachine");
70+
}
71+
5672
@Test
5773
void setMessageSourceWhenNullThenThrowsException() {
5874
assertThatIllegalArgumentException().isThrownBy(() -> this.extractor.setMessageSource(null));

web/src/test/java/org/springframework/security/web/authentication/preauth/x509/X509TestUtils.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,54 @@ public static X509Certificate buildTestCertificateWithCnAtEnd() throws Exception
135135
return (X509Certificate) cf.generateCertificate(in);
136136
}
137137

138+
public static X509Certificate buildTestCertficateWithEmbeddedDn() throws Exception {
139+
String cert = "-----BEGIN CERTIFICATE-----\n"
140+
+ "MIIDDTCCAfWgAwIBAgIJANSyvk4gJhqPMA0GCSqGSIb3DQEBCwUAMEYxDTALBgNV\n"
141+
+ "BAMMBGx1a2UxETAPBgNVBAsMCENOPWR1a2UsMRUwEwYDVQQKDAxFeGFtcGxlIENv\n"
142+
+ "cnAxCzAJBgNVBAYTAlVTMB4XDTI2MDEwNDE5MjY0N1oXDTI3MDEwNTE5MjY0N1ow\n"
143+
+ "RjENMAsGA1UEAwwEbHVrZTERMA8GA1UECwwIQ049ZHVrZSwxFTATBgNVBAoMDEV4\n"
144+
+ "YW1wbGUgQ29ycDELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n"
145+
+ "ggEKAoIBAQDU9fY74nEFbBKfIef7CK02J/BJb42sIF9kD8eHN5OvEwLQBeTh30it\n"
146+
+ "E7LLalXyOXeUFkPe1N1ZhGdVak9udsIqULSvQaWqTbN+IrAGklZAxuXYTC1GbhMF\n"
147+
+ "AkGWWM55J2SNqVGQaHzZUn6VPxWaDft6nZR0DxuvXMYM5kVG6VErdB3ygGUv8cjQ\n"
148+
+ "QBKAYpsZeRldnauRPt2dImmGTagvSuJVyr8X/AioE2Rl0guii456AKw+QSvRiZ+g\n"
149+
+ "w08Y8C9nDyzQmurqpdYYkp0X+4yqm1iVowMX+tSPvHnlqJdvVzaW2b0yRzrrT6ao\n"
150+
+ "UCgw25slR1P1IcyzqPKWQIoQRnYIaX1bAgMBAAEwDQYJKoZIhvcNAQELBQADggEB\n"
151+
+ "AIos+nr8DFM6bAt9AI/79O/12hcN7gVv4F3P4Vz6NRRkkvsb9WMN8fLLDEsEJ/BQ\n"
152+
+ "eQkAVnhlmAe++vrqy8OTHoQ7F5C3K0zrr19NLNoyNFTkXkFgnm4ZhYinSbusuIb7\n"
153+
+ "LPYoyCnEEiMdl0VMWWSWcOvZpipbvTtH3CiVxTqXLjFFNraEAyUN50kXjo/zuHpK\n"
154+
+ "HzTS1BAu0li9GdV3Da2ELdDx90zaUym7dDIejY4YUlXYIJ5UUYS61fqtgOHGLLdb\n"
155+
+ "UXGAr5gqEe7OrQ9D4ebg9w5ciTb7g1H2CmirjTf/rkii8AojmsGFKIfGVe3gY6EB\n" + "o9eF5FV9V9leo5yLo25ev08=\n"
156+
+ "-----END CERTIFICATE-----";
157+
ByteArrayInputStream in = new ByteArrayInputStream(cert.getBytes());
158+
CertificateFactory cf = CertificateFactory.getInstance("X.509");
159+
return (X509Certificate) cf.generateCertificate(in);
160+
}
161+
162+
public static X509Certificate buildTestCertficateWithEmbeddedEmailDn() throws Exception {
163+
String cert = "-----BEGIN CERTIFICATE-----\n"
164+
+ "MIIDfDCCAmSgAwIBAgIIXHoOUFeZ29MwDQYJKoZIhvcNAQELBQAwfjEhMB8GCSqG\n"
165+
+ "SIb3DQEJARYSbHVrZUBtb25rZXltYWNoaW5lMTUwMwYDVQQLDCxPSUQuMS4yLjg0\n"
166+
+ "MC4xMTM1NDkuMS45LjE9ZHVrZUBnb3JpbGxhZ2FkZ2V0LDEVMBMGA1UECgwMRXhh\n"
167+
+ "bXBsZSBDb3JwMQswCQYDVQQGEwJVUzAeFw0yNjAxMDQxOTMxMDhaFw0yNzAxMDUx\n"
168+
+ "OTMxMDhaMH4xITAfBgkqhkiG9w0BCQEWEmx1a2VAbW9ua2V5bWFjaGluZTE1MDMG\n"
169+
+ "A1UECwwsT0lELjEuMi44NDAuMTEzNTQ5LjEuOS4xPWR1a2VAZ29yaWxsYWdhZGdl\n"
170+
+ "dCwxFTATBgNVBAoMDEV4YW1wbGUgQ29ycDELMAkGA1UEBhMCVVMwggEiMA0GCSqG\n"
171+
+ "SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBuIQWnj+uvvG+4ZIyFMs4dSbiBavubCmC\n"
172+
+ "hudrHr93hP19QbPulbHRTVCUqEi8efvq+J9jmMdPd7tziuDX02PeG9uljp9+c5Ir\n"
173+
+ "pw9/oMoTRkF7K4PK1JxLN4tcgxjxVA4QkS+MjKLPeHrYyGCjKspcHbi+zBiQ9Xqp\n"
174+
+ "yHWq6N5XPd6mEj2gh0zamnsJCeUCOX4SJbcp3MFtcYzhguHAeVhy9Jv+EAMJejDn\n"
175+
+ "YIZmMUdP6Ykf2zTzs/4L3bRZb0oS5WvfeRdJB6SKg8mNO/jdGX87krSio//cRdDy\n"
176+
+ "TGQK+YCVDf8GyLLavYZW56AJbZxL3MWgHYilQjj4p+Kw/PWpaBVvAgMBAAEwDQYJ\n"
177+
+ "KoZIhvcNAQELBQADggEBAKVTMIo8JO0H0HRrpsEDP17E2pnfMJV4g70BwClUMMek\n"
178+
+ "wNIWZn+6XPR8oObzzjnVWXjrovMkmmyFk0vWIpF68MPyiQ++5fwdzOZiQtUP177n\n"
179+
+ "9ulAtLoIJld3olGeL9VsCZGp3J2PqiDe613zd+bkSUG1lQYC2awozWqJEdvwJJtf\n"
180+
+ "j9nlhyMsARKEEu3tFGJsCHST3XhbhFKOraf/GZ21xW650R7ap0ZNaEiB16M2a5Oe\n"
181+
+ "WXasgUukIo82Z8+yK4IITeCcr0aA1fJxwhU8J6qfYWloaoirSYj487HRnPPv3X/b\n"
182+
+ "RxZynIjtGKygT6T1dRaWennmoitqfprJnEO2tlhLwP0=\n" + "-----END CERTIFICATE-----";
183+
ByteArrayInputStream in = new ByteArrayInputStream(cert.getBytes());
184+
CertificateFactory cf = CertificateFactory.getInstance("X.509");
185+
return (X509Certificate) cf.generateCertificate(in);
186+
}
187+
138188
}

0 commit comments

Comments
 (0)