Как извлечь CN из X509Certificate в Java?
Я использую сертификаты SslServerSocket
и клиента и хочу извлечь CN из SubjectDN из клиента X509Certificate
.
В данный момент я вызываю cert.getSubjectX500Principal().getName()
, но это, конечно, дает мне полное форматированное DN клиента. По какой-то причине меня просто интересует часть CN=theclient
DN. Есть ли способ извлечь эту часть DN без синтаксического разбора строки?
Ответы
Ответ 1
Вот код для нового не устаревшего 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());
Ответ 2
вот еще один способ. идея заключается в том, что получаемый вами 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());
}
Ответ 3
Если добавление зависимостей не является проблемой, вы можете сделать это с помощью 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.
Ответ 4
В качестве альтернативы 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: - (
Ответ 5
Одна строка с 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>
Ответ 6
Все ответы, опубликованные до сих пор, имеют некоторые проблемы: большинство из них использует внутреннюю X500Name
или внешнюю зависимость Замка Баунти. Следующая сборка на @Jakub отвечает и использует только открытый 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(", "))
Ответ 7
У меня есть 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();
Ответ 8
Вот как это сделать с помощью регулярного выражения над cert.getSubjectX500Principal().getName()
, если вы не хотите получать зависимость от BouncyCastle.
Это регулярное выражение будет анализировать отличительное имя, давая name
и val
групп захвата для каждого совпадения.
Когда строки DN содержат запятые, они должны быть заключены в кавычки - это регулярное выражение правильно обрабатывает как строки в кавычках, так и строки в кавычках, а также обрабатывает экранированные кавычки в строках в кавычках:
(?:^|,\s?)(?:(?<name>[AZ]+)=(?<val>"(?:[^"]|"")+"|[^,]+))+
Вот красиво отформатировано:
(?:^|,\s?)
(?:
(?<name>[A-Z]+)=
(?<val>"(?:[^"]|"")+"|[^,]+)
)+
Вот ссылка, чтобы вы могли увидеть ее в действии: https://regex101.com/r/zfZX3f/2
Если вы хотите, чтобы регулярное выражение получало только CN, то эта адаптированная версия сделает это:
(?:^|,\s?)(?:CN=(?<val>"(?:[^"]|"")+"|[^,]+))
Ответ 9
В самом деле, благодаря 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;
Ответ 10
Для удобства использования cryptacular можно использовать для создания криптографической библиотеки Java поверх bouncycastle.
RDNSequence dn = new NameReader(cert).readSubject();
return dn.getValue(StandardAttributeType.CommonName);
Ответ 11
UPDATE: этот класс находится в пакете "sun", и вы должны использовать его с осторожностью. Спасибо Emil за комментарий:)
Просто хотел поделиться, чтобы получить CN, я делаю:
X500Name.asX500Name(cert.getSubjectX500Principal()).getCommonName();
Относительно комментария Эмиля Лундберга: Почему разработчики не должны писать программы, которые называют "sun" Packages
Ответ 12
Получение 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();
Ответ 13
Вы можете попробовать использовать getName (X500Principal.RFC2253, oidMap) или getName(X500Principal.CANONICAL, oidMap)
, чтобы увидеть, какой из них форматирует строку DN лучше. Возможно, одним из значений карты oidMap
будет строка, которую вы хотите.
Ответ 14
Выражения регулярных выражений довольно дороги в использовании. Для такой простой задачи это, вероятно, будет больше, чем убийство. Вместо этого вы можете использовать простое разделение строк:
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 "";
}
Ответ 15
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);
}
Ответ 16
BC сделала извлечение намного проще:
X500Principal principal = x509Certificate.getSubjectX500Principal();
X500Name x500name = new X500Name(principal.getName());
String cn = x500name.getCommonName();
Ответ 17
Для многозначных атрибутов - используя 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;
}
}
}
}
}