Używam SslServerSocket
certyfikatów a i klienta i chcę wyodrębnić CN z SubjectDN z klienta X509Certificate
.
W tej chwili dzwonię, cert.getSubjectX500Principal().getName()
ale to oczywiście daje mi całkowitą sformatowaną nazwę DN klienta. Z jakiegoś powodu interesuje mnie tylko CN=theclient
część DN. Czy istnieje sposób na wyodrębnienie tej części nazwy wyróżniającej bez samodzielnego analizowania ciągu?
java
ssl
x509certificate
x509
Martin C.
źródło
źródło
Odpowiedzi:
Oto fragment kodu nowego niezastąpionego interfejsu API BouncyCastle. Będziesz potrzebować dystrybucji bcmail i bcprov.
X509Certificate cert = ...; X500Name x500name = new JcaX509CertificateHolder(cert).getSubject(); RDN cn = x500name.getRDNs(BCStyle.CN)[0]; return IETFUtils.valueToString(cn.getFirst().getValue());
źródło
IETFUtils.valueToString
że nie daje prawidłowego wyniku. Mam CN, która zawiera kilka znaków równości z powodu kodowania podstawowego 64 (npAAECAwQFBgcICQoLDA0ODw==
.).valueToString
Metoda dodaje ukośniki z powrotem do wyniku. Zamiast tegotoString
wydaje się , że używanie działa. Trudno jest ustalić, czy w rzeczywistości jest to prawidłowe użycie interfejsu API.tutaj jest inny sposób. pomysł polega na tym, że otrzymana nazwa wyróżniająca ma format rfc2253, który jest taki sam, jak używany w przypadku nazwy wyróżniającej LDAP. Dlaczego więc nie wykorzystać ponownie interfejsu API LDAP?
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()); }
źródło
String commonName = new LdapName(certificate.getSubjectX500Principal().getName()).getRdns().stream() .filter(i -> i.getType().equalsIgnoreCase("CN")).findFirst().get().getValue().toString();
CN
(aka2.5.4.3
),Rdn#getValue()
zawieraString
. Jednak w przypadku typów niestandardowych wynik jestbyte[]
(może być oparty na wewnętrznej zakodowanej reprezentacji zaczynającej się od#
). Ofc,byte[]
->String
jest możliwe, ale zawiera dodatkowe (nieprzewidywalne) znaki. Rozwiązałem to za pomocą rozwiązań @laz opartych na BC, ponieważ obsługuje i dekoduje to poprawnie wString
.Jeśli dodanie zależności nie stanowi problemu, możesz to zrobić za pomocą API Bouncy Castle do pracy z certyfikatami 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);
Aktualizacja
W chwili pisania tego posta był to sposób na zrobienie tego. Jak jednak gtrak wspomina w komentarzach, to podejście jest obecnie przestarzałe. Zobacz zaktualizowany kod gtrak, który używa nowego interfejsu API Bouncy Castle.
źródło
Jako alternatywa dla kodu gtrak, który nie potrzebuje '' 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: Korzystałem z twojego rozwiązania, dopóki moje oprogramowanie nie musiało być uruchomione na Androidzie. A Android nie implementuje javax.naming.ldap :-(
źródło
X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName()); String cn = x500Name.getCommonName();
(używając Java 8)IETFUtils.valueToString
Zwraca wartość uciekł formie. Zauważyłem, że po prostu wywoływanie.toString()
zamiast tego działa dla mnie.Jedna linia z http://www.cryptacular.org
JavaDoc: http://www.cryptacular.org/javadocs/org/cryptacular/util/CertUtil.html#subjectCN(java.security.cert.X509Certificate)
Zależność Mavena:
<dependency> <groupId>org.cryptacular</groupId> <artifactId>cryptacular</artifactId> <version>1.1.0</version> </dependency>
źródło
Wszystkie opublikowane do tej pory odpowiedzi mają pewien problem: większość korzysta z wewnętrznej
X500Name
lub zewnętrznej zależności Bounty Castle. Poniższy tekst opiera się na odpowiedzi @ Jakub i używa tylko publicznego interfejsu API JDK, ale także wyodrębnia CN zgodnie z prośbą OP. Wykorzystuje również Javę 8, którą w połowie 2017 roku naprawdę powinieneś.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(", "))
źródło
Oto jak to zrobić, używając wyrażenia regularnego
cert.getSubjectX500Principal().getName()
, na wypadek, gdybyś nie chciał przyjmować zależności od BouncyCastle.To wyrażenie regularne przeanalizuje nazwę wyróżniającą, podając
name
ival
przechwytuj grupy dla każdego dopasowania.Kiedy łańcuchy DN zawierają przecinki, należy je cytować - to wyrażenie regularne poprawnie obsługuje zarówno cytowane, jak i niecytowane ciągi, a także obsługuje cudzysłowy w cudzysłowach:
(?:^|,\s?)(?:(?<name>[A-Z]+)=(?<val>"(?:[^"]|"")+"|[^,]+))+
Oto ładnie sformatowany:
(?:^|,\s?) (?: (?<name>[A-Z]+)= (?<val>"(?:[^"]|"")+"|[^,]+) )+
Oto link, dzięki któremu możesz zobaczyć to w akcji: https://regex101.com/r/zfZX3f/2
Jeśli chcesz, aby wyrażenie regularne otrzymywało tylko CN, zrobi to ta dostosowana wersja:
(?:^|,\s?)(?:CN=(?<val>"(?:[^"]|"")+"|[^,]+))
źródło
Mam BouncyCastle 1.49, a klasa, którą ma teraz, to org.bouncycastle.asn1.x509.Certificate. Zajrzałem do kodu
IETFUtils.valueToString()
- robi trochę wymyślnej ucieczki z ukośnikiem odwrotnym. W przypadku nazwy domeny nie zrobiłoby to nic złego, ale czuję, że możemy zrobić to lepiej. W przypadkach, w których patrzę,cn.getFirst().getValue()
zwraca różne rodzaje ciągów, z których wszystkie implementują interfejs ASN1String, który ma zapewnić metodę getString (). Więc to, co wydaje mi się działać, toCertificate c = ...; RDN cn = c.getSubject().getRDNs(BCStyle.CN)[0]; return ((ASN1String)cn.getFirst().getValue()).getString();
źródło
AKTUALIZACJA: Ta klasa jest w pakiecie "słońce" i należy jej używać z rozwagą. Dzięki Emilowi za komentarz :)
Chciałem się tylko udostępnić, aby dostać CN, robię:
Jeśli chodzi o komentarz Emila Lundberga, zobacz: Dlaczego programiści nie powinni pisać programów, które nazywają pakiety „sun”
źródło
X500Name
byciem wewnętrznym zastrzeżonym API, które może zostać usunięte w przyszłych wydaniach.Rzeczywiście, dzięki
gtrak
temu wydaje się, że aby uzyskać certyfikat klienta i wyodrębnić CN, najprawdopodobniej działa.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;
źródło
Przydałoby się cryptacular, który jest biblioteką kryptograficzną Javy zbudowaną na szczycie bouncycastle, aby była łatwa w użyciu.
RDNSequence dn = new NameReader(cert).readSubject(); return dn.getValue(StandardAttributeType.CommonName);
źródło
Możesz spróbować użyć getName (X500Principal.RFC2253, oidMap) lub
getName(X500Principal.CANONICAL, oidMap)
sprawdzić, który z nich najlepiej formatuje łańcuch nazwy wyróżniającej. Może jedna zoidMap
wartości mapy będzie żądanym ciągiem.źródło
Pobieranie CN z certyfikatu nie jest takie proste. Poniższy kod na pewno Ci pomoże.
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();
źródło
Jeszcze jeden sposób na zwykłą Javę:
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); }
źródło
Wyrażenia Regex są dość drogie w użyciu. W przypadku tak prostego zadania prawdopodobnie będzie to przesada. Zamiast tego możesz użyć prostego podziału String:
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 ""; }
źródło
\,
lub cudzysłowy.X500Name to wewnętrzna implementacja JDK, jednak możesz użyć odbicia.
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); }
źródło
BC znacznie ułatwił ekstrakcję:
X500Principal principal = x509Certificate.getSubjectX500Principal(); X500Name x500name = new X500Name(principal.getName()); String cn = x500name.getCommonName();
źródło
.getCommonName()
metody w X500Name .sun.security.x509.X500Name
- które, jak zauważyły inne odpowiedzi kilka lat wcześniej, jest nieudokumentowane i nie można na nich polegać?org.bouncycastle.asn1.x500.X500Name
klasę JavaDoc , która nie pokazuje tej metody…W przypadku atrybutów wielowartościowych - korzystanie z interfejsu API LDAP ...
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; } } } } }
źródło