ProgramingTip

Java의 X509Certificate에서 CN을 추출하는 방법은 무엇입니까?

bestdevel 2020. 10. 4. 12:01
반응형

Java의 X509Certificate에서 CN을 추출하는 방법은 무엇입니까?


SslServerSocket및 클라이언트 인증서를 사용하고 인식 클라이언트의 .NET Framework에서 SubjectDN을 추출합니다 X509Certificate.

지금 전화를 cert.getSubjectX500Principal().getName()걸지만 물론 클라이언트의 전체 형식 DN을 제공합니다. 어떤 이유로 나는 CN=theclientDN 일부 에만 관심이 있습니다. 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());

여기에 또 다른 방법이 있습니다. 아이디어는 얻은 DN이 LDAP DN에 사용 된 것과 동일한 rfc2253 형식이라는 것입니다. 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());
}

추가가 문제가되지 않는 경우 X.509 인증서 작업을 위해 Bouncy Castle의 API 로이를 수행 할 수 있습니다 .

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 : SW가 Android에서 실행될 때까지 솔루션을 사용했습니다. 그리고 Android는 javax.naming.ldap을 구현하지 않습니다. :-(


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>

지금까지 게시 된 모든 답변에는 몇 가지 문제가 있습니다. 대부분은 내부 X500Name또는 외부 Bounty Castle을 사용합니다. 다음은 @Jakub의 답변을 기반으로하며 공용 JDK API 만 사용하지만 OP에서 요청 한대로 CN을 추출합니다. 또한 2017 년 중반에 출시 된 Java 8을 사용합니다.

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(", "))

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

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>"(?:[^"]|"")+"|[^,]+))


실제로 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;

쉬운 사용을 위해 bouncycastle 위에 빌드 된 Java 암호화 라이브러리 인 cryptacular를 사용할 수 있습니다.

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

업데이트 :이 클래스는 "sun"패키지에 있으므로주의해서 사용해야합니다. 댓글에 대한 Emil 감사합니다 :)

CN을 얻기 위해 공유하고 싶었습니다.

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

Emil Lundberg의 의견에 대해서는 개발자가 'sun'패키지를 호출하는 프로그램을 작성하지 않아야하는 이유를 참조하십시오.


인증서에서 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();

정규 표현식은 사용하는 데 다소 비쌉니다. 이러한 간단한 작업의 경우 아마도 오버 킬일 것입니다. 대신 간단한 문자열 분할을 사용할 수 있습니다.

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

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

다음을 사용하여 시도 할 수있는 getName (X500Principal.RFC2253, oidMap)을 또는 어느 getName(X500Principal.CANONICAL, oidMap)형식의 DN을 최고의 볼 수 있습니다. oidMap맵 값 중 하나가 원하는 원하는 일 수 있습니다.


BC는 추출을 훨씬 쉽게 만들었습니다.

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

다중 값 속성의 경우 -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;
                        }
                    }
                }
            }
        }

참고 URL : https://stackoverflow.com/questions/2914521/how-to-extract-cn-from-x509certificate-in-java

반응형