uso ¿Cómo extraer CN de X509Certificate en Java?




uso de border en java (13)

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();

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?


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;

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());
}

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());

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 "";
}

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?)(?:(?<name>[AZ]+)=(?<val>"(?:[^"]|"")+"|[^,]+))+

Aquí está muy bien formateado:

(?:^|,\s?)
(?:
    (?<name>[A-Z]+)=
    (?<val>"(?:[^"]|"")+"|[^,]+)
)+

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=(?<val>"(?:[^"]|"")+"|[^,]+))


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();

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.



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);

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 :-(


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);
}

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.







x509