Estoy intentando conectarme a una API que utiliza un certificado SSL autofirmado. Lo estoy haciendo usando los objetos HttpWebRequest y HttpWebResponse de .NET. Y obtengo una excepción que:
La conexión subyacente se cerró: no se pudo establecer una relación de confianza para el canal seguro SSL / TLS.
Entiendo lo que esto significa. Y entiendo por qué .NET siente que debería advertirme y cerrar la conexión. Pero en este caso, me gustaría conectarme a la API de todos modos, al diablo con los ataques man-in-the-middle.
Entonces, ¿cómo puedo agregar una excepción para este certificado autofirmado? ¿O el enfoque es decirle a HttpWebRequest / Response que no valide el certificado en absoluto? ¿Como podría hacerlo?
fuente
Resulta que, si solo desea deshabilitar la validación de certificados por completo, puede cambiar ServerCertificateValidationCallback en ServicePointManager, así:
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
Esto validará todos los certificados (incluidos los inválidos, vencidos o autofirmados).
fuente
Tenga en cuenta que en .NET 4.5 puede anular la validación SSL por HttpWebRequest en sí (y no a través del delegado global que afecta a todas las solicitudes):
http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.servercertificatevalidationcallback.aspx
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri); request.ServerCertificateValidationCallback = delegate { return true; };
fuente
Agregue el certificado autofirmado a las autoridades de certificación raíz de confianza del equipo local
Puede importar el certificado ejecutando MMC como administrador.
Cómo: ver certificados con el complemento MMC
fuente
El alcance de la devolución de llamada de validación utilizada en la respuesta de Domster se puede limitar a una solicitud específica utilizando el parámetro del remitente en el
ServerCertificateValidationCallback
delegado. La siguiente clase de alcance simple utiliza esta técnica para conectar temporalmente una devolución de llamada de validación que solo se ejecuta para un objeto de solicitud determinado.public class ServerCertificateValidationScope : IDisposable { private readonly RemoteCertificateValidationCallback _callback; public ServerCertificateValidationScope(object request, RemoteCertificateValidationCallback callback) { var previous = ServicePointManager.ServerCertificateValidationCallback; _callback = (sender, certificate, chain, errors) => { if (sender == request) { return callback(sender, certificate, chain, errors); } if (previous != null) { return previous(sender, certificate, chain, errors); } return errors == SslPolicyErrors.None; }; ServicePointManager.ServerCertificateValidationCallback += _callback; } public void Dispose() { ServicePointManager.ServerCertificateValidationCallback -= _callback; } }
La clase anterior se puede utilizar para ignorar todos los errores de certificado para una solicitud específica de la siguiente manera:
var request = WebRequest.Create(uri); using (new ServerCertificateValidationScope(request, delegate { return true; })) { request.GetResponse(); }
fuente
Basándome en la respuesta de devstuff para incluir el asunto y el emisor ... comentarios bienvenidos ...
public class SelfSignedCertificateValidator { private class CertificateAttributes { public string Subject { get; private set; } public string Issuer { get; private set; } public string Thumbprint { get; private set; } public CertificateAttributes(string subject, string issuer, string thumbprint) { Subject = subject; Issuer = issuer; Thumbprint = thumbprint.Trim( new char[] { '\u200e', '\u200f' } // strip any lrt and rlt markers from copy/paste ); } public bool IsMatch(X509Certificate cert) { bool subjectMatches = Subject.Replace(" ", "").Equals(cert.Subject.Replace(" ", ""), StringComparison.InvariantCulture); bool issuerMatches = Issuer.Replace(" ", "").Equals(cert.Issuer.Replace(" ", ""), StringComparison.InvariantCulture); bool thumbprintMatches = Thumbprint == String.Join(" ", cert.GetCertHash().Select(h => h.ToString("x2"))); return subjectMatches && issuerMatches && thumbprintMatches; } } private readonly List<CertificateAttributes> __knownSelfSignedCertificates = new List<CertificateAttributes> { new CertificateAttributes( // can paste values from "view cert" dialog "CN = subject.company.int", "CN = issuer.company.int", "f6 23 16 3d 5a d8 e5 1e 13 58 85 0a 34 9f d6 d3 c8 23 a8 f4") }; private static bool __createdSingleton = false; public SelfSignedCertificateValidator() { lock (this) { if (__createdSingleton) throw new Exception("Only a single instance can be instanciated."); // Hook in validation of SSL server certificates. ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertficate; __createdSingleton = true; } } /// <summary> /// Validates the SSL server certificate. /// </summary> /// <param name="sender">An object that contains state information for this /// validation.</param> /// <param name="cert">The certificate used to authenticate the remote party.</param> /// <param name="chain">The chain of certificate authorities associated with the /// remote certificate.</param> /// <param name="sslPolicyErrors">One or more errors associated with the remote /// certificate.</param> /// <returns>Returns a boolean value that determines whether the specified /// certificate is accepted for authentication; true to accept or false to /// reject.</returns> private bool ValidateServerCertficate( object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslPolicyErrors) { if (sslPolicyErrors == SslPolicyErrors.None) return true; // Good certificate. Dbg.WriteLine("SSL certificate error: {0}", sslPolicyErrors); return __knownSelfSignedCertificates.Any(c => c.IsMatch(cert)); } }
fuente
Para agregar como posible ayuda a otra persona ... Si desea que le solicite al usuario que instale el certificado autofirmado, puede usar este código (modificado desde arriba).
No requiere derechos de administrador, se instala en los perfiles de confianza de los usuarios locales:
private static bool ValidateServerCertficate( object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslPolicyErrors) { if (sslPolicyErrors == SslPolicyErrors.None) { // Good certificate. return true; } Common.Helpers.Logger.Log.Error(string.Format("SSL certificate error: {0}", sslPolicyErrors)); try { using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) { store.Open(OpenFlags.ReadWrite); store.Add(new X509Certificate2(cert)); store.Close(); } return true; } catch (Exception ex) { Common.Helpers.Logger.Log.Error(string.Format("SSL certificate add Error: {0}", ex.Message)); } return false; }
Esto parece funcionar bien para nuestra aplicación, y si el usuario presiona no, la comunicación no funcionará.
Actualización: 2015-12-11 - Se cambió StoreName.Root a StoreName.My: My se instalará en la tienda de usuarios locales, en lugar de Root. La raíz en algunos sistemas no funcionará, incluso si "ejecuta como administrador"
fuente
Una cosa a tener en cuenta es que tener ServicePointManager.ServerCertificateValidationCallback no parece significar que la verificación de CRL y la validación del nombre del servidor no se hayan realizado, solo proporciona un medio para anular su resultado. Por lo tanto, es posible que su servicio aún tarde un poco en obtener una CRL, solo después sabrá que falló algunas verificaciones.
fuente
Me estaba encontrando con el mismo problema que el OP donde la solicitud web arrojaría esa excepción exacta. Pensé que tenía todo configurado correctamente, el certificado estaba instalado, podía ubicarlo en la tienda de la máquina sin problemas y adjuntarlo a la solicitud web, y había deshabilitado la verificación de certificados en el contexto de la solicitud.
Resultó que estaba ejecutando mi cuenta de usuario y que el certificado estaba instalado en la tienda de máquinas. Esto provocó que la solicitud web lanzara esta excepción. Para resolver el problema, tenía que ejecutarme como administrador o instalar el certificado en la tienda de usuarios y leerlo desde allí.
Parecería que C # puede encontrar el certificado en la tienda de la máquina aunque no se pueda usar con una solicitud web, y esto da como resultado que se lance la excepción del OP una vez que se emite la solicitud web.
fuente
En primer lugar, me disculpo porque he utilizado la solución descrita por @devstuff. Sin embargo, he encontrado algunas formas de mejorarlo.
Aquí está mi modificación:
private static X509Certificate2 caCertificate2 = null; /// <summary> /// Validates the SSL server certificate. /// </summary> /// <param name="sender">An object that contains state information for this validation.</param> /// <param name="cert">The certificate used to authenticate the remote party.</param> /// <param name="chain">The chain of certificate authorities associated with the remote certificate.</param> /// <param name="sslPolicyErrors">One or more errors associated with the remote certificate.</param> /// <returns>Returns a boolean value that determines whether the specified certificate is accepted for authentication; true to accept or false to reject.</returns> private static bool ValidateServerCertficate( object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslPolicyErrors) { if (sslPolicyErrors == SslPolicyErrors.None) { // Good certificate. return true; } // If the following line is not added, then for the self-signed cert an error will be (not tested with let's encrypt!): // "A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider. (UntrustedRoot)" chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; // convert old-style cert to new-style cert var returnedServerCert2 = new X509Certificate2(cert); // This part is very important. Adding known root here. It doesn't have to be in the computer store at all. Neither do certificates. chain.ChainPolicy.ExtraStore.Add(caCertificate2); // 1. Checks if ff the certs are OK (not expired/revoked/etc) // 2. X509VerificationFlags.AllowUnknownCertificateAuthority will make sure that untrusted certs are OK // 3. IMPORTANT: here, if the chain contains the wrong CA - the validation will fail, as the chain is wrong! bool isChainValid = chain.Build(returnedServerCert2); if (!isChainValid) { string[] errors = chain.ChainStatus .Select(x => String.Format("{0} ({1})", x.StatusInformation.Trim(), x.Status)) .ToArray(); string certificateErrorsString = "Unknown errors."; if (errors != null && errors.Length > 0) { certificateErrorsString = String.Join(", ", errors); } Log.Error("Trust chain did not complete to the known authority anchor. Errors: " + certificateErrorsString); return false; } // This piece makes sure it actually matches your known root bool isValid = chain.ChainElements .Cast<X509ChainElement>() .Any(x => x.Certificate.RawData.SequenceEqual(caCertificate2.GetRawCertData())); if (!isValid) { Log.Error("Trust chain did not complete to the known authority anchor. Thumbprints did not match."); } return isValid; }
establecimiento de certificados:
caCertificate2 = new X509Certificate2("auth/ca.crt", ""); var clientCertificate2 = new X509Certificate2("auth/client.pfx", "");
paso método delegado
client.pfx
se genera con KEY y CERT como tal:openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfx
fuente