Java'da X509Certificate'dan CN nasıl çıkarılır?


92

Bir SslServerSocketve istemci sertifikaları kullanıyorum ve CN'yi SubjectDN'den istemcininkinden çıkarmak istiyorum X509Certificate.

Şu anda arıyorum cert.getSubjectX500Principal().getName()ama bu elbette bana müşterinin toplam biçimlendirilmiş DN'sini veriyor. Nedense CN=theclientDN kısmıyla ilgileniyorum . Dize'yi kendim ayrıştırmadan DN'nin bu bölümünü çıkarmanın bir yolu var mı?



2
@AhmadAbdelghany Sorumun bağlantılı olandan yaklaşık 1,5 yıl daha eski olduğunu fark ettiniz mi? Yani eğer bir şey varsa, diğeri benim kopyam :-)
Martin C.

Doğru tespit. Diğerini işaretleyeceğim.
Ahmad Abdelghany

Akış çözümü Abhijit Sarkar bağlantı açıklamasını buraya girin iyi çalışıyor!
Christian M.

Yanıtlar:


90

Yeni, kullanımdan kaldırılmamış BouncyCastle API'sinin bazı kodları aşağıda verilmiştir. Hem bcmail hem de bcprov dağıtımlarına ihtiyacınız olacak.

X509Certificate cert = ...;

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

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

10
@grak, bu çözümü nasıl bulduğunuzla ilgileniyorum. Kesinlikle API belgelerine baktığımda bunu asla çözemeyecektim.
Elliot Vargas

5
evet, bu duyguyu paylaşıyorum ... Mail listesinde sormam gerekiyordu.
gtrak

7
Geçerli (23 Ekim 2012) BouncyCastle (1.47) kodundaki bu kodun ayrıca bcpkix dağıtımı gerektirdiğini unutmayın.
EwyynTomato

Bir sertifika birden çok CN'ye sahip olabilir. Sadece cn.getFirst () döndürmek yerine, hepsini yinelemeli ve bir CN listesi döndürmelisiniz.
varrunr

5
IETFUtils.valueToStringDoğru bir sonuç üretmek üzere görünmüyor. Baz 64 kodlaması nedeniyle bazı eşitlik işaretlerini içeren bir CN'im var (örneğin AAECAwQFBgcICQoLDA0ODw==). valueToStringYöntem sonuca eğik çizgi geri ekler. Bunun yerine kullanmak toStringişe yarıyor gibi görünüyor. Bunun aslında api'nin doğru bir kullanımı olduğunu belirlemek zordur.
Chris

94

işte başka bir yol. fikir, elde ettiğiniz DN'nin, LDAP DN için kullanılanla aynı olan rfc2253 biçiminde olmasıdır. Öyleyse neden LDAP API'yi yeniden kullanmıyorsunuz?

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
Yay kullanıyorsanız yararlı bir kısayol: LdapUtils.getStringValue (ldapDN, "cn");
Berthier Lemieux


En azından CN üzerinde çalıştığım durum için çok öznitelikli bir RDN içindedir . Başka bir deyişle: önerilen çözüm, RDN'nin öznitelikleri üzerinde yineleme yapmaz. Olması gerekiyor!
peterh

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

Not: İyi bir çözüm gibi görünse de bazı sorunları vardır. "Standart olmayan" alanlarda kod çözme sorunlarını keşfedene kadar bunu birkaç yıl kullanıyordum. CN(Aka 2.5.4.3) gibi iyi bilinen türlere sahip alanlar için Rdn#getValue()a String. Ancak, özel türler için sonuç şu byte[]şekildedir (ile başlayan dahili kodlanmış gösterime dayalı olabilir #). Ofc, byte[]-> Stringmümkündür, ancak ek (öngörülemeyen) karakterler içerir. Bunu BC'ye dayalı @laz çözümleriyle çözdüm, çünkü bunu doğru bir şekilde ele alıyor ve çözüyor String.
knalli

12

Bağımlılık eklemek sorun değilse, bunu X.509 sertifikalarıyla çalışmak için Bouncy Castle'ın API'si ile yapabilirsiniz:

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

Güncelleme

