110

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?

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
Martin C.
  • 12,140
  • 7
  • 40
  • 52

21 Answers21

105

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());
Elliot Vargas
  • 20,499
  • 11
  • 34
  • 36
gtrak
  • 5,598
  • 4
  • 32
  • 41
  • 11
    @grak, I'm interested in how you figured out this solution. Certainly just from looking at the API documentation I was never gonna be able to figure this out. – Elliot Vargas Aug 24 '12 at 19:34
  • 8
    yea, I share that sentiment... I had to ask on the mailing list. – gtrak Aug 29 '12 at 19:38
  • 7
    Note that this code on current (Oct 23 2012) BouncyCastle (1.47) also requires bcpkix distribution. – EwyynTomato Oct 23 '12 at 07:18
  • 2
    A certificate can have multiple CNs. Instead of just returning cn.getFirst() you should iterate through all and return a list of CNs. – varrunr May 08 '14 at 01:24
  • Nice find on the `IETFUtils`, that is not obvious at all! – EpicPandaForce Nov 10 '14 at 09:51
  • 6
    The `IETFUtils.valueToString` does not appear to produce a correct result. I have a CN that includes some equals signs because of base 64 encoding (e.g. `AAECAwQFBgcICQoLDA0ODw==`). The `valueToString` method adds back slashes to the result. In lieu of that, using `toString` seems to be working. It's difficult to determine that this is in fact a correct usage of the api. – Chris May 05 '15 at 18:59
  • I think it is better to check `if (0 != x500name.getRDNs(BCStyle.CN).length)`. – Yang Aug 10 '15 at 23:19
  • please have a sight at my question: http://stackoverflow.com/questions/40613147/how-to-get-the-policy-identifier-and-the-subject-type-of-basic-constraints-in-a – Hosein Aqajani Nov 17 '16 at 11:05
101

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());
}
Jakub
  • 2,344
  • 2
  • 21
  • 20
  • 1
    One useful shortcut if you're using spring : LdapUtils.getStringValue(ldapDN, "cn"); – Berthier Lemieux Aug 16 '16 at 05:48
  • At least for the case I'm working on the CN is *within* a multi-attribute RDN. In other words: the proposed solution doesn't iterate over attributes of the RDN. It should! – peterh Sep 09 '18 at 09:39
  • 1
    `String commonName = new LdapName(certificate.getSubjectX500Principal().getName()).getRdns().stream() .filter(i -> i.getType().equalsIgnoreCase("CN")).findFirst().get().getValue().toString();` – Reto Höhener May 08 '19 at 02:20
  • 2
    Note: Although it looks like a fine solution, it has some issues. I was using this one for some years until I discovered decoding issues with "non standard" fields. For fields with types like well known types like `CN` (aka `2.5.4.3`) `Rdn#getValue()` contains a `String`. However, for custom types, the result is `byte[]` (maybe based on an internal encoded representation starting with `#`). Ofc, `byte[]` -> `String` is possible, but contains additional (unpredictable) characters. I have solved this with @laz solutions based on BC, because it handles and decodes this correctly in `String`. – knalli Aug 19 '20 at 15:15
14

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.

Community
  • 1
  • 1
laz
  • 28,320
  • 5
  • 53
  • 50
  • 1
    it seems like X509Name is deprecated in Bouncycastle 1.46, and they intend to use x500Name. Know anything about that or the intended alternative to do the same thing? – gtrak Feb 28 '11 at 15:40
  • Wow, looking at the new API I'm having a hard time figuring out how to accomplish the same goal as the above code. Perhaps the Bouncycastle mailing list archives might have an answer. I'll update this answer if I figure it out. – laz Mar 01 '11 at 03:55
  • I'm having the same problem. Please let me know if you come up with anything. This is as far as I've gotten: x500name = X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(cert)); RDN cn = x500name.getRDNs(BCStyle.CN)[0]; – gtrak Apr 01 '11 at 15:25
  • I found how to do it via a mailing list discussion, I created an answer that shows how. – gtrak Apr 03 '11 at 02:23
  • Good find gtrak. I spent 10 minutes trying to figure it out at one point and never got back around to it. – laz Feb 13 '12 at 22:54
  • I had to get help for that one – gtrak Feb 13 '12 at 22:59
  • why does it have to be that nasty?? – Jaime Hablutzel May 14 '12 at 22:28
  • Have a look at gtrak's answer below. It is using the newer API which is much nicer – laz May 15 '12 at 03:14
  • please have a sight at my question: http://stackoverflow.com/questions/40613147/how-to-get-the-policy-identifier-and-the-subject-type-of-basic-constraints-in-a – Hosein Aqajani Nov 17 '16 at 11:08
