J'utilise SslServerSocket
des certificats client et et je souhaite extraire le CN du SubjectDN du client X509Certificate
.
Pour le moment, j'appelle cert.getSubjectX500Principal().getName()
mais cela me donne bien sûr le DN formaté total du client. Pour une raison quelconque, je suis simplement intéressé par la CN=theclient
partie du DN. Existe-t-il un moyen d'extraire cette partie du DN sans analyser moi-même la chaîne?
java
ssl
x509certificate
x509
Martin C.
la source
la source
Réponses:
Voici du code pour la nouvelle API BouncyCastle non obsolète. Vous aurez besoin des distributions bcmail et bcprov.
X509Certificate cert = ...; X500Name x500name = new JcaX509CertificateHolder(cert).getSubject(); RDN cn = x500name.getRDNs(BCStyle.CN)[0]; return IETFUtils.valueToString(cn.getFirst().getValue());
la source
IETFUtils.valueToString
ne semble pas produire un résultat correct. J'ai un CN qui inclut des signes égaux en raison du codage en base 64 (par exempleAAECAwQFBgcICQoLDA0ODw==
). LavalueToString
méthode ajoute des barres obliques inverses au résultat. Au lieu de cela, l'utilisationtoString
semble fonctionner. Il est difficile de déterminer qu'il s'agit en fait d'une utilisation correcte de l'API.voici une autre manière. l'idée est que le DN que vous obtenez est au format rfc2253, qui est le même que celui utilisé pour LDAP DN. Alors pourquoi ne pas réutiliser l'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()); }
la source
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()
contient unString
. Cependant, pour les types personnalisés, le résultat estbyte[]
(peut-être basé sur une représentation codée interne commençant par#
). Ofc,byte[]
->String
est possible, mais contient des caractères supplémentaires (imprévisibles). J'ai résolu cela avec les solutions @laz basées sur BC, car il gère et décode cela correctement dansString
.Si l'ajout de dépendances n'est pas un problème, vous pouvez le faire avec l' API de Bouncy Castle pour travailler avec les certificats 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);
Mise à jour
Au moment de cette publication, c'était la manière de procéder. Cependant, comme le mentionne gtrak dans les commentaires, cette approche est désormais obsolète. Consultez le code mis à jour de gtrak qui utilise la nouvelle API Bouncy Castle.
la source
Comme alternative au code de gtrak qui n'a pas besoin de `` 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: J'ai utilisé votre solution jusqu'à ce que mon logiciel soit exécuté sur Android. Et Android n'implémente pas javax.naming.ldap :-(
la source
X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName()); String cn = x500Name.getCommonName();
(en utilisant java 8)IETFUtils.valueToString
renvoie la valeur sous forme d' échappement . J'ai trouvé que l'invocation.toString()
fonctionnait simplement pour moi.Une ligne avec http://www.cryptacular.org
JavaDoc: http://www.cryptacular.org/javadocs/org/cryptacular/util/CertUtil.html#subjectCN(java.security.cert.X509Certificate)
Dépendance Maven:
<dependency> <groupId>org.cryptacular</groupId> <artifactId>cryptacular</artifactId> <version>1.1.0</version> </dependency>
la source
Toutes les réponses publiées jusqu'à présent ont un problème: la plupart utilisent la
X500Name
dépendance interne ou externe de Bounty Castle. Ce qui suit s'appuie sur la réponse de @ Jakub et utilise uniquement l'API JDK publique, mais extrait également le CN comme demandé par l'OP. Il utilise également Java 8, qui se tenait à la mi-2017, vous devriez vraiment.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(", "))
la source
Voici comment le faire en utilisant une regex over
cert.getSubjectX500Principal().getName()
, au cas où vous ne voudriez pas prendre une dépendance sur BouncyCastle.Cette regex analysera un nom distinctif, donnant
name
etval
un groupe de capture pour chaque match.Lorsque les chaînes DN contiennent des virgules, elles sont censées être entre guillemets - cette expression régulière gère correctement les chaînes entre guillemets et non, et gère également les guillemets échappés entre guillemets:
(?:^|,\s?)(?:(?<name>[A-Z]+)=(?<val>"(?:[^"]|"")+"|[^,]+))+
Voici bien formaté:
(?:^|,\s?) (?: (?<name>[A-Z]+)= (?<val>"(?:[^"]|"")+"|[^,]+) )+
Voici un lien pour que vous puissiez le voir en action: https://regex101.com/r/zfZX3f/2
Si vous voulez qu'une regex n'obtienne que le CN, alors cette version adaptée le fera:
(?:^|,\s?)(?:CN=(?<val>"(?:[^"]|"")+"|[^,]+))
la source
J'ai BouncyCastle 1.49, et la classe qu'il a maintenant est org.bouncycastle.asn1.x509.Certificate. J'ai regardé dans le code de
IETFUtils.valueToString()
- il fait un peu de fantaisie échapper avec des barres obliques inverses. Pour un nom de domaine, cela ne ferait rien de mal, mais je pense que nous pouvons faire mieux. Dans les cas que j'ai examinés,cn.getFirst().getValue()
renvoie différents types de chaînes qui implémentent toutes l'interface ASN1String, qui est là pour fournir une méthode getString (). Donc, ce qui semble fonctionner pour moi estCertificate c = ...; RDN cn = c.getSubject().getRDNs(BCStyle.CN)[0]; return ((ASN1String)cn.getFirst().getValue()).getString();
la source
MISE À JOUR: Cette classe est dans le package "sun" et vous devez l'utiliser avec prudence. Merci Emil pour le commentaire :)
Je voulais juste partager, pour obtenir le CN, je fais:
Concernant le commentaire d'Emil Lundberg, voir: Pourquoi les développeurs ne devraient pas écrire des programmes qui appellent des packages `` sun ''
la source
X500Name
qu'il s'agit d'une API propriétaire interne qui pourrait être supprimée dans les versions futures.En effet, grâce à
gtrak
il semble que pour obtenir le certificat client et extraire le CN, cela fonctionne très probablement.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;
la source
Pourrait utiliser cryptacular qui est une bibliothèque cryptographique Java construite au-dessus de bouncycastle pour une utilisation facile.
RDNSequence dn = new NameReader(cert).readSubject(); return dn.getValue(StandardAttributeType.CommonName);
la source
Vous pouvez essayer d'utiliser getName (X500Principal.RFC2253, oidMap) ou
getName(X500Principal.CANONICAL, oidMap)
pour voir lequel formate le mieux la chaîne DN. Peut-être que l'une desoidMap
valeurs de la carte sera la chaîne souhaitée.la source
Récupérer CN à partir d'un certificat n'est pas aussi simple. Le code ci-dessous vous aidera certainement.
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();
la source
Une autre façon de faire avec Java brut:
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); }
la source
Les expressions Regex sont plutôt coûteuses à utiliser. Pour une tâche aussi simple, ce sera probablement un sur-kill. Au lieu de cela, vous pouvez utiliser un simple fractionnement de chaîne:
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 ""; }
la source
\,
ou des valeurs entre guillemets.X500Name est une implémentation interne de JDK, mais vous pouvez utiliser la réflexion.
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); }
la source
BC a rendu l'extraction beaucoup plus facile:
X500Principal principal = x509Certificate.getSubjectX500Principal(); X500Name x500name = new X500Name(principal.getName()); String cn = x500name.getCommonName();
la source
.getCommonName()
méthode dans X500Name .sun.security.x509.X500Name
- ce qui, comme d'autres réponses notées plusieurs années plus tôt, n'est pas documenté et ne peut pas être invoqué?org.bouncycastle.asn1.x500.X500Name
classe, qui ne montre pas cette méthode…Pour les attributs à valeurs multiples - à l'aide de l'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; } } } } }
la source