Bu gönderi sırasında, bunu yapmanın yolu buydu. Ancak gtrak'ın yorumlarda bahsettiği gibi, bu yaklaşım artık kullanımdan kaldırılmıştır. Gtrak'ın yeni Bouncy Castle API'sini kullanan güncellenmiş koduna bakın .


X509Name, Bouncycastle 1.46'da kullanımdan kaldırılmış gibi görünüyor ve x500Name'i kullanmayı planlıyorlar. Bununla ilgili bir şey veya aynı şeyi yapmak için tasarlanan alternatif hakkında bir şey biliyor musunuz?
gtrak

Vay canına, yeni API'ye baktığımda, yukarıdaki kodla aynı hedefi nasıl gerçekleştireceğimi bulmakta zorlanıyorum. Belki Bouncycastle posta listesi arşivlerinin bir cevabı olabilir. Anlarsam bu yanıtı güncelleyeceğim.
laz

Bendede aynı sorun var. Bir şey bulursanız lütfen bana bildirin. Bu, aldığım kadarı ile: x500name = X500Name.getInstance (PrincipalUtil.getIssuerX509Principal (cert)); RDN cn = x500name.getRDN'ler (BCStyle.CN) [0];
gtrak

Nasıl yapılacağını bir posta listesi tartışmasıyla buldum, nasıl yapılacağını gösteren bir cevap oluşturdum.
gtrak

İyi bul gtrak. Bir noktada anlamaya çalışmak için 10 dakika harcadım ve bir daha asla geri dönmedim.
laz

9

'' Bcmail '' gerektirmeyen gtrak koduna alternatif olarak:

    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: Yazılımımın Android'de çalıştırılması gerekene kadar çözümünüzü kullandım. Ve Android javax.naming.ldap'i uygulamaz :-(


Bu çözümü
benimseme

8
Bunun ne zaman değiştiğinden emin değilim, ancak şu anda çalışıyor: X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName()); String cn = x500Name.getCommonName();(java 8 kullanarak)
trichner


IETFUtils.valueToStringDeğeri geri kaçan formu. Bunun .toString()yerine benim için çalışmayı basitçe çağırmayı buldum .
holmis83

7

Http://www.cryptacular.org ile bir satır

CertUtil.subjectCN(certificate);

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

Maven bağımlılığı:

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

Cryptacular 1.1.x serisinin Java 7 ve 1.2.x Java 8 için olduğuna dikkat edin. Yine de çok iyi bir kitaplık!
Markus L

6

Şimdiye kadar gönderilen tüm cevapların bir sorunu var: Çoğu dahili X500Nameveya harici Bounty Castle bağımlılığını kullanıyor. Aşağıdakiler @ Jakub'ın cevabına dayanır ve yalnızca genel JDK API'sini kullanır, aynı zamanda OP tarafından istenen CN'yi de çıkarır. Ayrıca, 2017'nin ortasında duran Java 8'i de kullanıyor, gerçekten kullanmalısınız.

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

Benim durumumda, CN çok özellikli bir RDN içindedir . Sanırım bu çözümü geliştirmeniz gerekecek, böylece her bir RDN için RDN öznitelikleri üzerinde yineleme yapacaksınız, daha sonra sadece RDN'nin ilk özniteliğine bakmak yerine, burada örtük olarak yaptığınız şeyin bu olduğunu düşünüyorum.
peterh

4

cert.getSubjectX500Principal().getName()BouncyCastle'a bağımlı olmak istemiyorsanız, bir normal ifade kullanarak bunu nasıl yapacağınız aşağıda açıklanmıştır .

Bu normal ifade, her eşleşme için ayırt edici bir adı, veren nameve valyakalama gruplarını ayrıştırır .

DN dizeleri virgül içerdiğinde, bunların tırnak içine alınması gerekir - bu normal ifade hem tırnak içine alınmış hem de tırnaksız dizeleri doğru bir şekilde işler ve ayrıca tırnaklı dizelerdeki çıkış karakterli tırnakları işler:

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

İşte güzel biçimlendirilmiş:

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

İşte iş başında görebilmeniz için bir bağlantı: https://regex101.com/r/zfZX3f/2

Bir normal ifadenin yalnızca CN'yi almasını istiyorsanız , bu uyarlanmış sürüm bunu yapacaktır:

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


