102

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

目前我打电话cert.getSubjectX500Principal().getName(),但这当然给了我客户的总格式化DN。出于某种原因,我只是对CN=theclientDN 的部分感兴趣。有没有办法在不自己解析字符串的情况下提取这部分 DN?

4

20 回答 20

99

这是新的未弃用的 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());
于 2011-04-03T02:23:06.787 回答
98

这是另一种方式。这个想法是您获得的 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());
}
于 2011-10-03T12:07:01.397 回答
14

如果添加依赖项不是问题,您可以使用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的更新代码。

于 2010-05-26T20:46:00.907 回答
9

作为不需要 ''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:在我的软件必须在 Android 上运行之前,我一直在使用你的解决方案。而且Android没有实现javax.naming.ldap :-(

于 2011-11-30T08:20:35.327 回答
7

到目前为止发布的所有答案都有一些问题:大多数使用内部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(", "))
于 2017-07-13T22:35:59.993 回答
7

一行与http://www.cryptacular.org

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>
于 2015-08-12T16:31:37.300 回答
6

这是使用正则表达式 over 的方法cert.getSubjectX500Principal().getName(),以防您不想依赖 BouncyCastle。

此正则表达式将解析一个专有名称,为每个匹配项提供name和捕获组。val

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

(?:^|,\s?)(?:(?<name>[A-Z]+)=(?<val>"(?:[^"]|"")+"|[^,]+))+

这是很好的格式:

(?:^|,\s?)
(?:
    (?<name>[A-Z]+)=
    (?<val>"(?:[^"]|"")+"|[^,]+)
)+

这是一个链接,因此您可以看到它的实际效果: https ://regex101.com/r/zfZX3f/2

如果您希望正则表达式获取 CN,则此改编版本将执行此操作:

(?:^|,\s?)(?:CN=(?<val>"(?:[^"]|"")+"|[^,]+))

于 2018-04-03T10:26:51.950 回答
3

我有 BouncyCastle 1.49,它现在拥有的类是 org.bouncycastle.asn1.x509.Certificate。我查看了代码IETFUtils.valueToString()- 它正在使用反斜杠进行一些花哨的转义。对于一个域名它不会做任何坏事,但我觉得我们可以做得更好。在我看到的情况下,cn.getFirst().getValue()返回不同类型的字符串,它们都实现了 ASN1String 接口,它提供了一个 getString() 方法。所以,似乎对我有用的是

Certificate c = ...;
RDN cn = c.getSubject().getRDNs(BCStyle.CN)[0];
return ((ASN1String)cn.getFirst().getValue()).getString();
于 2013-09-17T20:39:58.317 回答
3

更新:这个类在“sun”包中,你应该谨慎使用它。感谢埃米尔的评论:)

只是想分享,为了获得CN,我这样做:

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

关于 Emil Lundberg 的评论,请参阅:Why Developers Should Not Write Programs That Call 'sun' Packages

于 2015-09-14T06:42:45.967 回答
2

实际上,由于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;
于 2014-11-10T09:56:33.377 回答
2

在不使用任何库的情况下获取证书的通用名称。使用正则表达式

获取名称

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

希望这对任何人都有帮助。(-_-)

于 2021-10-22T11:40:36.500 回答
2

使用纯 Java 的另一种方法:

public static String getCommonName(X509Certificate certificate) {
    String name = certificate.getSubjectX500Principal().getName();
    int start = name.indexOf("CN=");
    int end = name.indexOf(",", start);
    if (end == -1) {
        end = name.length();
    }
    return name.substring(start + 3, end);
}
于 2020-05-28T18:30:00.873 回答
1

可以使用 cryptacular,这是一个构建在 bouncycastle 之上的 Java 密码库,以便于使用。

RDNSequence dn = new NameReader(cert).readSubject();
return dn.getValue(StandardAttributeType.CommonName);
于 2015-05-28T09:31:40.853 回答
1

从证书中获取 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();
于 2017-01-17T21:33:55.460 回答
1

BC 使提取变得更加容易:

X500Principal principal = x509Certificate.getSubjectX500Principal();
X500Name x500name = new X500Name(principal.getName());
String cn = x500name.getCommonName();
于 2017-11-09T12:31:55.940 回答
0

正则表达式使用起来相当昂贵。对于这样一个简单的任务,它可能是一个过度杀戮。相反,您可以使用简单的字符串拆分:

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 "";
}
于 2014-07-24T12:41:40.690 回答
0

您可以尝试使用getName(X500Principal.RFC2253, oidMap)getName(X500Principal.CANONICAL, oidMap)查看哪种格式的 DN 字符串最好。也许oidMap地图值之一将是您想要的字符串。

于 2010-05-26T16:02:38.870 回答
0

对于多值属性 - 使用 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;
                        }
                    }
                }
            }
        }
于 2019-06-11T03:14:12.980 回答
0

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);
}
于 2016-12-23T07:36:22.453 回答
0

使用 Spring Security 可以使用SubjectDnX509PrincipalExtractor

X509Certificate certificate = ...;
new SubjectDnX509PrincipalExtractor().extractPrincipal(certificate).toString();
于 2020-11-25T10:53:27.617 回答