1.概述

在X.509证书的Distinguished Name (DN) 字段中,Common Name (CN) 是一个属性。通常情况下,CN代表证书所属组织的域名。在应用程序中,我们有时需要从证书文件中获取CN值。

本教程将介绍在Java中提取CN值的不同方法

2. CN

证书包含有关证书所有者的详细信息:有效期、证书用途、DN等。

Distinguished Name (DN)本质上是一组名称-值对,包括如国家(C)、组织(O)、组织单位(OU)、CN等名称

一个DN示例是“CN=Baeldung, L=Casablanca, ST=Morocco, C=MA”。如这个例子所示,CN通常是网站的域名。

在Java中从X.509证书中提取CN,我们可以执行以下操作:

  1. 解析证书
  2. 获取其DN
  3. 解析DN以提取CN

接下来的章节将使用不同的库来提取CN。

3. 使用BouncyCastle

BouncyCastle 是一组补充Java Cryptographic Extension (JCE)默认加密操作的API。它还提供了一种方便的方式来获取证书信息。

3.1. Maven依赖

首先,在我们的pom.xml中声明bouncycastle依赖:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.70</version>
</dependency>

3.2. 提取CN

首先,从证书文件获取一个X509Certificate对象:

Security.addProvider(new BouncyCastleProvider());
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509", "BC");
X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(new FileInputStream("src/main/resources/Baeldung.cer"));

在上面的代码中,我们使用addProvider()方法注册了BouncyCastleProvider作为安全提供者。然后,我们使用getInstance()方法创建一个CertificateFactory对象。getInstance()方法接受两个参数——证书类型“X.509”和安全提供者“BC”。随后,我们通过generateCertificate()方法使用certificateFactory实例生成X509Certificate对象。

接下来,从X509Certificate对象中获取CN:

@Test
void whenUsingBouncyCastle_thenExtractCommonName() {
    X500Principal principal = certificate.getSubjectX500Principal();
    X500Name x500Name = new X500Name(principal.getName());
    RDN[] rdns = x500Name.getRDNs(BCStyle.CN);
    List<String> names = new ArrayList<>();
    for (RDN rdn : rdns) {
        String name = IETFUtils.valueToString(rdn.getFirst().getValue());
        names.add(name);
    }

    for (String commonName : names) {
        assertEquals("Baeldung", commonName);
    }
}

在这段代码中,我们使用getSubjectX500Principal()方法以X500Principal格式获取主体DN。接着,我们将DN转换为BouncyCastle的X500Name表示形式。然后,我们通过getRDNs()方法从X500Name对象中提取CNs。一个RDN是BouncyCastle类,代表X.500Name对象的一部分。一个X.500Name对象由多个RDN组成,每个RDN由一个属性类型和一个属性值构成。最后,我们使用常量BCStyle.CN,它是CN属性类型的BouncyCastle常量。

4. 使用正则表达式

正则表达式(regex)是Java中强大的字符串处理工具。我们可以利用它从证书中提取CN

创建一个测试用例提取CN:

@Test
void whenUsingRegex_thenExtractCommonName() {
    X500Principal principal = certificate.getSubjectX500Principal();
    List<String> names = new ArrayList<>();
    Pattern pattern = Pattern.compile("CN=([^,]+)");
    Matcher matcher = pattern.matcher(principal.getName());
    while (matcher.find()) {
        names.add(matcher.group(1));
    }

    for (String commonName : names) {
        assertEquals("Baeldung", commonName);
    }
}

上述代码中,我们使用PatternMatcher类。首先,使用静态compile()方法创建一个Pattern对象,并传入模式“*CN=([^,]+)*”。然后,通过调用Pattern对象的matcher()方法创建一个Matcher对象,并传入DN值。最后,我们在Matcher对象上调用find()方法。

5. 使用Cryptacular库

另一种从证书中获取CN值的方法是使用Cryptacular库

5.1. Maven依赖

pom.xml中声明cryptacular依赖:

<dependency>
    <groupId>org.cryptacular</groupId>
    <artifactId>cryptacular</artifactId>
    <version>1.2.6</version>
</dependency>

5.2. 提取CN

让我们使用CertUtil类创建一个测试用例来提取CN:

@Test
void whenUsingCryptacular_thenExtractCommonName() {
    String commonName = CertUtil.subjectCN(certificate);
    assertEquals("Baeldung", commonName);
}

我们使用subjectCN()方法从X509Certificate对象中提取CN。另外,需要注意的是,当证书有多个CN时,此库只会返回一个CN

6. 使用LDAP API

我们也可以使用标准的JDK LDAP API实现相同的目标

@Test
void whenUsingLDAPAPI_thenExtractCommonName() throws Exception {
    X500Principal principal = certificate.getSubjectX500Principal();
    LdapName ldapDN = new LdapName(principal.getName());
    List<String> names = new ArrayList<>();
    for (Rdn rdn : ldapDN.getRdns()) {
        if (rdn.getType().equalsIgnoreCase("cn")) {
            String name = rdn.getValue().toString();
            names.add(name);
        }
    }

    for (String commonName : names) {
        assertEquals("Baeldung", commonName);
    }
}

上述代码处理在LDAP上下文中解析X.509证书的DN。我们根据DN的字符串表示构建LdapName对象。这是将X.509证书上下文中的DN转换为LDAP上下文的一种方式。

一旦有了LdapName实例,我们可以轻松地将DN拆分为各个组成部分(如CN、OU、O等)使用getRdns()方法

7. 总结

CN是证书中的重要部分,在SSL/TLS证书中,CN用于标识与证书关联的域名。

本文介绍了如何使用多种方法从证书文件中提取CN值。

一如既往,示例代码可在GitHub上找到。