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



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



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

749   16  

16 ответов:

вот некоторый код для нового неосуждаемого после установки 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());

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

в качестве альтернативы коду 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.называющий.ldap : - (

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

CertUtil.subjectCN(certificate);

документация: 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>

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

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

все ответы, опубликованные до сих пор, имеют некоторые проблемы: большинство использует внутренний X500Name или зависимость от внешнего замка щедрости. Следующее строится на ответе @Jakub и использует только публичный API JDK, но также извлекает 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;

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

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

UPDATE: этот класс находится в пакете" sun", и вы должны использовать его с осторожностью. Спасибо Эмиль за комментарий :)

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

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

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

извлечение 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();

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

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

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

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

вот это красиво формат:

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

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

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

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

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

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

BC сделал извлечение намного проще:

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

выражения регулярных выражений, довольно дороги в использовании. Для такой простой задачи это, вероятно, будет более убить. Вместо этого вы можете использовать простое разделение строк:

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

Comments

    Ничего не найдено.