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


91

SslServerSocketとクライアント証明書を使用していて、クライアントのSubjectDNからCNを抽出したいと思いますX509Certificate

現時点では電話をかけてcert.getSubjectX500Principal().getName()いますが、もちろんこれにより、クライアントのフォーマットされたDNの合計がわかります。どういうわけか、私はCN=theclientDNの部分に興味があります。文字列を自分で解析せずにDNのこの部分を抽出する方法はありますか?



2
@AhmadAbdelghany私の質問は、リンクされている質問よりも約1。5年古いことに気づきましたか?したがって、どちらかといえば、もう1つは私の複製です:-)
Martin C.

フェアポイント。もう一方にフラグを立てます。
Ahmad Abdelghany 2017

ストリームソリューションAbhijitSarkarここにリンクの説明を入力してください
クリスチャン

回答:


89

非推奨ではない新しいBouncyCastleAPIのコードを次に示します。bcmailとbcprovの両方のディストリビューションが必要です。

X509Certificate cert = ...;

X500Name x500name = new JcaX509CertificateHolder(cert).getSubject();
RDN cn = x500name.getRDNs(BCStyle.CN)[0];

return IETFUtils.valueToString(cn.getFirst().getValue());

9
@grak、私はあなたがこの解決策をどのように理解したかに興味があります。確かに、APIドキュメントを見ただけでは、これを理解することはできませんでした。
エリオットバルガス

5
ええ、私はその感情を共有します...私はメーリングリストで尋ねなければなりませんでした。
gtrak 2012

7
現在(2012年10月23日)のBouncyCastle(1.47)のこのコードには、bcpkixの配布も必要であることに注意してください。
EwyynTomato 2012年

証明書には複数のCNを含めることができます。cn.getFirst()を返すだけでなく、すべてを繰り返し処理してCNのリストを返す必要があります。
varrunr 2014年

5
IETFUtils.valueToString正しい結果を生成するためには表示されません。Base 64エンコーディングのためにいくつかの等号を含むCNがあります(例AAECAwQFBgcICQoLDA0ODw==)。このvalueToStringメソッドは、結果にスラッシュを追加します。その代わりに、使用toStringが機能しているようです。これが実際にAPIの正しい使用法であるかどうかを判断するのは困難です。
クリス

94

ここに別の方法があります。取得するDNはrfc2253形式であり、LDAPDNに使用されるものと同じであるという考え方です。では、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
Springを使用している場合の便利なショートカットの1つ:LdapUtils.getStringValue(ldapDN、 "cn");
Berthier Lemieux 2016

:私の質問に視力を持ってくださいstackoverflow.com/questions/40613147/...
Hosein Aqajani

少なくとも、私がCNに取り組んでいる場合は、マルチ属性RDN内にあります。言い換えると、提案されたソリューションはRDNの属性を反復処理しません。そうすべき!
peterh 2018

String commonName = new LdapName(certificate.getSubjectX500Principal().getName()).getRdns().stream() .filter(i -> i.getType().equalsIgnoreCase("CN")).findFirst().get().getValue().toString();
レトHöhener

注:すばらしい解決策のように見えますが、いくつかの問題があります。「非標準」フィールドのデコードの問題を発見するまで、私はこれを数年間使用していました。CN(aka 2.5.4.3)のようなよく知られたタイプのようなタイプのフィールドにRdn#getValue()は、が含まれますString。ただし、カスタムタイプの場合、結果は次のようになりますbyte[](おそらく、で始まる内部エンコード表現に基づいてい#ます)。多くの場合、byte[]->Stringは可能ですが、追加の(予測できない)文字が含まれています。でこれを正しく処理およびデコードするため、BCに基づく@lazソリューションでこれを解決しましたString
knalli

12

依存関係の追加が問題にならない場合は、X.509証明書を操作するためのBouncyCastleの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がコメントで言及しているように、このアプローチは現在非推奨です。新しいBouncyCastleAPIを使用するgtrakの更新されたコードを参照してください。


X509NameはBouncycastle1.46で非推奨になっているようで、x500Nameを使用する予定です。それについて、または同じことをするために意図された代替案について何か知っていますか?
gtrak 2011

うわー、新しいAPIを見ると、上記のコードと同じ目標を達成する方法を理解するのに苦労しています。おそらく、Bouncycastleメーリングリストのアーカイブに答えがあるかもしれません。私がそれを理解したら、私はこの答えを更新します。
laz 2011年

私は同じ問題を抱えています。何か思いついたら教えてください。これは私が得た限りです:x500name = X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(cert)); RDN cn = x500name.getRDNs(BCStyle.CN)[0];
gtrak 2011

