जावा में CN50 X सर्टिफिकेट से CN कैसे निकाले?


91

मैं एक SslServerSocketऔर ग्राहक प्रमाणपत्रों का उपयोग कर रहा हूं और ग्राहक के विषय से सीएन को निकालना चाहता हूं X509Certificate

फिलहाल मैं फोन करता हूं, cert.getSubjectX500Principal().getName()लेकिन यह निश्चित रूप से मुझे क्लाइंट का कुल स्वरूपित डीएन देता है। किसी कारण से मैं सिर्फ CN=theclientडीएन के हिस्से में दिलचस्पी रखता हूं । क्या डीआर के इस हिस्से को खुद को स्ट्रिंग करने के बिना निकालने का एक तरीका है?



2
@ अहदअबदेल्घनी आपको एहसास हुआ, कि मेरा सवाल एक लिंक से 1.5 साल पुराना है? इसलिए अगर कुछ भी हो, तो दूसरा मेरा एक डुप्लिकेट है :-)
मार्टिन सी।

निष्पक्ष बिंदु। मैं दूसरे को झंडा दूंगा।
अहमद अब्देलघानी

स्ट्रीम समाधान अभिजीत सरकार लिंक विवरण दर्ज करें यहाँ ठीक काम करता है!
क्रिश्चियन एम।

जवाबों:


90

नए गैर-वंचित 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());

9
@grak, मुझे इस बात में दिलचस्पी है कि आपने इसका समाधान कैसे निकाला। निश्चित रूप से सिर्फ एपीआई प्रलेखन को देखने से मैं कभी भी यह पता लगाने में सक्षम नहीं था।
इलियट वर्गास

5
हाँ, मैं उस भावना को साझा करता हूं ... मुझे मेलिंग सूची पर पूछना पड़ा।
gtrak

7
ध्यान दें कि वर्तमान (23 अक्टूबर 2012) BouncyCastle (1.47) कोड को भी bcpkix वितरण की आवश्यकता है।
EwyynTomato

एक प्रमाणपत्र में कई CN हो सकते हैं। केवल cn.getFirst () वापस करने के बजाय आपको सभी के माध्यम से पुनरावृति करना चाहिए और CNs की सूची वापस करनी चाहिए।
वरुण्र

5
IETFUtils.valueToStringएक सही परिणाम का उत्पादन करने के प्रकट नहीं होता है। मेरे पास एक CN है जिसमें आधार 64 एन्कोडिंग (जैसे AAECAwQFBgcICQoLDA0ODw==) के कारण कुछ समान संकेत शामिल हैं । valueToStringविधि परिणाम को स्लैश वापस कहते हैं। इसके एवज में, का उपयोग toStringकर काम करने लगता है। यह निर्धारित करना मुश्किल है कि यह वास्तव में एपीआई का सही उपयोग है।
क्रिस

94

यहाँ एक और तरीका है। विचार यह है कि आपके द्वारा प्राप्त डीएन 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());
}

1
यदि आप वसंत का उपयोग कर रहे हैं तो एक उपयोगी शॉर्टकट: LdapUtils.getStringValue (ldapDN, "cn");
ब्यूटिएर लेमीक्स

कृपया मेरे प्रश्न पर दृष्टि रखें: stackoverflow.com/questions/40613147/…
होसिन अकाजानी

कम से कम जिस मामले के लिए मैं CN पर काम कर रहा हूं, वह मल्टी-फ़ीचर RDN के भीतर है। दूसरे शब्दों में: प्रस्तावित समाधान RDN की विशेषताओं पर पुनरावृति नहीं करता है। यह होना चाहिए!
पीटर सिप

String commonName = new LdapName(certificate.getSubjectX500Principal().getName()).getRdns().stream() .filter(i -> i.getType().equalsIgnoreCase("CN")).findFirst().get().getValue().toString();
रेटो होनर

नोट: हालांकि यह एक अच्छा समाधान की तरह दिखता है, इसके कुछ मुद्दे हैं। मैं कुछ वर्षों से इस का उपयोग कर रहा था जब तक कि मैंने "गैर मानक" क्षेत्रों के साथ डिकोडिंग मुद्दों की खोज नहीं की। ऐसे प्रकारों जैसे कि CN(उर्फ 2.5.4.3) जैसे क्षेत्रों के लिए Rdn#getValue()a String। हालांकि, कस्टम प्रकारों के लिए, परिणाम है byte[](शायद एक आंतरिक एन्कोडेड प्रतिनिधित्व पर आधारित है #)। Ofc, byte[]-> Stringसंभव है, लेकिन इसमें अतिरिक्त (अप्रत्याशित) वर्ण हैं। मैंने इसे BC पर आधारित @laz समाधान के साथ हल किया है, क्योंकि यह इसे सही ढंग से संभालता है और डीकोड करता है String
knalli

