如何从Java中的X509Certificate中提取CN?

时间:2010-05-26 15:44:22

标签: java ssl x509certificate x509

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

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

18 个答案:

答案 0 :(得分:88)

这是另一种方式。您的想法是您获得的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());
}

答案 1 :(得分:75)

以下是新推荐的不推荐使用的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());

答案 2 :(得分:12)

如果添加依赖项不是问题,可以使用Bouncy Castle's 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 updated code

答案 3 :(得分:9)

作为gtrak代码的替代品,不需要&#39; bcmail&#39;&#39;:

    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:我已经使用了你的解决方案,直到我的SW必须在Android上运行。 Android没有实现javax.naming.ldap: - (

答案 4 :(得分:5)

一行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>

答案 5 :(得分:5)

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

答案 6 :(得分: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();

答案 7 :(得分:2)

如果您不想依赖BouncyCastle,请使用cert.getSubjectX500Principal().getName()上的正则表达式进行操作。

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

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

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

格式很好:

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

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

如果你想要一个正则表达式只能 CN,那么这个改编的版本将会这样做:

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

答案 8 :(得分:1)

确实,感谢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;

答案 9 :(得分:1)

可以使用基于bouncycastle构建的Java加密库加密,以方便使用。

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

答案 10 :(得分: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();

答案 11 :(得分:1)

使用纯Java的另一种方法:

#include <stdio.h>
#include <stdlib.h>

struct patients{
char last_name[15];
int passport_number;
char disease[30];
char doctors_last_name[15];
};

int main (){
int n,i;
char enter_doctors_last_name [15];



struct patients mas_struct[3]={{"Ivanov",5457401,"COVID-18","Davis"},{"Petrov",2864228,"COVID-19","Davis"},{"Petrova",63863380,"COVID-19","Dixon"}};
    printf("\nPatients:");
    printf("\n Last name  |   Passport number  | \tDisease   |   Doctor's last name ");
for (i=0;i<3;i++)
    printf("\n %s \t%d \t%s \t%s",mas_struct[i].last_name,mas_struct[i].passport_number,mas_struct[i].disease,mas_struct[i].doctors_last_name);
    printf("\n");
    printf("\nEnter doctor's last name:");
    scanf("%s", enter_doctors_last_name);
    printf("\nPatients:");

for (i=0;i<3;i++)
 if(mas_struct[i].doctors_last_name == enter_doctors_last_name)
    printf("\n %s \t%d \t%s \t%s",mas_struct[i].last_name,mas_struct[i].passport_number,mas_struct[i].disease,mas_struct[i].doctors_last_name);

return 0;
}

答案 12 :(得分: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 "";
}

答案 13 :(得分:0)

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

答案 14 :(得分: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);
}

答案 15 :(得分:0)

不列颠哥伦比亚省使提取更容易:

X500Principal principal = x509Certificate.getSubjectX500Principal();
X500Name x500name = new X500Name(principal.getName());
String cn = x500name.getCommonName();

答案 16 :(得分: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;
                        }
                    }
                }
            }
        }

答案 17 :(得分:0)

借助Spring Security,可以使用SubjectDnX509PrincipalExtractor

X509Certificate certificate = ...;
new SubjectDnX509PrincipalExtractor().extractPrincipal(certificate).toString();