c# - ¿Cómo encuentro programáticamente qué certificado se usó para firmar un certificado dado?
.net ssl (2)
En mi código C # tengo un objeto X509Certificate2
que representa un certificado SSL (de un almacén local o de una solicitud HTTP exitosa sobre SSL). El certificado está firmado con algún certificado intermedio que quizás esté presente en la tienda local, tal vez no, por lo que el uso de X509Chain.Build()
probablemente no funcione.
Una imagen del visor de certificados de Firefox (porque todavía no tengo un código utilizable):
En Detalles, en la "Jerarquía de certificados", veo esto:
- DigiCert High Assurance EV Root CA
- Servidor de validación extendida DigiCert SHA2 CA
- github.com
- Servidor de validación extendida DigiCert SHA2 CA
Mi objeto representa "github.com", la línea más baja de la cadena. Necesito identificar mediante programación la línea central ("CA de Servidor de Validación Extendida DigiCert SHA2").
¿Cómo puedo saber una huella digital o algo equivalente que me permita identificar qué certificado se usó para firmar mi certificado?
En este caso específico (github.com), X509Chain.Build
funcionará, porque el certificado final contiene información sobre la ubicación del certificado del emisor (en la extensión de acceso a la información de la Autoridad).
Pero a veces esto puede no funcionar (por ejemplo, con los certificados Thawte, porque Thawte no proporciona información explícita sobre la ubicación del certificado del emisor). Y si el certificado está instalado en el almacén de certificados local, no hay manera de localizar automáticamente al emisor.
Opción 1 - conexión SSL
Sin embargo, si trabaja con un certificado SSL y puede establecer una sesión SSL, puede obtener el certificado agregando un agente de escucha a la propiedad ServicePointManager.ServerCertificateValidationCallback
: https://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.servercertificatevalidationcallback.aspx
RemoteCertificateValidationCallback delegate contiene varios parámetros, uno de ellos es chain
, que contiene una cadena de certificado SSL devuelta por el servidor. Y si el servidor remoto contiene un certificado de emisor, se presentará allí en la colección ChainElements
. Este objeto suele contener varios elementos:
-Leaf Certificate
-Issuer Certificate
-(Optional Issuer certs when available)
Por lo tanto, necesita comprobar dos cosas:
- Si
ChainElements
contiene al menos dos elementos (por ejemplo, certificado de hoja y emisor propuesto). - Si el primer elemento de la colección
ChainElements
no tiene el estadoNotSignatureValid
en la colecciónChainelementStatus
.
Puede agregar el siguiente fragmento de código en el delegado RemoteCertificateValidationCallback
para realizar estas comprobaciones:
X509Certificate2 issuer = null;
if (
chain.ChainElements.Count > 1 &&
!chain.ChainElements[0].ChainElementStatus.Any(x => x.Status == X509ChainStatusFlags.NotSignatureValid)) {
issuer = chain.ChainElements[1].Certificate;
}
Si después de ejecutar este fragmento de código la variable del issuer
es null
, entonces no puede determinar automáticamente quién es el emisor de su certificado. Este proceso requerirá una investigación adicional. Y no es null
, entonces la variable del issuer
tendrá el certificado del emisor real.
Opción 2 - buscar en el almacén de certificados local
De acuerdo, según sus comentarios, desea determinar si el certificado del emisor está instalado en el almacén de certificados local o no. Al leer tu pregunta no lo entendí. ¿Por qué deberíamos adivinar lo que realmente estás buscando? Eventualmente, todavía no estoy seguro si sabe / entiende lo que quiere lograr.
Si desea saber si el emisor está instalado en la tienda local, puede usar el siguiente algoritmo:
1) use el método X509Certificate2Collection.Find y encuentre certificados candidatos por su nombre de sujeto
2) encuentre en la lista de candidatos (recuperados en el paso 1) los que tienen el valor de Identificador de clave de asunto igual al valor de Identificador de clave de autoridad del certificado en el asunto.
X509Certificate2Collection certs = new X509Certificate2Collection();
// grab candidates from CA and Root stores
foreach (var storeName in new[] { StoreName.CertificateAuthority, StoreName.Root }) {
X509Store store = new X509Store(storeName, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
certs.AddRange(store.Certificates);
store.Close();
}
certs = certs.Find(X509FindType.FindBySubjectDistinguishedName, cert.Issuer, false);
if (certs.Count == 0) {
Console.WriteLine("Issuer is not installed in the local certificate store.");
return;
}
var aki = cert.Extensions["2.5.29.35"];
if (aki == null) {
Console.WriteLine("Issuer candidates: ");
foreach (var candidate in certs) {
Console.WriteLine(candidate.Thumbprint);
}
return;
}
var match = Regex.Match(aki.Format(false), "KeyID=(.+)", RegexOptions.IgnoreCase);
if (match.Success) {
var keyid = match.Groups[1].Value.Replace(" ", null).ToUpper();
Console.WriteLine("Issuer candidates: ");
foreach (var candidate in certs.Find(X509FindType.FindBySubjectKeyIdentifier, keyid, false)) {
Console.WriteLine(candidate.Thumbprint);
}
} else {
// if KeyID is not presented in the AKI extension, attempt to get serial number from AKI:
match = Regex.Match(aki.Format(false), "Certificate SerialNumber=(.+)", RegexOptions.IgnoreCase);
var serial = match.Groups[1].Value.Replace(" ", null);
Console.WriteLine("Issuer candidates: ");
foreach (var candidate in certs.Find(X509FindType.FindBySerialNumber, serial, false)) {
Console.WriteLine(candidate.Thumbprint);
}
}
asumiendo que cert
variable almacena el certificado en el asunto (para el cual se busca el emisor). Este enfoque tiene problemas, ya que no valida la firma y puede devolver falsos positivos.
Pregunté: "¿Qué hay de malo con la propiedad IssuerName?"
La respuesta fue: "Nada, excepto que no tiene que coincidir con el asunto del certificado del firmante e incluso si coincide, no hay manera de saber si es exactamente el certificado correcto o simplemente algún certificado con el mismo asunto".
Esto es incorrecto.
PKIX § 6.1 dice, "para todas las x en {1, ..., n-1}, el sujeto del certificado x es el emisor del certificado x + 1".
Las subsecciones aclaran esto al indicar "Asignar el nombre del sujeto del certificado a working_issuer_name", y luego, para el siguiente certificado de la cadena, verificar que "... el nombre del emisor del certificado es working_issuer_name".
Podría estar confundido porque puede tener muchos certificados de emisor con el mismo nombre, pero con claves diferentes. En ese caso, la clave de firma correcta se puede identificar haciendo coincidir el identificador de la clave del sujeto del emisor con el identificador de la clave de autoridad del sujeto. Sin embargo, los identificadores de clave coincidentes no son suficientes: los nombres deben coincidir primero.
No está claro por qué intentas hacer esto manualmente. Debería poder proporcionar todos los intermedios disponibles a su biblioteca de creación de rutas, y permitirle encontrar una cadena válida si existe alguna.