12

यदि निर्भरता जोड़ना कोई समस्या नहीं है तो आप 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 का उल्लेख है, लेकिन यह दृष्टिकोण अब पदावनत हो गया है। Gtrak का अपडेटेड कोड देखें जो नए Bouncy Castle API का उपयोग करता है।


ऐसा लगता है कि X509Name को B Councilycastle 1.46 में पदावनत किया गया है, और वे x500Name का उपयोग करने का इरादा रखते हैं। उसी के बारे में कुछ भी जानिए या फिर वही काम करने के लिए इच्छित विकल्प?

वाह, नए एपीआई को देखकर मुझे एक कठिन समय मिल रहा है कि उपरोक्त कोड के समान लक्ष्य को कैसे पूरा किया जाए। शायद B Councilycastle मेलिंग सूची अभिलेखागार का जवाब हो सकता है। यदि मैं इसका पता लगाता हूं तो मैं इस उत्तर को अपडेट करूंगा।
लाज़

मुझे भी यही तकलीफ़ है। कृपया मुझे बताएं कि क्या आप कुछ भी लेकर आते हैं। यह जहाँ तक मैंने प्राप्त किया है: x500name = X500Name.getInstance (PrincipalUtil.getIssuerX509Principal (cert)); RDN cn = x500name.getRDNs (BCStyle.CN) [0];
gtrak

मैंने पाया कि इसे मेलिंग सूची चर्चा के माध्यम से कैसे किया जाता है, मैंने एक उत्तर बनाया जो दिखाता है कि कैसे।
gtrak

अच्छा लगता है gtrak। मैंने 10 मिनट बिताए और एक बिंदु पर यह पता लगाने की कोशिश की कि वह कभी इसके आसपास नहीं गया।
laz

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

@ जैकब: मैंने आपके समाधान का उपयोग तब तक किया है जब तक कि मेरे एसडब्ल्यू को एंड्रॉइड पर नहीं चलाया जाना था। और Android javax.naming.ldap को लागू नहीं करता है :-(


ठीक यही कारण है कि मैं इस समाधान के साथ कैम करता हूं: एंड्रॉइड पर पोर्टिंग ...
इविन

8
यकीन नहीं है कि यह कब बदल गया है, लेकिन यह अब काम करता है: X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName()); String cn = x500Name.getCommonName();(जावा 8 का उपयोग करके)
ट्रायकनर

कृपया मेरे प्रश्न पर दृष्टि रखें: stackoverflow.com/questions/40613147/…
होज़िन अकाजानी

IETFUtils.valueToStringमें मान देता है बच गए प्रपत्र। मैंने पाया कि .toString()मेरे लिए काम करने के बजाय बस इनवॉइस करना है।
holmis83

7

Http://www.cryptacular.org के साथ एक लाइन

CertUtil.subjectCN(certificate);

JavaDoc: http://www.cryptacular.org/javadocs/org/cryptacular/util/CertUtil.html#subjectCN(java.security.cert.X509Certificate)

मावेन निर्भरता:

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

ध्यान दें कि Cryptacular 1.1.x Series Java 7 और 1.2.x के लिए Java 8 के लिए है। बहुत अच्छी लाइब्रेरी है, हालाँकि!
मार्कस एल

6

अब तक पोस्ट किए गए सभी उत्तरों में कुछ समस्या है: ज्यादातर आंतरिक X500Nameया बाहरी बाउंटी कैसल निर्भरता का उपयोग करते हैं । निम्नलिखित @ जकब के उत्तर पर बनाता है और केवल सार्वजनिक JDK एपीआई का उपयोग करता है, लेकिन ओपी द्वारा पूछे गए अनुसार सीएन को भी निकालता है। यह जावा 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(", "))

मेरे मामले में CN एक बहु-विशेषता RDN के भीतर है। मुझे लगता है कि आपको इस समाधान को बढ़ाने की आवश्यकता होगी ताकि प्रत्येक आरडीएन के लिए आप आरडीएन विशेषताओं पर पुनरावृति कर सकें, बल्कि फिर आरडीएन के पहले विशेषता को देख रहे हैं, जो मुझे लगता है कि आप यहाँ क्या कर रहे हैं।
पीटर सिप

4

cert.getSubjectX500Principal().getName()यदि आप बाउंसीकैस्टल पर निर्भरता नहीं लेना चाहते हैं, तो रेगेक्स ओवर का उपयोग करके इसे कैसे करें ।

यह रेगेक्स प्रत्येक मैच के लिए एक विशिष्ट नाम, देने nameऔर valकैप्चर करने वाले समूहों को पार्स करेगा ।

जब डीएन स्ट्रिंग्स में कॉमा होते हैं, तो उन्हें उद्धृत किया जाता है - यह रेगेक्स दोनों उद्धृत किए गए और सही ढंग से स्ट्रिंग्स को संभालता है, और उद्धृत स्ट्रिंग्स में बच गए उद्धरणों को भी संभालता है:

