Как извлечь CN из X509Certificate в Java?

Я использую SslServerSocket и клиентские сертификаты и хочу извлечь CN из SubjectDN из X509Certificate клиента.

На данный момент я вызываю cert.getSubjectX500Principal().getName() но это, конечно, дает мне полный формат DN клиента. По какой-то причине меня просто интересует CN=theclient часть DN. Есть ли способ извлечь эту часть DN без синтаксического parsingа строки?

Вот код для нового не устаревшего API BouncyCastle. Вам понадобятся как bcmail, так и bcprov.

 X509Certificate cert = ...; X500Name x500name = new JcaX509CertificateHolder(cert).getSubject(); RDN cn = x500name.getRDNs(BCStyle.CN)[0]; return IETFUtils.valueToString(cn.getFirst().getValue()); 

вот еще один способ. идея заключается в том, что получаемый вами DN находится в формате rfc2253, который аналогичен используемому для LDAP DN. Так почему бы не повторно использовать 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()); } 

Если добавление зависимостей не является проблемой, вы можете сделать это с помощью API Bouncy Castle для работы с сертификатами 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, в котором используется новый API Bouncy Castle.

В качестве альтернативы коду 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 🙁

Одна строка с http://www.cryptacular.org

 CertUtil.subjectCN(certificate); 

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

Зависимость от Maven:

  org.cryptacular cryptacular 1.1.0  

У меня есть BouncyCastle 1.49, и теперь class, который у него есть, – org.bouncycastle.asn1.x509.Certificate. Я просмотрел код IETFUtils.valueToString() – он делает некоторое причудливое экранирование с IETFUtils.valueToString() . Для доменного имени это не принесло бы ничего плохого, но я чувствую, что мы можем сделать лучше. В случаях, когда я смотрю cn.getFirst().getValue() возвращает разные типы строк, которые реализуют интерфейс ASN1String, который должен предоставить метод getString (). Итак, для меня, похоже, работает

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

Все ответы, опубликованные до сих пор, имеют некоторые проблемы: большинство из них используют внутреннее имя X500Name или внешнюю зависимость Bounty Castle. Следующий compilation основан на ответе @ Jakub и использует только общеansible JDK API, но также извлекает CN, как просил OP. Он также использует Java 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(", ")) 

Действительно, благодаря 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; 

Могу использовать криптовакулярный, который представляет собой сборку криптографической библиотеки Java поверх bouncycastle для удобства использования.

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

ОБНОВЛЕНИЕ: этот class находится в пакете «солнце», и вы должны использовать его с осторожностью. Спасибо Эмилю за комментарий 🙂

Просто хотел поделиться, чтобы получить CN, я делаю:

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

Что касается комментария Эмиля Лундберга, см.: Почему разработчики не должны писать программы, которые называют «солнечными» пакетами

Получение 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(); 

Вот как это сделать, используя regex над cert.getSubjectX500Principal().getName() , если вы не хотите брать зависимость от BouncyCastle.

Это регулярное выражение будет анализировать отличительное имя, указывая name и val группы захвата для каждого совпадения.

Когда строки DN содержат запятые, они предназначены для цитирования – это регулярное выражение правильно обрабатывает строки с кавычками и недочетами, а также обрабатывает экранированные кавычки в цитируемых строках:

(?:^|,\s?)(?:(?[AZ]+)=(?"(?:[^"]|"")+"|[^,]+))+

Вот красиво отформатировано:

 (?:^|,\s?) (?: (?[AZ]+)= (?"(?:[^"]|"")+"|[^,]+) )+ 

Вот ссылка, чтобы вы могли увидеть ее в действии: https://regex101.com/r/zfZX3f/2

Если вы хотите, чтобы регулярное выражение получило только CN, тогда эта адаптированная версия сделает это:

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

X500Name – это внутренняя реализация JDK, однако вы можете использовать reflection.

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

Вы можете попробовать использовать getName (X500Principal.RFC2253, oidMap) или getName(X500Principal.CANONICAL, oidMap) чтобы увидеть, какой из них форматирует строку DN лучше. Возможно, одним из oidMap карты oidMap будет строка, которую вы хотите.

BC значительно облегчил извлечение:

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

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

Не удалось решить: com.google.firebase: firebase-core: 11.2.0

дата Сортировка проблемы с JQuery Tablesorter

Идентификатор автоинкремента Hibernate

Преобразование растровых изображений в один многостраничный TIFF-образ в .NET 2.0

Получить объект JavaScript из массива объектов по значению свойства

Как подмножить данные в R без потери строк NA?

Должен ли оператор << быть реализован как друг или как функция-член?

Как сделать условную автоматическую проводку весной?

Каковы некоторые хорошие примеры JQuery, использующие JSONP, говорящие с .net?

Почему я получаю X. в именах столбцов при чтении фрейма данных?

В чем разница между ключевыми словами «ref» и «out»?

Пользовательский навигатор ios 11 переходит в строку состояния

Может ли какой-то хакер украсть файл cookie у пользователя и войти с этим именем на веб-сайт?

Предупреждение загрузки Google Chrome

Действие страницы расширения Chrome, появившееся за пределами адресной строки

Давайте будем гением компьютера.