web-dev-qa-db-fra.com

Comment extraire CN de X509Certificate en Java?

J'utilise un SslServerSocket et des certificats client et je souhaite extraire le CN du SubjectDN du X509Certificate Du client.

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 partie CN=theclient Du DN. Existe-t-il un moyen d'extraire cette partie du DN sans analyser moi-même la chaîne?

80
Martin C.

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

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 le DN LDAP. 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());
}
90
Jakub

Si l'ajout de dépendances n'est pas un problème, vous pouvez le faire avec l'API Bouncy Castle's 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 façon de procéder. Cependant, comme le mentionne gtrak dans les commentaires, cette approche est désormais obsolète. Voir gtrak's code mis à jour qui utilise la nouvelle API Bouncy Castle.

12
laz

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

9
Ivin

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)

Dépendance Maven:

<dependency>
    <groupId>org.cryptacular</groupId>
    <artifactId>cryptacular</artifactId>
    <version>1.1.0</version>
</dependency>
6
Erdem Memisyazici

Toutes les réponses publiées jusqu'à présent ont un problème: la plupart utilisent la dépendance interne X500Name 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 à la mi-2017, vous devriez vraiment le faire.

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(", "))
5
Abhijit Sarkar

J'ai BouncyCastle 1.49, et la classe qu'il a maintenant est org.bouncycastle.asn1.x509.Certificate. J'ai regardé le code de IETFUtils.valueToString() - il s'échappe 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, c'est

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

Voici comment le faire en utilisant une expression régulière sur cert.getSubjectX500Principal().getName(), au cas où vous ne voudriez pas prendre une dépendance sur BouncyCastle.

Cette expression régulière analysera un nom distinctif, donnant à name et val un groupe de capture pour chaque correspondance.

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 sans guillemets, et gère également les guillemets échappés dans les chaînes entre guillemets:

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

Voici est 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 obtienne seulement le CN, alors cette version adaptée le fera:

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

2
Cocowalla

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, 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 de programmes qui appellent des packages 'Sun'

1
Rad

La récupération du CN à partir 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
vinayaka cn

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
EpicPandaForce

Pourrait utiliser cryptacular qui est une bibliothèque de chiffrement Java Java au-dessus de bouncycastle pour une utilisation facile.

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

Les expressions regex sont assez chères à utiliser. Pour une tâche aussi simple, ce sera probablement une tuerie excessive. 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 "";
}
0
AivarsDa

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 des valeurs de mappage oidMap sera la chaîne souhaitée.

0
Gilbert Le Blanc

BC a rendu l'extraction beaucoup plus facile:

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

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;
                        }
                    }
                }
            }
        }
0
TodayGuessWhat

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);
}
0
bro.xian