Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/328.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在Java中从X509证书中提取CN?_Java_Ssl_X509certificate_X509 - Fatal编程技术网

如何在Java中从X509证书中提取CN?

如何在Java中从X509证书中提取CN?,java,ssl,x509certificate,x509,Java,Ssl,X509certificate,X509,我正在使用SslServerSocket和客户端证书,并希望从客户端的X509Certificate中从SubjectDN提取CN 现在我调用cert.getSubjectX500 principal().getName(),但这当然会给出客户端的总格式DN。出于某种原因,我只对DN的CN=theclient部分感兴趣。有没有一种方法可以在不解析字符串的情况下提取DN的这一部分?您可以尝试使用或getName(X500Principal.CANONICAL,oidMap)查看哪一种格式最适合DN

我正在使用
SslServerSocket
和客户端证书,并希望从客户端的
X509Certificate
中从SubjectDN提取CN


现在我调用
cert.getSubjectX500 principal().getName()
,但这当然会给出客户端的总格式DN。出于某种原因,我只对DN的
CN=theclient
部分感兴趣。有没有一种方法可以在不解析字符串的情况下提取DN的这一部分?

您可以尝试使用或
getName(X500Principal.CANONICAL,oidMap)
查看哪一种格式最适合DN字符串。可能其中一个
oidMap
映射值就是您想要的字符串。

如果添加依赖项不是问题,您可以使用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);
import org.bounchycastle.asn1.x509.X509Name;
导入org.bouncycastle.jce.PrincipalUtil;
导入org.bouncycastle.jce.X509Principal;
...
最终X509Principal=PrincipalUtil.getSubjectX509Principal(证书);
最终向量值=principal.getValues(X509Name.CN);
最终字符串cn=(字符串)值。get(0);
更新


在发布这篇文章时,这就是实现这一点的方法。然而,正如gtrak在评论中提到的那样,这种方法现在已被弃用。请参阅使用新的Bouncy Castle API的gtrak。

以下是新的非弃用Bouncy Castle 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());
}

作为不需要“bcmail”的gtrak代码的替代方案:


@Jakub:在我的软件必须在Android上运行之前,我一直在使用你的解决方案。Android没有实现javax.naming.ldap:-(

我有BouncyCastle 1.49,它现在的类是org.BouncyCastle.asn1.x509.Certificate。我查看了
ietfuitls.valueToString()的代码)
-它使用反斜杠进行了一些奇特的转义。对于域名来说,它不会有什么不好的地方,但我觉得我们可以做得更好。在我看到的例子中,
cn.getFirst().getValue()
返回各种类型的字符串,它们都实现了ASN1String接口,该接口用于提供一个getString()方法。所以,对我来说有效的是

Certificate c = ...;
RDN cn = c.getSubject().getRDNs(BCStyle.CN)[0];
return ((ASN1String)cn.getFirst().getValue()).getString();

正则表达式的使用成本相当高。对于这样一个简单的任务,它可能会造成过度杀戮。相反,您可以使用简单的字符串拆分:

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 "";
}

实际上,多亏了
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;

可以使用Cryptocal,这是一个构建在bouncycastle之上的Java加密库,易于使用

RDNSequence dn = new NameReader(cert).readSubject();
return dn.getValue(StandardAttributeType.CommonName);
一行

JavaDoc:

Maven依赖项:

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

隐语
隐语的
1.1.0
更新:该类在“sun”软件包中,您应该谨慎使用。感谢Emil的评论:)

只是想分享,为了得到CN,我做了:

X500Name.asX500Name(cert.getSubjectX500Principal()).getCommonName();

关于EmilLundberg的评论,请参见:

X500Name是JDK的内部实现,但是您可以使用反射

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);
}
publicstringgetcn(stringformatteddn)引发异常{
ClassX500NameCLZZ=Class.forName(“sun.security.x509.X500Name”);
Constructor=x500NameClzz.getConstructor(String.class);
对象x500NameInst=constructor.newInstance(FormattedDN);
方法Method=x500NameClzz.getMethod(“getCommonName”,null);
return(String)method.invoke(x500NameInst,null);
}

