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,我们可以执行以下操作:
- 解析证书
- 获取其DN
- 解析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);
}
}
上述代码中,我们使用Pattern
和Matcher
类。首先,使用静态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上找到。