我正在使用SslServerSocket
和客户端证书,想要从客户端的X509Certificate
中提取SubjectDN中的CN。
目前我调用cert.getSubjectX500Principal().getName()
,但这当然给了我客户端的总格式化DN。由于某种原因,我只对DN中的CN=theclient
部分感兴趣。是否有方法可以在不自己解析字符串的情况下提取DN的此部分?
我正在使用SslServerSocket
和客户端证书,想要从客户端的X509Certificate
中提取SubjectDN中的CN。
目前我调用cert.getSubjectX500Principal().getName()
,但这当然给了我客户端的总格式化DN。由于某种原因,我只对DN中的CN=theclient
部分感兴趣。是否有方法可以在不自己解析字符串的情况下提取DN的此部分?
这里有一些新的非弃用的BouncyCastle API代码。您需要bcmail和bcprov两个发行版。
X509Certificate cert = ...;
X500Name x500name = new JcaX509CertificateHolder(cert).getSubject();
RDN cn = x500name.getRDNs(BCStyle.CN)[0];
return IETFUtils.valueToString(cn.getFirst().getValue());
IETFUtils.valueToString
方法似乎不能正确输出结果。我的 CN 包含一些等号,因为它是通过 Base64 编码得到的(例如 AAECAwQFBgcICQoLDA0ODw==
)。valueToString
方法会在结果中添加反斜杠。而使用 toString
方法似乎可以正常工作。很难确定这是否实际上是 API 的正确用法。 - Chris这里还有另一种方法。其思路是,你获得的DN采用的是rfc2253格式,这个格式和LDAP DN使用的格式相同。那么为什么不重用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());
}
String commonName = new LdapName(certificate.getSubjectX500Principal().getName()).getRdns().stream() .filter(i -> i.getType().equalsIgnoreCase("CN")).findFirst().get().getValue().toString();
- Reto HöhenerCN
(又名2.5.4.3
)这样的众所周知的类型字段,Rdn#getValue()
包含一个String
。然而,对于自定义类型,结果是byte[]
(可能基于以#
开头的内部编码表示)。当然,byte[]
->String
是可能的,但包含额外的(不可预测的)字符。我已经通过基于BC的@laz解决方案解决了这个问题,因为它可以正确地在String
中处理和解码。 - knalli如果添加依赖项不是问题,您可以使用Bouncy Castle的API来处理X.509证书:
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);
更新
在此帖发表时,这是实现此功能的方法。但是如gtrak在评论中提到的那样,这种方法现在已过时。请参见gtrak的已更新代码,该代码使用了新的Bouncy Castle API。
作为不需要''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:我一直使用你的解决方案,直到我的软件需要在Android上运行。但是Android没有实现javax.naming.ldap :-(
X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName()); String cn = x500Name.getCommonName();
(使用Java 8) - trichnerIETFUtils.valueToString
以 转义形式 返回值。我发现简单地调用 .toString()
对我而言更有效。 - holmis83到目前为止发布的所有答案都存在一些问题:大多数使用内部X500Name
或外部Bounty Castle依赖项。以下建立在@Jakub的答案上,只使用公共JDK API,并且根据OP的要求提取CN。它还使用Java 8,在2017年中期,你真的应该使用。
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(", "))
一行文字和http://www.cryptacular.org
CertUtil.subjectCN(certificate);
Maven 依赖:
<dependency>
<groupId>org.cryptacular</groupId>
<artifactId>cryptacular</artifactId>
<version>1.1.0</version>
</dependency>
如果您不想依赖BouncyCastle,可以使用正则表达式对cert.getSubjectX500Principal().getName()
进行解析。该正则表达式将解析显式名称,并为每个匹配项捕获组name
和val
。
由于DN字符串包含逗号时应进行引用,因此该正则表达式可以正确处理带引号和不带引号的字符串,并且还可以处理带引号字符串中的转义引号:
(?:^|,\s?)(?:(?<name>[A-Z]+)=(?<val>"(?:[^"]|"")+"|[^,]+))+
以下是格式良好的正则表达式:
(?:^|,\s?)
(?:
(?<name>[A-Z]+)=
(?<val>"(?:[^"]|"")+"|[^,]+)
)+
使用正则表达式获取证书的通用名称,不使用任何库。
要获取名称:
String name = x509Certificate.getSubjectDN().getName();
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));
}
希望这能对任何人有所帮助。(-_-)
更新:此类位于“sun”软件包中,您应该谨慎使用。感谢Emil的评论 :)
只是想分享一下,获取CN的方法如下:
X500Name.asX500Name(cert.getSubjectX500Principal()).getCommonName();
关于Emil Lundberg的评论,请参见:为什么开发人员不应编写调用“sun”包的程序
X500Name
是一个内部专有API,可能会在未来的版本中被移除。 - Emil LundbergCertificate c = ...;
RDN cn = c.getSubject().getRDNs(BCStyle.CN)[0];
return ((ASN1String)cn.getFirst().getValue()).getString();