12

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 :-(

Ivin
  • 1,081
  • 11
  • 21
  • 1
    That is exactly the same reason I cam up with this solution: porting to Android... – Ivin Jan 05 '12 at 09:07
  • 10
    Not sure when this changed, but this now works: `X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName()); String cn = x500Name.getCommonName();` (using java 8) – trichner Sep 22 '14 at 12:56
  • please have a sight at my question: http://stackoverflow.com/questions/40613147/how-to-get-the-policy-identifier-and-the-subject-type-of-basic-constraints-in-a – Hosein Aqajani Nov 17 '16 at 11:08
  • The `IETFUtils.valueToString` returns the value in **escaped** form. I found simply invoking `.toString()` instead work for me. – holmis83 May 04 '17 at 10:22
11

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(", "))
Abhijit Sarkar
  • 21,927
  • 20
  • 110
  • 219
  • In my case the CN is *within* a multi-attribute RDN. I think you'll need to enhance this solution so that for each RDN you would iterate over RDN attributes, rather then just looking at the first attribute of the RDN, which I think is what you are implicitly doing here. – peterh Sep 09 '18 at 09:42
8

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>
Lars Werkman
  • 2,528
  • 2
  • 20
  • 20
  • Note that the Cryptacular 1.1.x series is for Java 7 and 1.2.x for Java 8. Very good library, though! – Markus L Oct 20 '16 at 11:29
7

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>"(?:[^"]|"")+"|[^,]+))

Cocowalla
  • 13,822
  • 6
  • 66
  • 112
  • Most robust answer around. Also, if you want to support even OID's specified by its number (e.g. OID.2.5.4.97), allowed chars should be extended from [A-Z] to [A-Z,0-9,.] – yurislav Feb 05 '20 at 08:42
  • This seemed to struggle for me with an escaped comma. For example: "O=Acme Stuff\, and more" will parse out as "Acme Stuff\" – appmattus Apr 23 '23 at 13:16
5

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.(-_-)

Gopi Neelam
  • 51
  • 1
  • 1
4

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

Rad
  • 4,292
  • 8
  • 33
  • 71
  • 1
    This is my favourite among the current answers since it's simple, readable and uses only what's bundled in the JDK. – Emil Lundberg Sep 24 '15 at 09:22
  • Agree with what you said about using JDK classes :) – Rad Sep 25 '15 at 03:36
  • 4
    One should note, however, that javac warns about `X500Name` being an internal proprietary API that may be removed in future releases. – Emil Lundberg Oct 02 '15 at 11:59
  • Yeah, after reading the [linked FAQ](http://www.oracle.com/technetwork/java/faq-sun-packages-142232.html) I need to revoke my first comment. Sorry. – Emil Lundberg Oct 14 '15 at 08:10
  • 1
    No problem at all. What you pointed out is really important. Thanks :) In fact, I don't use that class any more :P – Rad Oct 15 '15 at 03:51
3

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();
G L
  • 31
  • 2
2

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;
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
2

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);
}
barth
  • 431
  • 2
  • 5
1

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);
Ghetolay
  • 3,222
  • 2
  • 30
  • 29
1

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();
user812786
  • 4,302
  • 5
  • 38
  • 50
1

BC made the extraction much easier:

X500Principal principal = x509Certificate.getSubjectX500Principal();
X500Name x500name = new X500Name(principal.getName());
String cn = x500name.getCommonName();
s1m0nw1
  • 76,759
  • 17
  • 167
  • 196
  • 1
    I can't find any `.getCommonName()` method in [X500Name](http://bouncycastle.org/docs/docs1.5on/org/bouncycastle/asn1/x500/X500Name.html). – lapo Mar 30 '18 at 09:35
  • 1
    (@lapo) are you sure you aren't actually using `sun.security.x509.X500Name` -- which as other answers noted several years earlier is undocumented and can't be relied on? – dave_thompson_085 Apr 12 '18 at 04:04
  • 1
    Well, I did link the JavaDoc of `org.bouncycastle.asn1.x500.X500Name` class, which doesn't show that method… – lapo Apr 19 '18 at 12:10
1

With Spring Security it is possible to use SubjectDnX509PrincipalExtractor:

X509Certificate certificate = ...;
new SubjectDnX509PrincipalExtractor().extractPrincipal(certificate).toString();
MastaP
  • 51
  • 1
  • 5
  • [SubjectDnX509PrincipalExtractor](https://docs.spring.io/spring-security/site/docs/5.6.2/api/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.html) uses a regular expression under the hood: `"CN=(.*?)(?:,|$)"`. – Gerardo Cauich May 04 '22 at 11:40
1

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
suside
  • 609
  • 8
  • 8
0

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 "";
}
AivarsDa
  • 326
  • 3
  • 7
  • I really like it! Platform and library independent. This is really cool! – user2007447 Nov 06 '14 at 23:29
  • 2
    Down-vote from me. If you read [RFC 2253](https://tools.ietf.org/html/rfc2253#section-3), you'll see there are edge cases you have to consider, e.g. escaped commas `\,` or quoted values. – Duncan Jones Dec 17 '14 at 13:09
0

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.

Community
  • 1
  • 1
Gilbert Le Blanc
  • 50,182
  • 6
  • 67
  • 111
0

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

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