Martin Carpella
Martin Carpella

Reputation: 12573

How to extract CN from X509Certificate in Java?

I am using a SslServerSocket and client certificates and want to extract the CN from the SubjectDN from the client's X509Certificate.

At the moment I call cert.getSubjectX500Principal().getName() but this of course gives me the total formatted DN of the client. For some reason I am just interested in the CN=theclient part of the DN. Is there a way to extract this part of the DN without parsing the String myself?

Upvotes: 113

Views: 122638

Answers (22)

pieroxy
pieroxy

Reputation: 859

In java 17 you have a method giving out the names: getSubjectAlternativeNames and getSubjectX500Principal for the principal name.

The javadoc says it's there since Java 1.4, but I could not find it in the java 8 documentation, so I don't know when it was added.

To get the principal name:

System.out.println(cer.getSubjectX500Principal().getName())

If there are more than one domain name:

System.out.println(cer.getSubjectAlternativeNames().stream().filter(l->l.get(0).equals(2)).map(l -> String.valueOf(l.get(1))).collect(Collectors.joining(",")))

Reference: Javadoc

Upvotes: 1

suside
suside

Reputation: 695

If you want to use API 100% dedicated to do this (especially if you have more "advanced" cert) you could use eu.europa.esig.dss like so:

val x509Certificate = X509CertUtils.parse(certPem)
val certToken = CertificateToken(x509Certificate)

val commonName = DSSASN1Utils.extractAttributeFromX500Principal(
    ASN1ObjectIdentifier(X520Attributes.COMMONNAME.oid),
    X500PrincipalHelper(
        x509Certificate.subjectX500Principal
    )
)

The advantage here is that X520Attributes class "knows" not only commonName but pretty much every possible attribute allowed by spec like organizationIdentifier, encryptedBusinessCategory and so on (currently 239).

esig.dss lib can also extract cert extensions and many other things. For example PSD2 roles:

CertificateExtensionsUtils.getQcStatements(certToken).psd2QcType.rolesOfPSP

Upvotes: 1

Gopi Neelam
Gopi Neelam

Reputation: 61

Get the common name of the certificate Without using any library. with using regular expression

To get the name

String name = x509Certificate.getSubjectDN().getName();

to get the extract the common name from the full name

    String name = "CN=Go Daddy Root Certificate Authority - G2, O=\"GoDaddy.com, Inc.\", L=Scottsdale, ST=Arizona, C=US";
    Pattern pattern = Pattern.compile("CN=(.*?)(?:,|\$)");
    Matcher matcher = pattern.matcher(name);
    if (matcher.find()) {
        System.out.println(matcher.group(1));
    }

Hope this helps anyone.(-_-)

Upvotes: 6

MastaP
MastaP

Reputation: 81

With Spring Security it is possible to use SubjectDnX509PrincipalExtractor:

X509Certificate certificate = ...;
new SubjectDnX509PrincipalExtractor().extractPrincipal(certificate).toString();

Upvotes: 2

barth
barth

Reputation: 441

One more way to do with plain Java :

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

Upvotes: 2

TodayGuessWhat
TodayGuessWhat

Reputation: 61

For multi-valued attributes - using LDAP API ...

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

Upvotes: 0

Cocowalla
Cocowalla

Reputation: 14331

Here's how to do it using a regex over cert.getSubjectX500Principal().getName(), in case you don't want to take a dependency on BouncyCastle.

This regex will parse a distinguished name, giving name and val a capture groups for each match.

When DN strings contain commas, they are meant to be quoted - this regex correctly handles both quoted and unquotes strings, and also handles escaped quotes in quoted strings:

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

Here is is nicely formatted:

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

Here's a link so you can see it in action: https://regex101.com/r/zfZX3f/2

If you want a regex to get only the CN, then this adapted version will do it:

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

Upvotes: 7

s1m0nw1
s1m0nw1

Reputation: 81859

BC made the extraction much easier:

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

Upvotes: 0

Abhijit Sarkar
Abhijit Sarkar

Reputation: 24518

All the answers posted so far have some issue: Most use the internal X500Name or external Bounty Castle dependency. The following builds on @Jakub's answer and uses only public JDK API, but also extracts the CN as asked for by the OP. It also uses Java 8, which standing in mid-2017, you really should.

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

Upvotes: 12

vinayaka cn
vinayaka cn

Reputation: 11

Fetching CN from certificate is not that simple. The below code will definitely help you.

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

Upvotes: 1

Gilbert Le Blanc
Gilbert Le Blanc

Reputation: 51445

You could try using getName(X500Principal.RFC2253, oidMap) or getName(X500Principal.CANONICAL, oidMap) to see which one formats the DN string best. Maybe one of the oidMap map values will be the string you want.

Upvotes: 0

bro.xian
bro.xian

Reputation: 9

X500Name is internal implemention of JDK, however you can use reflection.

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

Upvotes: 0

Erdem Memisyazici
Erdem Memisyazici

Reputation: 81

One line with 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>

Upvotes: 8

Rad
Rad

Reputation: 4982

UPDATE: This class is in "sun" package and you should use it with caution. Thanks Emil for the comment :)

Just wanted to share, to get the CN, I do:

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

Regarding Emil Lundberg's comment see: Why Developers Should Not Write Programs That Call 'sun' Packages

Upvotes: 4

Ghetolay
Ghetolay

Reputation: 3342

Could use cryptacular which is a Java cryptographic library build on top of bouncycastle for easy use.

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

Upvotes: 1

EpicPandaForce
EpicPandaForce

Reputation: 81529

Indeed, thanks to gtrak it appears that to get the client certificate and extract the CN, this most likely works.

    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;

Upvotes: 2

AivarsDa
AivarsDa

Reputation: 346

Regex expressions, are rather expensive to use. For such a simple task it will probably be an over kill. Instead you could use a simple String 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 "";
}

Upvotes: 0

G L
G L

Reputation: 31

I have BouncyCastle 1.49, and the class it has now is org.bouncycastle.asn1.x509.Certificate. I looked into the code of IETFUtils.valueToString() - it is doing some fancy escaping with backslashes. For a domain name it would not do anything bad, but I feel we can do better. In the cases I've look at cn.getFirst().getValue() returns different kinds of strings that all implement ASN1String interface, which is there to provide a getString() method. So, what seems to work for me is

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

Upvotes: 3

gtrak
gtrak

Reputation: 5676

Here's some code for the new non-deprecated BouncyCastle API. You'll need both bcmail and bcprov distributions.

X509Certificate cert = ...;

X500Name x500name = new JcaX509CertificateHolder(cert).getSubject();
RDN cn = x500name.getRDNs(BCStyle.CN)[0];

return IETFUtils.valueToString(cn.getFirst().getValue());

Upvotes: 112

laz
laz

Reputation: 28638

If adding dependencies isn't a problem you can do this with Bouncy Castle's API for working with X.509 certificates:

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

Update

At the time of this posting, this was the way to do this. As gtrak mentions in the comments however, this approach is now deprecated. See gtrak's updated code that uses the new Bouncy Castle API.

Upvotes: 14

Ivin
Ivin

Reputation: 1101

As an alternative to gtrak's code that does not need ''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: I have used your solution until my SW had to be run on Android. And Android does not implement javax.naming.ldap :-(

Upvotes: 12

Jakub
Jakub

Reputation: 2384

here is another way. the idea is that the DN you obtain is in rfc2253 format, which is the same as used for LDAP DN. So why not reuse the LDAP API?

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

Upvotes: 102

Related Questions