web-dev-qa-db-ja.com

JavaでX509CertificateからCNを抽出する方法は?

SslServerSocketとクライアント証明書を使用しており、クライアントの_X509Certificate_からSubjectDNからCNを抽出したい。

現時点ではcert.getSubjectX500Principal().getName()を呼び出していますが、これはもちろん、クライアントのフォーマットされたDN全体を提供します。何らかの理由で、DNの_CN=theclient_部分に興味があるだけです。文字列を自分で解析せずにDNのこの部分を抽出する方法はありますか?

80
Martin C.

非推奨ではない新しい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());
76
gtrak

ここに別の方法があります。取得する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());
}
90
Jakub

依存関係の追加が問題でない場合は、X-509証明書を操作するための Bouncy Castle's 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の 更新されたコード を参照してください。

12
laz

'' 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を実装しません:-(

9
Ivin

http://www.cryptacular.org を含む1行

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>
6

これまでに投稿されたすべての回答にはいくつかの問題があります。ほとんどは内部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(", "))
5
Abhijit Sarkar

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();
3
G L

BouncyCastleに依存したくない場合に、cert.getSubjectX500Principal().getName()を正規表現を使用して実行する方法を次に示します。

この正規表現は識別名を解析し、nameおよびvalに各一致のキャプチャグループを与えます。

DN文字列にカンマが含まれる場合、引用符で囲む必要があります-この正規表現は、引用符付き文字列と引用符なし文字列の両方を正しく処理し、引用符付き文字列のエスケープ引用符も処理します

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

うまくフォーマットされています:

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

ここにリンクがありますので、実際の動作をご覧ください: https://regex101.com/r/zfZX3f/2

正規表現にCNをonlyだけ取得させたい場合は、この適合バージョンがそれを行います:

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

2
Cocowalla

更新:このクラスは「Sun」パッケージに含まれているため、注意して使用する必要があります。 Emilにコメントをありがとう。

CNを取得するために共有したいだけです。

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

Emil Lundbergのコメントについては、 「Sun」パッケージを呼び出すプログラムを開発者が書くべきではない理由

1
Rad

証明書から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();
1
vinayaka cn

確かに、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;
1
EpicPandaForce

Java簡単に使用できるようにbouncycastleの上に構築された暗号化ライブラリであるcryptacularを使用できます。

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

正規表現は、使用するのにかなり費用がかかります。このような単純なタスクの場合は、おそらくオーバーキルになります。代わりに、単純な文字列分割を使用できます。

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

getName(X500Principal.RFC2253、oidMap) またはgetName(X500Principal.CANONICAL, oidMap)を使用して、DN文字列の最適なフォーマットを確認してください。おそらく、oidMapマップ値の1つが必要な文字列になるでしょう。

0

BCは抽出をはるかに簡単にしました。

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

複数値の属性の場合-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;
                        }
                    }
                }
            }
        }
0
TodayGuessWhat

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);
}
0
bro.xian