Comment extraire CN à partir de X509Certificate en Java?

j'utilise un SslServerSocket et des certificats clients et je veux extraire le CN du sujet DN du X509Certificate du client .

au moment où j'appelle cert.getSubjectX500Principal().getName() mais cela me donne bien sûr le DN formaté total du client. Pour une raison quelconque, je suis juste intéressé par la partie CN=theclient de la DN. Y a-t-il un moyen d'extraire cette partie du DN sans analyser la chaîne moi-même?

69
demandé sur ROMANIA_engineer 2010-05-26 19:44:21

16 réponses

voici du code pour la nouvelle API Non-dépréciée de BouncyCastle. 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());
70
répondu gtrak 2012-11-03 18:47:58

voici une autre façon. 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());
}
81
répondu Jakub 2011-10-03 12:07:01

si ajouter des dépendances n'est pas un problème, vous pouvez le faire avec L'API 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 affectation, c'était la façon de faire. Toutefois, comme le mentionne grak dans les commentaires, cette approche est maintenant dépréciée. Voir le de gtrak qui utilise la nouvelle API de Château gonflable.

11
répondu laz 2017-05-23 12:02:23

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 SW doive être exécuté sur Android. Et Android n'implémente pas javax.nommer.ldap: - (

9
répondu Ivin 2011-11-30 08:20:35

une ligne avec http://www.cryptacular.org

CertUtil.subjectCN(certificate);

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

Maven dependency:

<dependency>
    <groupId>org.cryptacular</groupId>
    <artifactId>cryptacular</artifactId>
    <version>1.1.0</version>
</dependency>
5
répondu Erdem Memisyazici 2016-01-04 12:54:59

J'ai BouncyCastle 1.49, et la classe qu'il a maintenant est org.bouncycastle.asn1.x509.Certificat. J'ai regardé dans le code de IETFUtils.valueToString() - il fait un peu de fantaisie s'échapper avec des backslashs. Pour un nom de domaine, cela ne ferait rien de mal, mais je pense que nous pouvons faire mieux. Dans les cas où j'ai regardé 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 est

Certificate c = ...;
RDN cn = c.getSubject().getRDNs(BCStyle.CN)[0];
return ((ASN1String)cn.getFirst().getValue()).getString();
3
répondu G L 2013-09-17 20:39:58

toutes les réponses postées jusqu'à présent ont un problème: la plupart utilisent le X500Name interne ou la dépendance de Château de prime externe. 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 trouve à 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(", "))
3
répondu Abhijit Sarkar 2017-07-13 22:35:59

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;
1
répondu EpicPandaForce 2014-11-10 09:56:33

pourrait utiliser cryptacular qui est une bibliothèque cryptographique Java construit sur le dessus de bouncycastle pour une utilisation facile.

RDNSequence dn = new NameReader(cert).readSubject();
return dn.getValue(StandardAttributeType.CommonName);
1
répondu Ghetolay 2015-05-28 09:31:40

mise à jour: cette classe est dans l'emballage" sun " et vous devez l'utiliser avec prudence. Merci Emil pour le commentaire:)

je voulais Juste partager, pour obtenir le CN, je fais:

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

en ce qui concerne le commentaire D'Emil Lundberg, voir: pourquoi les développeurs ne devraient pas écrire des programmes qui appellent des paquets "sun

1
répondu Rad 2015-10-15 04:22:48

tirer le CN du certificat n'est pas si 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();
1
répondu vinayaka cn 2017-01-17 22:28:55

Voici comment le faire en utilisant un regex sur cert.getSubjectX500Principal().getName() , au cas où vous ne voulez pas prendre une dépendance sur BouncyCastle.

ce regex analysera un nom distingué, donnant name et val un groupe de capture pour chaque match.

lorsque les chaînes DN contiennent des virgules, elles sont destinées à être citées - ce regex gère correctement les chaînes citées et non-citées, et gère également les citations échappées dans les chaînes citées:

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

voici est joliment 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 un regex pour obtenir seulement le CN, alors cette version adaptée le fera:

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

1
répondu Cocowalla 2018-04-03 10:26:51

X500Name est une implémentation interne de JDK, cependant 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);
}
0
répondu bro.xian 2016-12-23 07:36:22

vous pouvez essayer d'utiliser getName(X500 principal.RFC2253, oidMap) ou getName(X500Principal.CANONICAL, oidMap) pour voir lequel formate le mieux la chaîne DN. Peut-être qu'une des valeurs de la carte oidMap sera la chaîne que vous voulez.

0
répondu Gilbert Le Blanc 2017-01-06 15:08:54

BC a rendu l'extraction beaucoup plus facile:

X500Principal principal = x509Certificate.getSubjectX500Principal();
X500Name x500name = new X500Name(principal.getName());
String cn = x500name.getCommonName();
0
répondu s1m0nw1 2017-11-09 12:31:55
Les expressions

Regex, sont assez chères à utiliser. Pour une tâche aussi simple, ce sera probablement une over-kill. À la place, vous pouvez utiliser une simple chaîne de caractères split:

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 "";
}
-1
répondu AivarsDa 2014-07-24 12:41:40