2010-05-26 39 views
64

我正在使用SslServerSocket和客戶端證書,並且想要從客戶端的X509Certificate的SubjectDN中提取CN。如何從Java中的X509Certificate中提取CN?

目前我打電話給cert.getSubjectX500Principal().getName(),但這當然給了我客戶端的格式化的DN。出於某種原因,我只對DN的CN=theclient部分感興趣。有沒有一種方法可以在不解析字符串的情況下提取DN的這部分內容?

+0

[解析CN出證書DN的]的可能的複製(https://stackoverflow.com/questions/7933468/parsing-the- cn-out-of-a-certificate-dn) – 2017-12-22 09:02:18

+0

@AhmadAbdelghany你意識到,我的問題比鏈接的大約大1。5年?所以如果有的話,另一個是我的重複:-) – 2017-12-22 12:43:31

+0

公平點。我會標記另一個。 – 2017-12-22 14:25:59

回答

63

下面是新的非棄用的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()); 
+4

@grak,我對你如何解決這個問題感興趣。當然,從查看API文檔中,我永遠無法弄清楚這一點。 – 2012-08-24 19:34:53

+3

是啊,我分享這種情緒......我不得不在郵件列表上詢問。 – gtrak 2012-08-29 19:38:22

+7

請注意,目前的這個代碼(2012年10月23日)BouncyCastle(1.47)也需要bcpkix分發。 – EwyynTomato 2012-10-23 07:18:55

0

您可以嘗試使用getName(X500Principal.RFC2253, oidMap)getName(X500Principal.CANONICAL, oidMap)來查看哪一種格式最適合DN字符串。也許oidMap地圖值之一將是你想要的字符串。

10

如果添加依賴不是一個問題,您可以與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

+0

它好像X509Name在Bouncycastle 1.46中被棄用,並且他們打算使用x500Name。知道關於這個或者做同樣事情的另一個選擇嗎? – gtrak 2011-02-28 15:40:24

+0

哇,看着新的API我很難找出如何完成上述代碼相同的目標。也許Bouncycastle郵件列表檔案可能有答案。如果我弄明白了,我會更新這個答案。 – laz 2011-03-01 03:55:55

+0

我遇到同樣的問題。如果你想出任何東西,請讓我知道。這是我得到的:x500name = X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(cert)); RDN cn = x500name.getRDNs(BCStyle.CN)[0]; – gtrak 2011-04-01 15:25:34

73

這裏是另一種方式。您的想法是您獲得的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()); 
} 
+0

+1重用,看起來很乾淨 – Sebastian 2012-01-04 08:11:17

+0

這非常有創意 – gtrak 2012-02-13 23:00:46

+1

一個有用的快捷方式,如果您使用的是spring:LdapUtils.getStringValue(ldapDN,「cn」); – 2016-08-16 05:48:20

9

作爲替代gtrak的代碼並不需要'bcmail「」:

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 :-(

+0

這與我解決這個問題的方法完全一樣:移植到Android ... – Ivin 2012-01-05 09:07:52

+7

不知道什麼時候這個改變了,但現在這個工作:'X500Name x500Name = new X500Name(cert.getSubjectX500Principal()。getName()); String cn = x500Name.getCommonName();'(使用java 8) – trichner 2014-09-22 12:56:24

+0

請看看我的問題:http://stackoverflow.com/questions/40613147/how-to-get-the-policy-identifier-and基本約束條件的主題類型 – 2016-11-17 11:08:15

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

我遇到了反斜槓問題,所以這解決了我的問題。 – Amber 2017-02-06 21:41:16

-1

正則表達式的表情,相當使用昂貴的。對於這樣一個簡單的任務,它可能會是一個超必殺。相反,你可以使用一個簡單的字符串分割:

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

我真的很喜歡它! 平臺和庫獨立。這真的很酷! – user2007447 2014-11-06 23:29:50

+2

向我投票。如果你閱讀[RFC 2253](https://tools.ietf.org/html/rfc2253#section-3),你會發現你必須考慮邊緣情況,例如轉義逗號'\,'或引用的值。 – 2014-12-17 13:09:10

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; 
1

可以使用cryptacular這是一個Java密碼庫建立在bouncycastle的頂部以便於使用。

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

好好使用@Erdem Memisyazici的建議。 – Ghetolay 2015-08-13 17:29:42

4

一個符合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> 
+0

請注意,Cryptacular 1.1.x系列適用於Java 7和Java 1.2以上的1.2.x.雖然非常好的庫! – 2016-10-20 11:29:41

1

UPDATE:這個類是 「太陽」 包,你應該使用它謹慎。感謝埃米爾的評論:)

只是想分享,以獲取CN,我做的:

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

關於周華健倫德伯格的評論見:Why Developers Should Not Write Programs That Call 'sun' Packages

+0

這是我最喜歡的答案,因爲它簡單易讀,只使用JDK中捆綁的東西。 – 2015-09-24 09:22:44

+0

同意你對使用JDK類的說法:) – Rad 2015-09-25 03:36:27

+3

但是,應該注意的是,javac會警告'X500Name'是內部專有API,可能在未來版本中被刪除。 – 2015-10-02 11:59:30

0

X500Name是JDK的內部FPGA實現,然而,你可以使用反射。

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

發佈至今有一些問題的所有答案:大多數使用內部X500Name或外部賞金城堡依賴。以下內容建立在@ Jakub的答案上,僅使用公共JDK API,但也根據OP的要求提取CN。它也使用Java 8,它站在2017年年中,你真的應該。 BC

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

取得的提取更加容易:

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