Estou usando um SslServerSocket
e certificados de cliente e desejo extrair o CN do SubjectDN do cliente X509Certificate
.
No momento eu ligo, cert.getSubjectX500Principal().getName()
mas é claro que isso me dá o DN formatado total do cliente. Por alguma razão, estou interessado apenas na CN=theclient
parte do DN. Existe uma maneira de extrair essa parte do DN sem analisar a String sozinho?
java
ssl
x509certificate
x509
Martin C.
fonte
fonte
Respostas:
Aqui está um código para a nova API BouncyCastle não obsoleta. Você precisará das distribuições bcmail e bcprov.
X509Certificate cert = ...; X500Name x500name = new JcaX509CertificateHolder(cert).getSubject(); RDN cn = x500name.getRDNs(BCStyle.CN)[0]; return IETFUtils.valueToString(cn.getFirst().getValue());
fonte
IETFUtils.valueToString
não parece produzir um resultado correto. Eu tenho um CN que inclui alguns sinais de igual por causa da codificação de base 64 (por exemploAAECAwQFBgcICQoLDA0ODw==
). OvalueToString
método adiciona barras invertidas ao resultado. Em vez disso, o usotoString
parece estar funcionando. É difícil determinar se esse é de fato um uso correto da API.aqui está outra maneira. a ideia é que o DN que você obtém está no formato rfc2253, que é o mesmo usado para o DN do LDAP. Então, por que não reutilizar a 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()); }
fonte
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()
contém aString
. No entanto, para tipos personalizados, o resultado ébyte[]
(talvez baseado em uma representação codificada interna começando com#
). Ofc,byte[]
->String
é possível, mas contém caracteres adicionais (imprevisíveis). Eu resolvi isso com soluções @laz baseadas em BC, porque ele lida e decodifica isso corretamente noString
.Se adicionar dependências não for um problema, você pode fazer isso com a API do Bouncy Castle para trabalhar com certificados 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);
Atualizar
No momento desta postagem, essa era a maneira de fazer isso. Como o gtrak menciona nos comentários, no entanto, essa abordagem agora está obsoleta. Veja o código atualizado do gtrak que usa a nova API Bouncy Castle.
fonte
Como alternativa ao código do gtrak que não precisa de '' 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: Usei sua solução até que meu SW teve que ser executado no Android. E o Android não implementa javax.naming.ldap :-(
fonte
X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName()); String cn = x500Name.getCommonName();
(usando java 8)IETFUtils.valueToString
retorna o valor em formato de escape . Descobri que simplesmente invocar.toString()
funciona para mim.Uma linha com http://www.cryptacular.org
JavaDoc: http://www.cryptacular.org/javadocs/org/cryptacular/util/CertUtil.html#subjectCN(java.security.cert.X509Certificate)
Dependência de Maven:
<dependency> <groupId>org.cryptacular</groupId> <artifactId>cryptacular</artifactId> <version>1.1.0</version> </dependency>
fonte
Todas as respostas postadas até agora têm algum problema: a maioria usa a
X500Name
dependência interna ou externa do Bounty Castle. O seguinte baseia-se na resposta de @Jakub e usa apenas a API JDK pública, mas também extrai o CN conforme solicitado pelo OP. Ele também usa o Java 8, que em meados de 2017, você realmente deveria.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(", "))
fonte
Veja como fazer isso usando um regex
cert.getSubjectX500Principal().getName()
, caso você não queira depender do BouncyCastle.Esta regex analisará um nome distinto, dando
name
eval
um grupo de captura para cada correspondência.Quando as strings de DN contêm vírgulas, elas devem ser colocadas entre aspas - este regex lida corretamente com strings entre aspas e sem aspas, e também lida com aspas escapadas em strings entre aspas:
(?:^|,\s?)(?:(?<name>[A-Z]+)=(?<val>"(?:[^"]|"")+"|[^,]+))+
Aqui está bem formatado:
(?:^|,\s?) (?: (?<name>[A-Z]+)= (?<val>"(?:[^"]|"")+"|[^,]+) )+
Aqui está um link para que você possa vê-lo em ação: https://regex101.com/r/zfZX3f/2
Se você quiser que um regex obtenha apenas o CN, esta versão adaptada fará isso:
(?:^|,\s?)(?:CN=(?<val>"(?:[^"]|"")+"|[^,]+))
fonte
Tenho o BouncyCastle 1.49 e a classe que ele tem agora é org.bouncycastle.asn1.x509.Certificate. Eu olhei para o código de
IETFUtils.valueToString()
- está escapando com barras invertidas. Para um nome de domínio, não faria nada de mal, mas sinto que podemos fazer melhor. Nos casos que observei,cn.getFirst().getValue()
retornos diferentes tipos de strings que implementam a interface ASN1String, que existe para fornecer um método getString (). Então, o que parece funcionar para mim éCertificate c = ...; RDN cn = c.getSubject().getRDNs(BCStyle.CN)[0]; return ((ASN1String)cn.getFirst().getValue()).getString();
fonte
ATUALIZAÇÃO: Esta classe está no pacote "sun" e você deve usá-la com cautela. Obrigado Emil pelo comentário :)
Só queria compartilhar, para conseguir o CN, eu:
Sobre o comentário de Emil Lundberg, veja: Por que os desenvolvedores não devem escrever programas que chamam pacotes 'sun'
fonte
X500Name
ser uma API proprietária interna que pode ser removida em versões futuras.Na verdade, graças a
gtrak
ele parece que, para obter o certificado do cliente e extrair o CN, isso provavelmente funcionará.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;
fonte
Poderia usar cryptacular que é uma biblioteca criptográfica Java construída em cima do bouncycastle para fácil uso.
RDNSequence dn = new NameReader(cert).readSubject(); return dn.getValue(StandardAttributeType.CommonName);
fonte
Você pode tentar usar getName (X500Principal.RFC2253, oidMap) ou
getName(X500Principal.CANONICAL, oidMap)
ver qual formata melhor a string DN. Talvez um dosoidMap
valores do mapa seja a string que você deseja.fonte
Buscar o CN do certificado não é tão simples. O código abaixo definitivamente irá ajudá-lo.
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();
fonte
Mais uma maneira de fazer com Java simples:
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); }
fonte
Expressões Regex são bastante caras para usar. Para uma tarefa tão simples, provavelmente será uma matança exagerada. Em vez disso, você pode usar uma divisão de string simples:
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 ""; }
fonte
\,
ou valores entre aspas.X500Name é uma implementação interna do JDK, no entanto, você pode usar reflexão.
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); }
fonte
BC tornou a extração muito mais fácil:
X500Principal principal = x509Certificate.getSubjectX500Principal(); X500Name x500name = new X500Name(principal.getName()); String cn = x500name.getCommonName();
fonte
.getCommonName()
método em X500Name .sun.security.x509.X500Name
- o que, como outras respostas observadas vários anos antes, não é documentado e não é confiável?org.bouncycastle.asn1.x500.X500Name
classe, que não mostra esse método ...Para atributos de vários valores - usando a 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; } } } } }
fonte
Com Spring Security é possível usar
SubjectDnX509PrincipalExtractor
:X509Certificate certificate = ...; new SubjectDnX509PrincipalExtractor().extractPrincipal(certificate).toString();
fonte