Estoy usando SslServerSocket
certificados de cliente y y quiero extraer el CN del SubjectDN del cliente X509Certificate
.
Por el momento llamo, cert.getSubjectX500Principal().getName()
pero esto, por supuesto, me da el DN total formateado del cliente. Por alguna razón, solo me interesa la CN=theclient
parte del DN. ¿Hay alguna forma de extraer esta parte del DN sin analizar la cadena yo mismo?
java
ssl
x509certificate
x509
Martín C.
fuente
fuente
Respuestas:
Aquí hay algo de código para la nueva API BouncyCastle no obsoleta. Necesitará 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());
fuente
IETFUtils.valueToString
no parece producir un resultado correcto. Tengo un CN que incluye algunos signos iguales debido a la codificación de base 64 (por ejemploAAECAwQFBgcICQoLDA0ODw==
). ElvalueToString
método agrega barras diagonales inversas al resultado. En lugar de eso, el usotoString
parece estar funcionando. Es difícil determinar si este es de hecho un uso correcto de la API.aquí hay otra forma. la idea es que el DN que obtenga esté en formato rfc2253, que es el mismo que se usa para LDAP DN. Entonces, ¿por qué no reutilizar la API 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()); }
fuente
String commonName = new LdapName(certificate.getSubjectX500Principal().getName()).getRdns().stream() .filter(i -> i.getType().equalsIgnoreCase("CN")).findFirst().get().getValue().toString();
CN
(aka2.5.4.3
)Rdn#getValue()
contiene unString
. Sin embargo, para los tipos personalizados, el resultado esbyte[]
(quizás basado en una representación codificada interna que comienza con#
). Ofc,byte[]
->String
es posible, pero contiene caracteres adicionales (impredecibles). Resolví esto con las soluciones @laz basadas en BC, porque maneja y decodifica esto correctamente enString
.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 forma de hacerlo. Sin embargo, como menciona gtrak en los comentarios, este enfoque ahora está en desuso. Vea el código actualizado de gtrak que usa la nueva API de Bouncy Castle.
fuente
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 usado su solución hasta que mi SW tuvo que ejecutarse en Android. Y Android no implementa javax.naming.ldap :-(
fuente
X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName()); String cn = x500Name.getCommonName();
(usando java 8)IETFUtils.valueToString
devuelve el valor en escapado formulario. Encontré que simplemente invocar.toString()
funcionaba para mí.Una línea con http://www.cryptacular.org
JavaDoc: http://www.cryptacular.org/javadocs/org/cryptacular/util/CertUtil.html#subjectCN(java.security.cert.X509Certificate)
Dependencia de Maven:
<dependency> <groupId>org.cryptacular</groupId> <artifactId>cryptacular</artifactId> <version>1.1.0</version> </dependency>
fuente
Todas las respuestas publicadas hasta ahora tienen algún problema: la mayoría usa la
X500Name
dependencia interna o externa de Bounty Castle. Lo siguiente se basa en la respuesta de @ Jakub y usa solo la API pública de JDK, pero también extrae el CN como lo solicita el OP. También usa Java 8, que estaba a mediados de 2017, realmente debería hacerlo.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(", "))
fuente
A continuación, le mostramos cómo hacerlo usando una expresión regular
cert.getSubjectX500Principal().getName()
, en caso de que no desee tomar una dependencia en BouncyCastle.Esta expresión regular analizará un nombre distinguido, dando
name
yval
capturando grupos para cada coincidencia.Cuando las cadenas DN contienen comas, deben estar entrecomilladas: esta expresión regular maneja correctamente cadenas entre comillas y sin comillas, y también maneja comillas de escape en cadenas entre comillas:
(?:^|,\s?)(?:(?<name>[A-Z]+)=(?<val>"(?:[^"]|"")+"|[^,]+))+
Aquí está muy bien formateado:
(?:^|,\s?) (?: (?<name>[A-Z]+)= (?<val>"(?:[^"]|"")+"|[^,]+) )+
Aquí tienes un enlace para que puedas verlo en acción: https://regex101.com/r/zfZX3f/2
Si desea que una expresión regular obtenga solo el CN, esta versión adaptada lo hará:
(?:^|,\s?)(?:CN=(?<val>"(?:[^"]|"")+"|[^,]+))
fuente
Tengo BouncyCastle 1.49, y la clase que tiene ahora es org.bouncycastle.asn1.x509.Certificate. Miré el código de
IETFUtils.valueToString()
- está haciendo un escape elegante con barras invertidas. Para un nombre de dominio no sería nada malo, pero creo que podemos hacerlo mejor. En los casos que he visto,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í esCertificate c = ...; RDN cn = c.getSubject().getRDNs(BCStyle.CN)[0]; return ((ASN1String)cn.getFirst().getValue()).getString();
fuente
ACTUALIZACIÓN: Esta clase está en el paquete "sun" y debe usarla con precaución. Gracias Emil por el comentario :)
Solo quería compartir, para obtener el CN, lo hago:
Con respecto al comentario de Emil Lundberg, consulte: Por qué los desarrolladores no deberían escribir programas que llamen paquetes 'sun'
fuente
X500Name
ser una API patentada interna que puede eliminarse en versiones futuras.De hecho, gracias a
gtrak
que parece que para obtener el certificado del cliente y extraer el CN, esto 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;
fuente
Podría usar cryptacular, que es una biblioteca criptográfica de Java construida sobre bouncycastle para un uso fácil.
RDNSequence dn = new NameReader(cert).readSubject(); return dn.getValue(StandardAttributeType.CommonName);
fuente
Puede intentar usar getName (X500Principal.RFC2253, oidMap) o
getName(X500Principal.CANONICAL, oidMap)
ver cuál formatea mejor la cadena DN. Quizás uno de losoidMap
valores del mapa sea la cadena que desea.fuente
Obtener 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();
fuente
Una forma más de hacerlo con Java simple:
public static String getCommonName(X509Certificate certificate) { String name = certificate.getSubjectX500Principal().getName(); int start = name.indexOf("CN="); int end = name.indexOf(",", start); if (end == -1) { end = name.length(); } return name.substring(start + 3, end); }
fuente
Las expresiones Regex son bastante caras de usar. Para una tarea tan simple, probablemente será una muerte excesiva. En su lugar, podría usar una división de cadena simple:
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 ""; }
fuente
\,
o valores entre comillas.X500Name es una implementación interna de JDK, sin embargo, puede utilizar 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); }
fuente
BC facilitó mucho la extracción:
X500Principal principal = x509Certificate.getSubjectX500Principal(); X500Name x500name = new X500Name(principal.getName()); String cn = x500name.getCommonName();
fuente
.getCommonName()
método en X500Name .sun.security.x509.X500Name
, que, como se señaló en otras respuestas varios años antes, no está documentado y no se puede confiar en él?org.bouncycastle.asn1.x500.X500Name
clase, que no muestra ese método…Para atributos de varios valores, utilizando la API LDAP ...
X509Certificate testCertificate = .... X500Principal principal = testCertificate.getSubjectX500Principal(); // return subject DN String dn = null; if (principal != null) { String value = principal.getName(); // return String representation of DN in RFC 2253 if (value != null && value.length() > 0) { dn = value; } } if (dn != null) { LdapName ldapDN = new LdapName(dn); for (Rdn rdn : ldapDN.getRdns()) { Attributes attributes = rdn != null ? rdn.toAttributes() : null; Attribute attribute = attributes != null ? attributes.get("CN") : null; if (attribute != null) { NamingEnumeration<?> values = attribute.getAll(); while (values != null && values.hasMoreElements()) { Object o = values.next(); if (o != null && o instanceof String) { String cnValue = (String) o; } } } } }
fuente