35

Let me begin by stating that this is a question of aesthetics. I've solved my own problem, I'm just curious about better ways of doing it.

So, I've got a certificate DN, something like this:

CN=Jimmy Blooptoop,OU=Someplace,OU=Employees,DC=Bloopsoft-Inc

Now, I want to grab the CN out of that. In java, there isn't native support to grab anything but the full DN from the X509 certificate without using some 3rd party library like bouncy castle - which I can't use. So I've got to parse it out, which isn't much of problem. The only thing that makes it slightly tricky is the fact that the CN isn't always going to be formatted as <first name> <last name>. More often than not, it's actually going to be <last name>, <first name> <middle initial>. So, in the example above, the CN could be Jimmy Blooptoop or Blooptoop, Jimmy J (short for the Joop of course).

After going and reading up about about regular expressions, I wrote the following, which works well enough:

Matcher m = Pattern.compile("CN=[A-Za-z]*[, ]*[ A-Za-z]*").matcher(dn);
if (m.find())
  cn = m.group();

I'm just curious if there are expressions that would look less like crap. I'm fairly confident that there are since I worked that out after reading just an introduction to regex.

Emil Lundberg
  • 7,268
  • 6
  • 37
  • 53
JDS
  • 1,233
  • 4
  • 15
  • 27

9 Answers9

72

How about javax.naming.ldap.LdapName?

String dn = "CN=Jimmy Blooptoop,OU=Someplace,OU=Employees,DC=Bloopsoft-Inc";
LdapName ln = new LdapName(dn);

for(Rdn rdn : ln.getRdns()) {
    if(rdn.getType().equalsIgnoreCase("CN")) {
        System.err.println("CN is: " + rdn.getValue());
        break;
    }
}

It's not the most beautiful interface since there is something missing like LdapName#getByType(String) but it saves you the trouble of having to think about what strange features DNs might have.

