iterate any attribute in multi-valued RDN to find the correct one (#14283)

Closes #14280
This commit is contained in:
Yoann GUION 2023-03-23 11:51:01 +01:00 committed by GitHub
parent fd28cd2d4b
commit ba66fe84fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 9 deletions

View file

@ -21,7 +21,9 @@ package org.keycloak.authentication.x509;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.Principal;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.function.Function;
import org.junit.Assert; import org.junit.Assert;
import org.junit.ClassRule; import org.junit.ClassRule;
@ -40,9 +42,12 @@ public abstract class CertificateIdentityExtractorTest {
@ClassRule @ClassRule
public static CryptoInitRule cryptoInitRule = new CryptoInitRule(); public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
private static final String UPN_CERT_PATH = "/certs/UPN-cert.pem";
private static final String ANS_CERT_PATH = "/certs/ANS-cert.pem";
@Test @Test
public void testExtractsCertInPemFormat() throws Exception { public void testExtractsCertInPemFormat() throws Exception {
X509Certificate x509Certificate = getCertificate(); X509Certificate x509Certificate = getCertificate(UPN_CERT_PATH);
String certificatePem = PemUtils.encodeCertificate(x509Certificate); String certificatePem = PemUtils.encodeCertificate(x509Certificate);
@ -56,7 +61,7 @@ public abstract class CertificateIdentityExtractorTest {
@Test @Test
public void testExtractsCertInSubjectDNFormat() throws Exception { public void testExtractsCertInSubjectDNFormat() throws Exception {
X509Certificate x509Certificate = getCertificate(); X509Certificate x509Certificate = getCertificate(UPN_CERT_PATH);
UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getX500NameExtractor("CN", certs -> { UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getX500NameExtractor("CN", certs -> {
return certs[0].getSubjectX500Principal(); return certs[0].getSubjectX500Principal();
@ -69,7 +74,7 @@ public abstract class CertificateIdentityExtractorTest {
public void testX509SubjectAltName_otherName() throws Exception { public void testX509SubjectAltName_otherName() throws Exception {
UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getSubjectAltNameExtractor(0); UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getSubjectAltNameExtractor(0);
X509Certificate cert = getCertificate(); X509Certificate cert = getCertificate(UPN_CERT_PATH);
Object upn = extractor.extractUserIdentity(new X509Certificate[] { cert}); Object upn = extractor.extractUserIdentity(new X509Certificate[] { cert});
Assert.assertEquals("test-user@some-company-domain", upn); Assert.assertEquals("test-user@some-company-domain", upn);
@ -80,18 +85,34 @@ public abstract class CertificateIdentityExtractorTest {
public void testX509SubjectAltName_email() throws Exception { public void testX509SubjectAltName_email() throws Exception {
UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getSubjectAltNameExtractor(1); UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getSubjectAltNameExtractor(1);
X509Certificate cert = getCertificate(); X509Certificate cert = getCertificate(UPN_CERT_PATH);
Object upn = extractor.extractUserIdentity(new X509Certificate[] { cert}); Object upn = extractor.extractUserIdentity(new X509Certificate[] { cert});
Assert.assertEquals("test@somecompany.com", upn); Assert.assertEquals("test@somecompany.com", upn);
} }
private X509Certificate getCertificate() throws Exception { private X509Certificate getCertificate(String resourceFilename) throws Exception {
InputStream is = getClass().getResourceAsStream("/certs/UPN-cert.pem"); InputStream is = getClass().getResourceAsStream(resourceFilename);
String s = StreamUtil.readString(is, Charset.defaultCharset()); String s = StreamUtil.readString(is, Charset.defaultCharset());
return PemUtils.decodeCertificate(s); return PemUtils.decodeCertificate(s);
} }
private static final Function<X509Certificate[], Principal> subject = certs -> {
return certs[0].getSubjectX500Principal();
};
@Test
public void testX509SubjectCommonName() throws Exception {
UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getX500NameExtractor("CN", subject);
X509Certificate cert = getCertificate(ANS_CERT_PATH);
Object cn = extractor.extractUserIdentity(new X509Certificate[] { cert });
Assert.assertEquals("899700252580", cn);
}
} }

View file

@ -0,0 +1,48 @@
-----BEGIN CERTIFICATE-----
MIIInDCCBoSgAwIBAgIQY9r/z8gDS9zqPRI2Y+5HPjANBgkqhkiG9w0BAQsFADB/
MQswCQYDVQQGEwJGUjETMBEGA1UECgwKQVNJUC1TQU5URTEXMBUGA1UECwwOMDAw
MiAxODc1MTI3NTExFzAVBgNVBAsMDklHQy1TQU5URSBURVNUMSkwJwYDVQQDDCBU
RVNUIEFDIElHQy1TQU5URSBGT1JUIFBFUlNPTk5FUzAeFw0yMDA3MDExMjQ2NTJa
Fw0yMzA3MDExMjQ2NTJaMGkxCzAJBgNVBAYTAkZSMSMwIQYDVQQMDBpNYXNzZXVy
LUtpbsOpc2l0aMOpcmFwZXV0ZTE1MA4GA1UEBAwHS0lORS1DSDAOBgNVBCoMB05P
UkJFUlQwEwYDVQQDDAw4OTk3MDAyNTI1ODAwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQC6dR2AtgriH1JKsEIlS6hmyYnhGozlm8cJtBZ6KZzWGP5bV1Hr
f/WZGdKUk1N0xJKnLmIN8hwhq0MfgoPipkkyIAsrxm5cZLm75j3kHv4oXwepBz4R
84OO1bYoz/ya0hny9SCLcOnM7esQMb3ElGzNeXbx6WTl5Rz1zI7auV9sNJiiTY+M
O4cWs70SwNOlzU77XGEvm4065LZKsIRnsdNnFhXedl5jBFtkr4tpBhxWlUtlXdTh
RHLCsMLmSYV20sGzNkrnZQie6dG6b8T3nxNREAWHBNolnYSqB2WmSkxpYAyX2pcB
ufZdysa/Vucw6Ub7iWBnS48zezR3JJs3jsVzAgMBAAGjggQoMIIEJDAJBgNVHRME
AjAAMB0GA1UdDgQWBBRy+GBUTgi8gcauDvl+vmdpxdct6jAfBgNVHSMEGDAWgBQ6
8ef1zuvt943JybKf7dRlKdwf/TAOBgNVHQ8BAf8EBAMCB4AwUwYDVR0gBEwwSjBI
Bg0qgXoBgVUBBwIBAQEBMDcwNQYIKwYBBQUHAgEWKWh0dHA6Ly9pZ2Mtc2FudGUu
ZXNhbnRlLmdvdXYuZnIvUEMlMjBURVNUMB8GA1UdJQQYMBYGCCsGAQUFBwMCBgor
BgEEAYI3FAICMDUGA1UdEQQuMCygKgYKKwYBBAGCNxQCA6AcDBo4Ljk5NzAwMjUy
NTgwQGNhcnRlLWNwcy5mcjCCAUAGA1UdHwSCATcwggEzMDygOqA4hjZodHRwOi8v
aWdjLXNhbnRlLmVzYW50ZS5nb3V2LmZyL0NSTC9BQ0ktRk8tUFAtVEVTVC5jcmww
gfKgge+ggeyGgelsZGFwOi8vYW5udWFpcmUtaWdjLmVzYW50ZS5nb3V2LmZyL2Nu
PVRFU1QlMjBBQyUyMElHQy1TQU5URSUyMEZPUlQlMjBQRVJTT05ORVMsb3U9VEVT
VCUyMEFDJTIwUkFDSU5FJTIwSUdDLVNBTlRFJTIwRk9SVCxvdT1JR0MtU0FOVEUl
MjBURVNULG91PTAwMDIlMjAxODc1MTI3NTEsbz1BU0lQLVNBTlRFLGM9RlI/Y2Vy
dGlmaWNhdGVyZXZvY2F0aW9ubGlzdDtiaW5hcnk/YmFzZT9vYmplY3RDbGFzcz1w
a2lDQTCB+gYDVR0uBIHyMIHvMIHsoIHpoIHmhoHjbGRhcDovL2FubnVhaXJlLWln
Yy5lc2FudGUuZ291di5mci9jbj1URVNUJTIwQUMlMjBJR0MtU0FOVEUlMjBGT1JU
JTIwUEVSU09OTkVTLG91PVRFU1QlMjBBQyUyMFJBQ0lORSUyMElHQy1TQU5URSUy
MEZPUlQsb3U9SUdDLVNBTlRFJTIwVEVTVCxvdT0wMDAyJTIwMTg3NTEyNzUxLG89
QVNJUC1TQU5URSxjPUZSP2RlbHRhcmV2b2NhdGlvbmxpc3Q7YmluYXJ5P2Jhc2U/
b2JqZWN0Q2xhc3M9cGtpQ0EwgYAGCCsGAQUFBwEBBHQwcjAmBggrBgEFBQcwAYYa
aHR0cDovL29jc3AuZXNhbnRlLmdvdXYuZnIwSAYIKwYBBQUHMAKGPGh0dHA6Ly9p
Z2Mtc2FudGUuZXNhbnRlLmdvdXYuZnIvQUMlMjBURVNUL0FDSS1GTy1QUC1URVNU
LmNlcjAPBggqgXoBRwECBQQDBAGAMA8GCCqBegFHAQICBAMCAQAwIwYIKoF6AUcB
AgMEFxMVODAyNTAwMDAwMS8yODAwMzgzNjI2MA8GCCqBegFHAQIHBAMCAUYwDQYJ
KoZIhvcNAQELBQADggIBAKczdjq7BBhOhITDS3IK09hhwpqnjrCRkiwwoDq+f0hz
K2WxEh73RGYufDxhg/wY036TRmiAnlxPJe1cvmyVbtmJDBYKhRv4qzyeiHcSwgtc
gypOyJ2GpmrwEOPi3tZpBEhj86XKPab9m5WAyCR/jCy6Md2rY+YS5M73mwpUw3MH
SUKjq2Ykp+jCqkvPeMC1kLyYpgAFuIAVHuG7H5wauZ7K4w8fiAqxrbwmErQf4eZb
6INfCgMTqvnR05sMvUHhKtYiBG4mUtuO1a6ZN0/OPnkVOOSOi+FMg4oUp8VXa5GR
X49+AObu6MaRWSQ2ipeapJGtLYozuatvvewhRe1tJ2rxLJ/rUZbzIEMYVzVUsLpV
FcnxkL/pjKgOpZxuLuVx7VUGA8Z8QExNPkrz0BIb3/k4EYwkN8f4UjusA6Q341Sg
S3p9mnTO1pTiuap9bR/sFtWvGsFdLOyhGM9NDe9gT9d5XwWZLc+SEUnkiwkF5Zm8
ibfx+t5CwIXBqJn8701VkQYFX9s4M/biIjm5N7roFS41a+EAR77ubtI5QkmQUP1z
GUbbK/SeqyKlMWkbDnKX+vYLBa/GEZEeHePop85ErHyWqvBpsNnZEzd0cfzVcwMy
T+HRcNc38tqbleNJZcD1r0tiYr1MYLF7cZrck4oqx08beq9mm3nqunEKi6cWlKvu
-----END CERTIFICATE-----

View file

@ -24,6 +24,7 @@ import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERUTF8String; import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x500.style.BCStyle;
@ -36,9 +37,11 @@ import java.io.ByteArrayInputStream;
import java.security.Principal; import java.security.Principal;
import java.security.cert.CertificateParsingException; import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
/** /**
@ -72,7 +75,19 @@ public class BCUserIdentityExtractorProvider extends UserIdentityExtractorProvi
RDN[] rnds = name.getRDNs(x500NameStyle); RDN[] rnds = name.getRDNs(x500NameStyle);
if (rnds != null && rnds.length > 0) { if (rnds != null && rnds.length > 0) {
RDN cn = rnds[0]; RDN cn = rnds[0];
return IETFUtils.valueToString(cn.getFirst().getValue()); if(cn.isMultiValued()){
AttributeTypeAndValue[] attributeTypeAndValues = cn.getTypesAndValues();
Optional<AttributeTypeAndValue> optionalFirst = Arrays.stream(attributeTypeAndValues).filter(attributeTypeAndValue -> attributeTypeAndValue.getType().getId().equals(x500NameStyle.getId())).findFirst();
if(optionalFirst.isPresent()) {
return IETFUtils.valueToString(optionalFirst.get().getValue());
}
else {
return null;
}
}
else {
return IETFUtils.valueToString(cn.getFirst().getValue());
}
} }
} }
return null; return null;

View file

@ -24,6 +24,7 @@ import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERUTF8String; import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x500.style.BCStyle;
@ -36,9 +37,11 @@ import java.io.ByteArrayInputStream;
import java.security.Principal; import java.security.Principal;
import java.security.cert.CertificateParsingException; import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
/** /**
@ -72,7 +75,19 @@ public class BCFIPSUserIdentityExtractorProvider extends UserIdentityExtractorP
RDN[] rnds = name.getRDNs(x500NameStyle); RDN[] rnds = name.getRDNs(x500NameStyle);
if (rnds != null && rnds.length > 0) { if (rnds != null && rnds.length > 0) {
RDN cn = rnds[0]; RDN cn = rnds[0];
return IETFUtils.valueToString(cn.getFirst().getValue()); if(cn.isMultiValued()){
AttributeTypeAndValue[] attributeTypeAndValues = cn.getTypesAndValues();
Optional<AttributeTypeAndValue> optionalFirst = Arrays.stream(attributeTypeAndValues).filter(attributeTypeAndValue -> attributeTypeAndValue.getType().getId().equals(x500NameStyle.getId())).findFirst();
if(optionalFirst.isPresent()) {
return IETFUtils.valueToString(optionalFirst.get().getValue());
}
else {
return null;
}
}
else {
return IETFUtils.valueToString(cn.getFirst().getValue());
}
} }
} }
return null; return null;