如何在Java中从X509Certificate中提取CN?
我正在使用SslServerSocket
和客户端证书,并希望从客户端的X509Certificate
的SubjectDN中提取CN。
目前我打电话给cert.getSubjectX500Principal().getName()
但这当然给了我的客户端的总格式化的DN。 出于某种原因,我只是对CN=theclient
部分感兴趣。 有没有办法提取这部分的DN,而不是自己parsingstring?
以下是一些新的非弃用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());
这是另一种方式。 您的想法是您获得的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()); }
如果添加依赖不是问题,那么可以使用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在评论中提到的那样,这种方法现在已经被弃用了。 请参阅使用新Bouncy Castle API的gtrak 更新代码 。
作为不需要“bcmail”的gtrak代码的替代方法:
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:我已经使用了你的解决scheme,直到我的SW必须在Android上运行。 而Android不会实现javax.naming.ldap 🙁
CertUtil.subjectCN(certificate);
JavaDoc: http ://www.cryptacular.org/javadocs/org/cryptacular/util/CertUtil.html#subjectCN(java.security.cert.X509Certificate )
Maven依赖:
<dependency> <groupId>org.cryptacular</groupId> <artifactId>cryptacular</artifactId> <version>1.1.0</version> </dependency>
我有BouncyCastle 1.49,现在的类是org.bouncycastle.asn1.x509.Certificate。 我研究了IETFUtils.valueToString()
的代码 – 它正在用反斜杠进行一些奇特的转义。 对于一个域名,它不会做任何坏事,但我觉得我们可以做得更好。 在我看cn.getFirst().getValue()
的情况下, cn.getFirst().getValue()
返回不同types的string,它们都实现了ASN1String接口,它提供了一个getString()方法。 所以,似乎为我工作的是
Certificate c = ...; RDN cn = c.getSubject().getRDNs(BCStyle.CN)[0]; return ((ASN1String)cn.getFirst().getValue()).getString();
事实上,感谢gtrak
似乎获得客户端证书并提取CN,这很可能起作用。
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;
可以使用Cryptacular这是一个Java密码库build立在bouncycastle顶部,方便使用。
RDNSequence dn = new NameReader(cert).readSubject(); return dn.getValue(StandardAttributeType.CommonName);
更新:这个类是在“太阳”包,你应该谨慎使用它。 感谢Emil的评论:)
只是想分享,拿到CN,我这样做:
X500Name.asX500Name(cert.getSubjectX500Principal()).getCommonName();
关于Emil Lundberg的评论,请参阅: 为什么开发人员不应该编写称为“太阳”软件包的程序
X500Name是JDK的内部实现,但是您可以使用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); }
您可以尝试使用getName(X500Principal.RFC2253,oidMap)或getName(X500Principal.CANONICAL, oidMap)
来查看哪一个格式的DNstring最好。 也许其中一个oidMap
地图值将是你想要的string。
从证书中提取CN并不那么简单。 下面的代码一定会帮助你。
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();
到目前为止发布的所有答案都有一些问题:大多数使用内部X500Name
或外部Bounty Castle依赖项。 以下内容build立在@ 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(", "))
BC使提取更容易:
X500Principal principal = x509Certificate.getSubjectX500Principal(); X500Name x500name = new X500Name(principal.getName()); String cn = x500name.getCommonName();
正则expression式,使用起来相当昂贵。 对于这样一个简单的任务,它可能会被杀死。 相反,你可以使用简单的string分割:
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 ""; }