musiKk
  • 14,751
  • 4
  • 55
  • 82
  • 4
    Definitively a more solid solution than the regular expression one, using BouncyCastle API is another choice, see [how to extract cn from x509certificate in java](http://stackoverflow.com/questions/2914521/how-to-extract-cn-from-x509certificate-in-java) – jmd Nov 17 '11 at 15:56
  • 2
    Slightly better solution: for (Rdn rdn : ln.getRdns()) { ... } – gregschlom Jan 24 '13 at 23:27
  • @gregschlom: You are so right. I have no idea why I went with the old school loop. – musiKk Jan 25 '13 at 08:59
  • @musiKk http://stackoverflow.com/questions/18669041/how-to-extract-cn-from-x509certificate-in-java-without-using-bouncy-castle – likejudo Sep 07 '13 at 02:22
  • 1
    This approach has limitations with DNs containing multi-valued RDNs, e.g.: `OU=Sales+CN=J. Smith,O=Widget Inc.,C=US`. In that case you can use `Rdn.toAttributes().getAll()` and iterate over the returned enumeration. – Jaime Hablutzel Jan 17 '16 at 00:09
  • String cn = new X500Name(dn).getCommonName(); worked for me! – Prasath Rajan Feb 09 '22 at 16:13
9

You can use Spring Frameworks LdapUtils to extract CN in a neat way like below :

String cn = LdapUtils.getStringValue(new LdapName(group),"cn");

OR

Without using Spring Framework like below :

String cn = (String)new LdapName(group).getRdns().stream().filter(rdn -> rdn.getType().equalsIgnoreCase("CN")).findFirst().get().getValue();
Sahil Chhabra
  • 10,621
  • 4
  • 63
  • 62
8

Looks like this works OK

CertificateFactory fact = CertificateFactory.getInstance("X.509");
FileInputStream is = new FileInputStream ("cert.pem");
X509Certificate cert = (X509Certificate) fact.generateCertificate(is);
String cn = ((X500Name)cert.getSubjectDN()).getCommonName()
Alex G
  • 718
  • 1
  • 7
  • 9
  • It also can be used for String issuerCN = ((X500Name)x509cert.getIssuerDN()).getCommonName(); This is very useful, especially if the CN of the issuer (or the subject) within the source certificate contains special characters, like comma, backslash or the quotation mark. For me it worked with the import sun.security.x509.X500Name. – awgold90 Jul 17 '20 at 16:07
  • 1
    This should be the accepted answer! – Janaka Bandara Nov 29 '20 at 14:21
  • 1
    This API has been "denigrated" for years (at least since [Java 7](https://docs.oracle.com/javase/7/docs/api/java/security/cert/X509Certificate.html#getSubjectDN())) and is deprecated since Java 16, see [JDK-8250970](https://bugs.openjdk.java.net/browse/JDK-8250970). – chkpnt Jan 10 '22 at 10:50
6

You can avoid using a 'crappy' expression. If you already have the DN, you can split the String and then find the CN. For example:

String dn = "CN=Jimmy Blooptoop,OU=Someplace,OU=Employees,DC=Bloopsoft-Inc";
String[] split = dn.split(","); 
for (String x : split) {
    if (x.contains("CN=")) {
        System.out.println(x.trim());
    }
}
Konstantin Yovkov
  • 62,134
  • 8
  • 100
  • 147
  • 3
    Although a Regex works, they are usually expensive to use. For such a simple string format, comma delimited, a string split would be better. Unfortunately, the OP has stated that the values can also sometimes contain commas, which makes this solution not work. – Jesse Webb Oct 28 '11 at 19:08
  • @JesseWebb: A `String#split()` creates a Regex as well. – musiKk Nov 18 '11 at 08:58
  • @musiKk - Thank you for pointing that out, I was not aware of that. I suppose either solution would be equivalently expensive. – Jesse Webb Nov 18 '11 at 16:53
  • 5
    Will fail on, for example, `CN=Go Daddy Root Certificate Authority - G2, O="GoDaddy.com, Inc.", L=Scottsdale, ST=Arizona, C=US` – Andrew Alcock Mar 03 '14 at 04:00
  • 2
    Will also fail on `CN=Development Cyanogen Application,OU=Release Management,O=Cyanogen\, Inc.,L=Seattle,ST=Washington,C=US` – tophyr Nov 13 '15 at 17:12
4

If you are using Spring Framework, you may use DistinguishedName as follow:

String path = "CN=Jimmy Blooptoop,OU=Someplace,OU=Employees,DC=Bloopsoft-Inc";
DistinguishedName dn = new DistinguishedName(path);

String cn = dn.getValue("cn"); // Jimmy Blooptoop
Rubens Mariuzzo
  • 28,358
  • 27
  • 121
  • 148
  • 2
    `DistinguishedName` is now deprecated in favour of using the `javax.naming.ldap.LdapName`. But you can still use Spring to extract the name from it using `LdapUtils.getStringValue(ldapName, "cn")` – jbx Oct 08 '17 at 23:26
2

You should not be using regular expressions offered above. The problem with those regular expressions is that you will not follow RFC 4514 and will not unescape previously escaped symbols and may potentially loose or misinterpret a distinguished name. Use LdapName class instead as recommended by musiKk to parse a distinguished name correctly.

egridasov
  • 819
  • 1
  • 8
  • 7
0

I parse a DN like this. You get all attributes as properties:

Principal principal = cert.getSubjectDN();
Properties prop = new Properties();
prop.load(new StringReader(principal.getName().replaceAll(",", "\n")));
prop.list(System.out);
String CN= props.get("CN");

It does not work for duplicate elements like ou or dc though.

  • 3
    Like other plain regex/split "solutions", this will fail on commas in quoted sections, such as `CN=Go Daddy Root Certificate Authority - G2, O="GoDaddy.com, Inc.", L=Scottsdale, ST=Arizona, C=US` – Emil Lundberg Oct 02 '15 at 10:47
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
  • 6
    Like other plain regex/split "solutions", this will fail on commas in quoted sections, such as `CN=Go Daddy Root Certificate Authority - G2, O="GoDaddy.com, Inc.", L=Scottsdale, ST=Arizona, C=US` – Emil Lundberg Oct 02 '15 at 10:47
-1

If I understood it correctly this should produce the same result, just looks a bit more readable. \w matches any word-character.

"CN=[\\w]*[., ]+[\\w ]*"

If you want something more flexible you can do this:

Matcher m = Pattern.compile("(CN=.*?),[A-Z]{2}=").matcher(dn);
if (m.find())
  cn = m.group(1);

That matches anything between "CN=" and ",XX=", where XX are two uppercase letters. group(1) returns the first group (the match inside the parentheses).

Regexp's aren't as difficult as they look !

Lycha
  • 9,937
  • 3
  • 38
  • 43
  • 1
    But will fail on commas in quoted sections, eg `CN=Go Daddy Root Certificate Authority - G2, O="GoDaddy.com, Inc.", L=Scottsdale, ST=Arizona, C=US` – Andrew Alcock Mar 03 '14 at 04:01