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

SslServerSocket및 클라이언트 인증서를 사용하고 있으며 클라이언트의 .NET Framework에서 SubjectDN에서 CN을 추출하려고합니다 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(", "))


답변

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