¿Cómo extraer CN de X509Certificate en Java?

Estoy utilizando un SslServerSocket y certificados de cliente y quiero extraer el CN ​​del SubjectDN del X509Certificate del cliente.

En este momento llamo a cert.getSubjectX500Principal().getName() pero esto, por supuesto, me da el DN formateado total del cliente. Por alguna razón, solo estoy interesado en la parte CN=theclient del DN. ¿Hay alguna forma de extraer esta parte del DN sin analizar el String por mí mismo?

Aquí hay un código para la nueva API BouncyCastle no desaprobada. Necesitará las distribuciones bcmail y bcprov.

 X509Certificate cert = ...; X500Name x500name = new JcaX509CertificateHolder(cert).getSubject(); RDN cn = x500name.getRDNs(BCStyle.CN)[0]; return IETFUtils.valueToString(cn.getFirst().getValue()); 

aquí hay otra manera. la idea es que el DN que obtenga esté en formato rfc2253, que es el mismo que el utilizado para LDAP DN. Entonces, ¿por qué no reutilizar la API de LDAP?

 import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; String dn = x509cert.getSubjectX500Principal().getName(); LdapName ldapDN = new LdapName(dn); for(Rdn rdn: ldapDN.getRdns()) { System.out.println(rdn.getType() + " -> " + rdn.getValue()); } 

Si agregar dependencias no es un problema, puede hacerlo con la API de Bouncy Castle para trabajar con certificados X.509:

 import org.bouncycastle.asn1.x509.X509Name; import org.bouncycastle.jce.PrincipalUtil; import org.bouncycastle.jce.X509Principal; ... final X509Principal principal = PrincipalUtil.getSubjectX509Principal(cert); final Vector values = principal.getValues(X509Name.CN); final String cn = (String) values.get(0); 

Actualizar

En el momento de esta publicación, esta era la manera de hacer esto. Sin embargo, como gtrak menciona en los comentarios, este enfoque ahora está en desuso. Vea el código actualizado de gtrak que usa la nueva API de Bouncy Castle.

Como alternativa al código de gtrak que no necesita ” bcmail ”:

  X509Certificate cert = ...; X500Principal principal = cert.getSubjectX500Principal(); X500Name x500name = new X500Name( principal.getName() ); RDN cn = x500name.getRDNs(BCStyle.CN)[0]); return IETFUtils.valueToString(cn.getFirst().getValue()); 

@Jakub: He utilizado su solución hasta que mi SW tuvo que ejecutarse en Android. Y Android no implementa javax.naming.ldap 🙁

Una línea con http://www.cryptacular.org

 CertUtil.subjectCN(certificate); 

JavaDoc: http://www.cryptacular.org/javadocs/org/cryptacular/util/CertUtil.html#subjectCN(java.security.cert.X509Certificate)

Dependencia de Maven:

  org.cryptacular cryptacular 1.1.0  

Tengo BouncyCastle 1.49, y la clase que tiene ahora es org.bouncycastle.asn1.x509.Certificate. Busqué en el código de IETFUtils.valueToString() – está haciendo un escape elegante con barras invertidas. Para un nombre de dominio no haría nada malo, pero creo que podemos hacerlo mejor. En los casos que he visto en cn.getFirst().getValue() devuelve diferentes tipos de cadenas que implementan la interfaz ASN1String, que está ahí para proporcionar un método getString (). Entonces, lo que parece funcionar para mí es

 Certificate c = ...; RDN cn = c.getSubject().getRDNs(BCStyle.CN)[0]; return ((ASN1String)cn.getFirst().getValue()).getString(); 

Todas las respuestas publicadas hasta ahora tienen algún problema: la mayoría usa el nombre de X500Name interno o la dependencia de Bounty Castle externa. A continuación, se basa en la respuesta de @Jakub y utiliza solo la API pública de JDK, pero también extrae el CN ​​según lo solicitado por el OP. También usa Java 8, que a mediados de 2017, realmente deberías.

 Stream.of(certificate) .map(cert -> cert.getSubjectX500Principal().getName()) .flatMap(name -> { try { return new LdapName(name).getRdns().stream() .filter(rdn -> rdn.getType().equalsIgnoreCase("cn")) .map(rdn -> rdn.getValue().toString()); } catch (InvalidNameException e) { log.warn("Failed to get certificate CN.", e); return Stream.empty(); } }) .collect(joining(", ")) 