En sağlam cevap. Ayrıca, numarasıyla belirtilen OID'leri bile desteklemek istiyorsanız (örneğin OID.2.5.4.97), izin verilen karakterler [AZ] 'den [AZ, 0-9,]' a
uzatılmalıdır

3

BouncyCastle 1.49'um var ve şu anda sahip olduğu sınıf org.bouncycastle.asn1.x509.Certificate. Koduna baktım IETFUtils.valueToString()- ters eğik çizgi ile bazı süslü kaçışlar yapıyor. Bir alan adı için kötü bir şey olmaz ama daha iyisini yapabileceğimizi düşünüyorum. Durumlarda ben bakmak ettik cn.getFirst().getValue()bütün bir getString () yöntem sağlamaktır ASN1String arabirimini uygulamak olduğunu getiri dizeleri farklı türde. Öyleyse, benim için işe yarayan şey

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

Ters eğik çizgi sorunuyla karşılaştım, bu yüzden bu sorunumu çözdü.
Amber

3

GÜNCELLEME: Bu sınıf "sun" paketindedir ve dikkatli kullanmalısınız. Yorum için teşekkürler Emil :)

Sadece paylaşmak istedim, CN'yi almak için yapıyorum:

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

Emil Lundberg'in yorumu ile ilgili olarak bakınız: Geliştiriciler Neden 'sun' Paketleri Çağrısı Yapan Programlar Yazmamalıdır


Bu, basit, okunabilir ve yalnızca JDK'da bulunanları kullandığı için mevcut cevaplar arasında favorim.
Emil Lundberg

JDK sınıflarının kullanımı hakkında söylediklerinize katılıyorum :)
Rad

3
Bununla birlikte, javac'ın X500Namegelecekteki sürümlerde kaldırılabilecek dahili bir tescilli API olma konusunda uyardığı unutulmamalıdır.
Emil Lundberg

Evet, bağlantılı SSS'yi okuduktan sonra ilk yorumumu iptal etmem gerekiyor. Afedersiniz.
Emil Lundberg

1
Hiç sorun değil. Söylediğin şey gerçekten önemli. Teşekkürler :) Aslında, o sınıfı artık kullanmıyorum: P
Rad

2

Gerçekten de, gtrakistemci sertifikasını almak ve CN'yi çıkarmak için bu büyük olasılıkla işe yarıyor gibi görünüyor.

    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;

Bu ilgili soruyu kontrol edin stackoverflow.com/a/28295134/2413303
EpicPandaForce

1

Kolay kullanım için bouncycastle üzerine inşa edilen bir Java şifreleme kütüphanesi olan cryptacular kullanılabilir.

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

@Erdem Memisyazici önerisini kullansanız iyi olur.
Ghetolay


1

Sertifikadan CN almak o kadar basit değil. Aşağıdaki kod kesinlikle size yardımcı olacaktır.

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

Düz Java ile yapmanın bir yolu daha:

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

Normal ifade ifadelerinin kullanımı oldukça pahalıdır. Böylesine basit bir görev için muhtemelen aşırı öldürme olacaktır. Bunun yerine basit bir String split kullanabilirsiniz:

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

Gerçekten beğendim! Platform ve kitaplık bağımsız. Bu gerçekten havalı!
user2007447

2
Benden olumsuz oy. RFC 2253'ü okursanız , göz önünde bulundurmanız gereken uç durumlar olduğunu göreceksiniz, örneğin, virgülden kaçan \,veya alıntılanan değerler.
Duncan Jones

0

X500Name, JDK'nın dahili uygulamasıdır, ancak yansımayı kullanabilirsiniz.

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, çıkarımı çok daha kolay hale getirdi:

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

X500Name'de herhangi bir .getCommonName()yöntem bulamıyorum .
lapo

(@lapo) gerçekten kullanmadığınızdan emin misiniz sun.security.x509.X500Name- birkaç yıl önce belirtildiği gibi hangisi belgelenmemiş ve güvenilemez?
dave_thompson_085

Eh, org.bouncycastle.asn1.x500.X500Namesınıfın
JavaDoc'unu bağladım,

0

Çok değerli öznitelikler için - LDAP API kullanarak ...

        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;
                        }
                    }
                }
            }
        }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.