从证书中获取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
或外部赏金城堡依赖项。以下内容基于@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();

下面介绍如何使用正则表达式在
cert.getSubjectX500Principal().getName()
上执行此操作,以防您不想依赖BouncyCastle

此正则表达式将解析一个可分辨名称,为每个匹配提供
name
val
捕获组

当DN字符串包含逗号时,它们将被引用-此正则表达式正确处理引号和非引号字符串,并且还处理引号字符串中的转义引号:

(?:^ |,\s?(?[A-Z]+)=(?“(?:[^”]|”“)+“[^,]+)+

以下是格式良好的:

(?:^|,\s?)
(?:
    (?<name>[A-Z]+)=
    (?<val>"(?:[^"]|"")+"|[^,]+)
)+
(?:^ |,\s?)
(?:
(?[A-Z]+)=
(?"(?:[^"]|"")+"|[^,]+)
)+
这里有一个链接,您可以看到它的运行:

如果您希望正则表达式仅获取CN,则此改编版本将实现:


多值属性的
(?:^ |,\s?(:CN=(?”(?:[^“]|”)+“|[^,]+)

-使用LDAP API

        X509Certificate testCertificate = ....

        X500Principal principal = testCertificate.getSubjectX500Principal(); // return subject DN
        String dn = null;
        if (principal != null)
        {
            String value = principal.getName(); // return String representation of DN in RFC 2253
            if (value != null && value.length() > 0)
            {
                dn = value;
            }
        }

        if (dn != null)
        {
            LdapName ldapDN = new LdapName(dn);
            for (Rdn rdn : ldapDN.getRdns())
            {
                Attributes attributes = rdn != null
                    ? rdn.toAttributes()
                    : null;

                Attribute attribute = attributes != null
                    ? attributes.get("CN")
                    : null;
                if (attribute != null)
                {
                    NamingEnumeration<?> values = attribute.getAll();
                    while (values != null && values.hasMoreElements())
                    {
                        Object o = values.next();
                        if (o != null && o instanceof String)
                        {
                            String cnValue = (String) o;
                        }
                    }
                }
            }
        }
X509Certificate testCertificate=。。。。
X500主体=testCertificate.GetSubjectX500主体();//返回主体DN
字符串dn=null;
if(主体!=null)
{
String value=principal.getName();//返回RFC 2253中DN的字符串表示形式
if(value!=null&&value.length()>0)
{
dn=数值;
}
}
如果(dn!=null)
{
LdapName ldapDN=新的LdapName(dn);
对于(Rdn Rdn:ldapDN.getRdns())
{
Attributes=rdn!=null
?rdn.toAttributes()
:null;
属性=属性!=null
?attributes.get(“CN”)
:null;
if(属性!=null)
{
NamingEnumeration values=attribute.getAll();
while(values!=null&&values.hasMoreElements())
(?:^|,\s?)
(?:
    (?<name>[A-Z]+)=
    (?<val>"(?:[^"]|"")+"|[^,]+)
)+
        X509Certificate testCertificate = ....

        X500Principal principal = testCertificate.getSubjectX500Principal(); // return subject DN
        String dn = null;
        if (principal != null)
        {
            String value = principal.getName(); // return String representation of DN in RFC 2253
            if (value != null && value.length() > 0)
            {
                dn = value;
            }
        }

        if (dn != null)
        {
            LdapName ldapDN = new LdapName(dn);
            for (Rdn rdn : ldapDN.getRdns())
            {
                Attributes attributes = rdn != null
                    ? rdn.toAttributes()
                    : null;

                Attribute attribute = attributes != null
                    ? attributes.get("CN")
                    : null;
                if (attribute != null)
                {
                    NamingEnumeration<?> values = attribute.getAll();
                    while (values != null && values.hasMoreElements())
                    {
                        Object o = values.next();
                        if (o != null && o instanceof String)
                        {
                            String cnValue = (String) o;
                        }
                    }
                }
            }
        }
X509Certificate certificate = ...;
new SubjectDnX509PrincipalExtractor().extractPrincipal(certificate).toString();