(?:^|,\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.b Councilycastle.asn1.x509.Certificate है। मैं के कोड में देखा IETFUtils.valueToString()- यह कुछ फैंसी backslashes के साथ भागने कर रहा है। एक डोमेन नाम के लिए यह कुछ भी बुरा नहीं करेगा, लेकिन मुझे लगता है कि हम बेहतर कर सकते हैं। जिन मामलों में मैंने cn.getFirst().getValue()रिटर्न देखा है वे विभिन्न प्रकार के स्ट्रिंग्स हैं जो सभी ASN1String इंटरफ़ेस को लागू करते हैं, जो कि गेटस्ट्रिंग () विधि प्रदान करने के लिए है। तो, जो मेरे लिए काम करने लगता है

Certificate c = ...;
RDN cn = c.getSubject().getRDNs(BCStyle.CN)[0];
return ((ASN1String)cn.getFirst().getValue()).getString();

मैं बैकस्लैश समस्या में भाग गया, इसलिए इसने मेरा मुद्दा ठीक कर दिया।
एम्बर

3

अद्यतन: यह वर्ग "सन" पैकेज में है और आपको इसे सावधानी के साथ उपयोग करना चाहिए। टिप्पणी के लिए धन्यवाद एमिल :)

बस, मैं CN पाने के लिए शेयर करना चाहता था:

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

एमिल लुंडबर्ग की टिप्पणी के बारे में देखें: क्यों डेवलपर्स को प्रोग्राम नहीं लिखना चाहिए जो 'सूरज' पैकेज कहते हैं


यह वर्तमान उत्तरों के बीच मेरा पसंदीदा है क्योंकि यह सरल, पठनीय है और केवल वही उपयोग करता है जो जेडीके में बंडल है।
एमिल लुंडबर्ग

JDK वर्गों का उपयोग करने के बारे में आपने जो कहा उससे सहमत :)
Rad

3
हालांकि, एक ध्यान देना चाहिए कि जेवैक X500Nameआंतरिक मालिकाना एपीआई होने के बारे में चेतावनी देता है जिसे भविष्य के रिलीज में हटाया जा सकता है।
एमिल लुंडबर्ग 11

हाँ, लिंक किए गए FAQ को पढ़ने के बाद मुझे अपनी पहली टिप्पणी को रद्द करना होगा। माफ़ करना।
एमिल लुंडबर्ग

1
बिल्कुल भी परेशानी नहीं है। आपने जो बताया वह वास्तव में महत्वपूर्ण है। धन्यवाद :) वास्तव में, मैं उस वर्ग को किसी भी अधिक का उपयोग नहीं करता हूं: पी
रेड

2

दरअसल, इसके लिए धन्यवाद gtrakप्रकट होता है कि क्लाइंट प्रमाण पत्र प्राप्त करने और सीएन को निकालने के लिए, यह सबसे अधिक संभावना है।

    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

आसानी से उपयोग के लिए बाउंसिलस्टल के शीर्ष पर एक जावा क्रिप्टोग्राफ़िक लाइब्रेरी बिल्ड जो क्रिप्टोग्राफ़िक का उपयोग कर सकता है।

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

अच्छी तरह से बेहतर @Erdem Memisyazici सुझाव का उपयोग करें।
घेटोले

1

आप getName (X500Principal.RFC2253, oidMap) का उपयोग करने की कोशिश कर सकते हैं या getName(X500Principal.CANONICAL, oidMap)यह देख सकते हैं कि कौन सा डीएन स्ट्रिंग सबसे अच्छा है। शायद oidMapमानचित्र मानों में से एक वह स्ट्रिंग होगा जो आप चाहते हैं।


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

सादे जावा के साथ करने का एक और तरीका:

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

Regex अभिव्यक्तियाँ, उपयोग करने के लिए महंगी हैं। इस तरह के एक सरल कार्य के लिए यह संभवतः एक ओवर किल होगा। इसके बजाय आप एक साधारण स्ट्रिंग विभाजन का उपयोग कर सकते हैं:

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

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) क्या आप सुनिश्चित हैं कि आप वास्तव में उपयोग नहीं कर रहे हैं sun.security.x509.X500Name- जो कि अन्य उत्तर के रूप में कई साल पहले नोट किया गया है, अविभाजित है और उस पर भरोसा नहीं किया जा सकता है?
dave_thompson_085

खैर, मैंने org.bouncycastle.asn1.x500.X500Nameक्लास के JavaDoc को लिंक किया , जो उस तरीके को नहीं दिखाता ...
लैपो

0

बहु-मूल्यवान विशेषताओं के लिए - 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;
                        }
                    }
                }
            }
        }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.