De hecho, gracias a gtrak parece que para obtener el certificado del cliente y extraer el CN, probablemente funcione.

  X509Certificate[] certs = (X509Certificate[]) httpServletRequest .getAttribute("javax.servlet.request.X509Certificate"); X509Certificate cert = certs[0]; X509CertificateHolder x509CertificateHolder = new X509CertificateHolder(cert.getEncoded()); X500Name x500Name = x509CertificateHolder.getSubject(); RDN[] rdns = x500Name.getRDNs(BCStyle.CN); RDN rdn = rdns[0]; String name = IETFUtils.valueToString(rdn.getFirst().getValue()); return name; 

Podría usar cryptacular, que es una biblioteca criptográfica de Java construida sobre bouncycastle para facilitar su uso.

 RDNSequence dn = new NameReader(cert).readSubject(); return dn.getValue(StandardAttributeType.CommonName); 

ACTUALIZACIÓN: Esta clase está en el paquete “sol” y debe usarla con precaución. Gracias Emil por el comentario 🙂

Solo quería compartir, para obtener el CN, lo hago:

 X500Name.asX500Name(cert.getSubjectX500Principal()).getCommonName(); 

Con respecto al comentario de Emil Lundberg, vea: ¿Por qué los desarrolladores no deben escribir progtwigs que llamen paquetes de ‘sol’?

La obtención de CN del certificado no es tan simple. El siguiente código definitivamente te ayudará.

 String certificateURL = "C://XYZ.cer"; //just pass location CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate testCertificate = (X509Certificate)cf.generateCertificate(new FileInputStream(certificateURL)); String certificateName = X500Name.asX500Name((new X509CertImpl(testCertificate.getEncoded()).getSubjectX500Principal())).getCommonName(); 

A continuación, le mostramos cómo hacerlo usando una expresión regular sobre cert.getSubjectX500Principal().getName() , en caso de que no quiera tomar una dependencia en BouncyCastle.

Esta expresión regular analizará un nombre completo, dando name y val a los grupos de captura para cada coincidencia.

Cuando las cadenas de DN contienen comas, están destinadas a ser citadas: esta expresión regular maneja correctamente las cadenas entrecomilladas y sin comillas, y también maneja las comillas escapadas en las cadenas entrecomilladas:

(?:^|,\s?)(?:(?[AZ]+)=(?"(?:[^"]|"")+"|[^,]+))+

Aquí está muy bien formateado:

 (?:^|,\s?) (?: (?[AZ]+)= (?"(?:[^"]|"")+"|[^,]+) )+ 

Aquí hay un enlace para que pueda verlo en acción: https://regex101.com/r/zfZX3f/2

Si desea que una expresión regular obtenga solo el CN, entonces esta versión adaptada lo hará:

(?:^|,\s?)(?:CN=(?"(?:[^"]|"")+"|[^,]+))

X500Name es una implementación interna de JDK; sin embargo, puede usar la reflexión.

 public String getCN(String formatedDN) throws Exception{ Class x500NameClzz = Class.forName("sun.security.x509.X500Name"); Constructor constructor = x500NameClzz.getConstructor(String.class); Object x500NameInst = constructor.newInstance(formatedDN); Method method = x500NameClzz.getMethod("getCommonName", null); return (String)method.invoke(x500NameInst, null); } 

Podría intentar usar getName (X500Principal.RFC2253, oidMap) o getName(X500Principal.CANONICAL, oidMap) para ver cuál formatea mejor la cadena de DN. Tal vez uno de los valores del mapa de oidMap será la cadena que desee.

BC hizo la extracción mucho más fácil:

 X500Principal principal = x509Certificate.getSubjectX500Principal(); X500Name x500name = new X500Name(principal.getName()); String cn = x500name.getCommonName(); 

Expresiones Regex, son bastante caros de usar. Para una tarea tan simple, probablemente sea una muerte excesiva. En cambio, podrías usar una simple secuencia de cadenas:

 String dn = ((X509Certificate) certificate).getIssuerDN().getName(); String CN = getValByAttributeTypeFromIssuerDN(dn,"CN="); private String getValByAttributeTypeFromIssuerDN(String dn, String attributeType) { String[] dnSplits = dn.split(","); for (String dnSplit : dnSplits) { if (dnSplit.contains(attributeType)) { String[] cnSplits = dnSplit.trim().split("="); if(cnSplits[1]!= null) { return cnSplits[1].trim(); } } } return ""; }