メーリングリストのディスカッションでその方法を見つけ、その方法を示す回答を作成しました。
gtrak 2011

gtrakを見つけてください。私はある時点でそれを理解しようとして10分を費やしましたが、二度と戻ってきませんでした。
laz

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:SWをAndroidで実行する必要があるまで、私はあなたのソリューションを使用しました。そしてAndroidはjavax.naming.ldapを実装していません:-(


これは、私がこのソリューションを思いついたのとまったく同じ理由です。Androidへの移植...
Ivin

8
これがいつ変更されたかはX500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName()); String cn = x500Name.getCommonName();
わかり

:私の質問に視力を持ってくださいstackoverflow.com/questions/40613147/...
Hosein Aqajani

IETFUtils.valueToStringで値を返すエスケープ形式。.toString()代わりに呼び出すだけでうまくいくことがわかりました。
holmis83 2017年

7

http://www.cryptacular.orgの1行

CertUtil.subjectCN(certificate);

JavaDoc: http)

Mavenの依存関係:

<dependency>
    <groupId>org.cryptacular</groupId>
    <artifactId>cryptacular</artifactId>
    <version>1.1.0</version>
</dependency>

Cryptacular1.1.xシリーズはJava7用で、1.2.xはJava 8用であることに注意してください。ただし、非常に優れたライブラリです。
Markus L

6

これまでに投稿されたすべての回答にはいくつかの問題があります。ほとんどの場合、内部X500Nameまたは外部のバウンティキャッスルの依存関係を使用します。以下は@Jakubの回答に基づいており、パブリックJDK APIのみを使用しますが、OPからの要求に応じてCNも抽出します。また、2017年半ばに登場するJava8も使用しています。

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

私の場合、CNはマルチ属性RDN内にあります。このソリューションを拡張して、RDNごとにRDNの最初の属性を確認するのではなく、RDN属性を反復処理する必要があると思います。これは、ここで暗黙的に行っていることだと思います。
peterh 2018

4

正規表現を使用してそれを行う方法は次のとおりです 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>"(?:[^"]|"")+"|[^,]+))


周りの最も堅牢な答え。また、番号で指定されたOID(OID.2.5.4.97など)でもサポートする場合は、許可される文字を[AZ]から[AZ、0-9、。]に拡張する必要があります
yurislav

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

バックスラッシュの問題が発生したため、これで問題が修正されました。
琥珀色

3

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

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

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

Emil Lundbergのコメントについては、「開発者が「sun」パッケージを呼び出すプログラムを作成すべきでない理由」を参照してください。


これは、シンプルで読みやすく、JDKにバンドルされているもののみを使用するため、現在の回答の中で私のお気に入りです。
Emil Lundberg 2015

JDKクラスの使用についてあなたが言ったことに同意します:)
Rad 2015

3
ただし、javacは、X500Name将来のリリースで削除される可能性のある内部独自のAPIであることについて警告していることに注意してください。
Emil Lundberg 2015年

ええ、リンクされたFAQを読んだ後、最初のコメントを取り消す必要があります。ごめんなさい。
Emil Lundberg 2015年

1
全く問題なし。あなたが指摘したことは本当に重要です。ありがとう:)実際、私はもうそのクラスを使用していません:P
Rad

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;

この関連の質問をチェックstackoverflow.com/a/28295134/2413303
EpicPandaForce

1

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

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

@ErdemMemisyaziciの提案を使用することをお勧めします。
ghetolay 2015


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

1

プレーンJavaを使用するもう1つの方法:

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

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

私は本当にそれが好き!プラットフォームとライブラリに依存しません。これは本当にクールです!
user2007447 2014年

2
私から反対票を投じてください。RFC 2253を読むと、エスケープされたコンマ\,や引用符で囲まれた値など、考慮しなければならないエッジケースがあることがわかります。
ダンカンジョーンズ

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

0

BCにより、抽出がはるかに簡単になりました。

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

X500Nameに.getCommonName()メソッドが見つかりません。
のLaPO

(@lapo)実際に使用していないことを確認しますsun.security.x509.X500Name-数年前に指摘された他の回答が文書化されておらず、信頼できないものですか?
dave_thompson_085 2018

ええと、私はorg.bouncycastle.asn1.x500.X500Nameそのメソッドを示さないクラスのJavaDocをリンクしました…
lapo 2018

0

複数値属性の場合-LDAPAPIを使用...

        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;
                        }
                    }
                }
            }
        }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.