keycloak-uncached

[KEYCLOAK-6116] - Get email attribute from 'subject alternative

2/16/2018 4:14:32 PM

Details

diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticator.java
index d5c6b60..742454e 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticator.java
@@ -57,6 +57,7 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
     public static final String MAPPING_SOURCE_SELECTION = "x509-cert-auth.mapping-source-selection";
     public static final String MAPPING_SOURCE_CERT_SUBJECTDN = "Match SubjectDN using regular expression";
     public static final String MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL = "Subject's e-mail";
+    public static final String MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL = "Subject's Alternative Name E-mail";
     public static final String MAPPING_SOURCE_CERT_SUBJECTDN_CN = "Subject's Common Name";
     public static final String MAPPING_SOURCE_CERT_ISSUERDN = "Match IssuerDN using regular expression";
     public static final String MAPPING_SOURCE_CERT_ISSUERDN_EMAIL = "Issuer's e-mail";
@@ -146,6 +147,9 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
                             .either(UserIdentityExtractor.getX500NameExtractor(BCStyle.EmailAddress, subject))
                             .or(UserIdentityExtractor.getX500NameExtractor(BCStyle.E, subject));
                     break;
+                case SUBJECTALTNAME_EMAIL:
+                    extractor = UserIdentityExtractor.getSubjectAltNameExtractor(1);
+                    break;
                 case ISSUERDN_CN:
                     extractor = UserIdentityExtractor.getX500NameExtractor(BCStyle.CN, issuer);
                     break;
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticatorFactory.java
index 03601e2..29af551 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticatorFactory.java
@@ -43,6 +43,7 @@ import static org.keycloak.authentication.authenticators.x509.AbstractX509Client
 import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_ISSUERDN_CN;
 import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_ISSUERDN_EMAIL;
 import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SERIALNUMBER;
+import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL;
 import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN;
 import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_CN;
 import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL;
@@ -68,6 +69,7 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
     private static final String[] mappingSources = {
             MAPPING_SOURCE_CERT_SUBJECTDN,
             MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL,
+            MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL,
             MAPPING_SOURCE_CERT_SUBJECTDN_CN,
             MAPPING_SOURCE_CERT_ISSUERDN,
             MAPPING_SOURCE_CERT_ISSUERDN_EMAIL,
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityExtractor.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityExtractor.java
index ef29aec..881fdb7 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityExtractor.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityExtractor.java
@@ -25,7 +25,11 @@ import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x500.style.IETFUtils;
 import org.keycloak.services.ServicesLogger;
 
+import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
 import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -92,6 +96,52 @@ public abstract class UserIdentityExtractor {
         }
     }
 
+    /**
+     * Extracts the subject identifier from the subjectAltName extension.
+     */
+    static class SubjectAltNameExtractor extends UserIdentityExtractor {
+
+        private final int generalName;
+
+        /**
+         * Creates a new instance
+         *
+         * @param generalName an integer representing the general name. See {@link X509Certificate#getSubjectAlternativeNames()}
+         */
+        SubjectAltNameExtractor(int generalName) {
+            this.generalName = generalName;
+        }
+
+        @Override
+        public Object extractUserIdentity(X509Certificate[] certs) {
+            if (certs == null || certs.length == 0) {
+                throw new IllegalArgumentException();
+            }
+
+            try {
+                Collection<List<?>> subjectAlternativeNames = certs[0].getSubjectAlternativeNames();
+
+                if (subjectAlternativeNames == null) {
+                    return null;
+                }
+
+                Iterator<List<?>> iterator = subjectAlternativeNames.iterator();
+
+                while (iterator.hasNext()) {
+                    List<?> next = iterator.next();
+
+                    if (Integer.class.cast(next.get(0)) == generalName) {
+                        return next.get(1);
+                    }
+                }
+            } catch (CertificateParsingException cause) {
+                logger.errorf(cause, "Failed to obtain identity from subjectAltName extension");
+            }
+
+            return null;
+        }
+    }
+
     static class PatternMatcher extends UserIdentityExtractor {
         private final String _pattern;
         private final Function<X509Certificate[],String> _f;
@@ -143,6 +193,16 @@ public abstract class UserIdentityExtractor {
         return new X500NameRDNExtractor(identifier, x500Name);
     }
 
+    /**
+     * Obtains the subjectAltName given a <code>generalName</code>.
+     *
+     * @param generalName an integer representing the general name. See {@link X509Certificate#getSubjectAlternativeNames()}
+     * @return the value from the subjectAltName extension
+     */
+    public static SubjectAltNameExtractor getSubjectAltNameExtractor(int generalName) {
+        return new SubjectAltNameExtractor(generalName);
+    }
+
     public static OrBuilder either(UserIdentityExtractor extractor) {
         return new OrBuilder(extractor);
     }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509AuthenticatorConfigModel.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509AuthenticatorConfigModel.java
index 87c60b4..a738f42 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509AuthenticatorConfigModel.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509AuthenticatorConfigModel.java
@@ -60,6 +60,7 @@ public class X509AuthenticatorConfigModel extends AuthenticatorConfigModel {
         ISSUERDN(MAPPING_SOURCE_CERT_ISSUERDN),
         SUBJECTDN_CN(MAPPING_SOURCE_CERT_SUBJECTDN_CN),
         SUBJECTDN_EMAIL(MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL),
+        SUBJECTALTNAME_EMAIL(MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL),
         SUBJECTDN(MAPPING_SOURCE_CERT_SUBJECTDN);
 
         private String name;