diff --git a/core/src/test/java/org/keycloak/authentication/x509/CertificateIdentityExtractorTest.java b/core/src/test/java/org/keycloak/authentication/x509/CertificateIdentityExtractorTest.java index 44f9dba24a..7bb5fbabed 100644 --- a/core/src/test/java/org/keycloak/authentication/x509/CertificateIdentityExtractorTest.java +++ b/core/src/test/java/org/keycloak/authentication/x509/CertificateIdentityExtractorTest.java @@ -21,7 +21,9 @@ package org.keycloak.authentication.x509; import java.io.InputStream; import java.nio.charset.Charset; +import java.security.Principal; import java.security.cert.X509Certificate; +import java.util.function.Function; import org.junit.Assert; import org.junit.ClassRule; @@ -40,9 +42,12 @@ public abstract class CertificateIdentityExtractorTest { @ClassRule 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 public void testExtractsCertInPemFormat() throws Exception { - X509Certificate x509Certificate = getCertificate(); + X509Certificate x509Certificate = getCertificate(UPN_CERT_PATH); String certificatePem = PemUtils.encodeCertificate(x509Certificate); @@ -56,7 +61,7 @@ public abstract class CertificateIdentityExtractorTest { @Test public void testExtractsCertInSubjectDNFormat() throws Exception { - X509Certificate x509Certificate = getCertificate(); + X509Certificate x509Certificate = getCertificate(UPN_CERT_PATH); UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getX500NameExtractor("CN", certs -> { return certs[0].getSubjectX500Principal(); @@ -69,7 +74,7 @@ public abstract class CertificateIdentityExtractorTest { public void testX509SubjectAltName_otherName() throws Exception { UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getSubjectAltNameExtractor(0); - X509Certificate cert = getCertificate(); + X509Certificate cert = getCertificate(UPN_CERT_PATH); Object upn = extractor.extractUserIdentity(new X509Certificate[] { cert}); Assert.assertEquals("test-user@some-company-domain", upn); @@ -80,18 +85,34 @@ public abstract class CertificateIdentityExtractorTest { public void testX509SubjectAltName_email() throws Exception { UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getSubjectAltNameExtractor(1); - X509Certificate cert = getCertificate(); + X509Certificate cert = getCertificate(UPN_CERT_PATH); Object upn = extractor.extractUserIdentity(new X509Certificate[] { cert}); Assert.assertEquals("test@somecompany.com", upn); } - private X509Certificate getCertificate() throws Exception { - InputStream is = getClass().getResourceAsStream("/certs/UPN-cert.pem"); + private X509Certificate getCertificate(String resourceFilename) throws Exception { + InputStream is = getClass().getResourceAsStream(resourceFilename); String s = StreamUtil.readString(is, Charset.defaultCharset()); return PemUtils.decodeCertificate(s); } + + + private static final Function 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); + } + } diff --git a/core/src/test/resources/certs/ANS-cert.pem b/core/src/test/resources/certs/ANS-cert.pem new file mode 100644 index 0000000000..7194be0d0e --- /dev/null +++ b/core/src/test/resources/certs/ANS-cert.pem @@ -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----- diff --git a/crypto/default/src/main/java/org/keycloak/crypto/def/BCUserIdentityExtractorProvider.java b/crypto/default/src/main/java/org/keycloak/crypto/def/BCUserIdentityExtractorProvider.java index f0ce43b848..13581eae64 100644 --- a/crypto/default/src/main/java/org/keycloak/crypto/def/BCUserIdentityExtractorProvider.java +++ b/crypto/default/src/main/java/org/keycloak/crypto/def/BCUserIdentityExtractorProvider.java @@ -24,6 +24,7 @@ import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.DERUTF8String; +import org.bouncycastle.asn1.x500.AttributeTypeAndValue; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; @@ -36,9 +37,11 @@ import java.io.ByteArrayInputStream; import java.security.Principal; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.function.Function; /** @@ -55,7 +58,7 @@ public class BCUserIdentityExtractorProvider extends UserIdentityExtractorProvi private ASN1ObjectIdentifier x500NameStyle; Function x500Name; - + public X500NameRDNExtractorBCProvider(String attrName, Function x500Name) { this.x500NameStyle = BCStyle.INSTANCE.attrNameToOID(attrName); this.x500Name = x500Name; @@ -72,7 +75,19 @@ public class BCUserIdentityExtractorProvider extends UserIdentityExtractorProvi RDN[] rnds = name.getRDNs(x500NameStyle); if (rnds != null && rnds.length > 0) { RDN cn = rnds[0]; - return IETFUtils.valueToString(cn.getFirst().getValue()); + if(cn.isMultiValued()){ + AttributeTypeAndValue[] attributeTypeAndValues = cn.getTypesAndValues(); + Optional 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; diff --git a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSUserIdentityExtractorProvider.java b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSUserIdentityExtractorProvider.java index 69c40369be..6cc13ad5db 100644 --- a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSUserIdentityExtractorProvider.java +++ b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSUserIdentityExtractorProvider.java @@ -24,6 +24,7 @@ import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.DERUTF8String; +import org.bouncycastle.asn1.x500.AttributeTypeAndValue; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; @@ -36,9 +37,11 @@ import java.io.ByteArrayInputStream; import java.security.Principal; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.function.Function; /** @@ -72,7 +75,19 @@ public class BCFIPSUserIdentityExtractorProvider extends UserIdentityExtractorP RDN[] rnds = name.getRDNs(x500NameStyle); if (rnds != null && rnds.length > 0) { RDN cn = rnds[0]; - return IETFUtils.valueToString(cn.getFirst().getValue()); + if(cn.isMultiValued()){ + AttributeTypeAndValue[] attributeTypeAndValues = cn.getTypesAndValues(); + Optional 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;