c# - servicepointmanager - Usando un certificado autofirmado con HttpWebRequest/Response de.NET
servicepointmanager.servercertificatevalidationcallback c# (9)
@Domster: eso funciona, pero es posible que desee imponer un poco de seguridad comprobando si el hash del certificado coincide con lo que espera. Por lo tanto, una versión expandida se ve un poco así (en base a algún código en vivo que estamos usando):
static readonly byte[] apiCertHash = { 0xZZ, 0xYY, ....};
/// <summary>
/// Somewhere in your application''s startup/init sequence...
/// </summary>
void InitPhase()
{
// Override automatic validation of SSL server certificates.
ServicePointManager.ServerCertificateValidationCallback =
ValidateServerCertficate;
}
/// <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;
}
log.DebugFormat("SSL certificate error: {0}", sslPolicyErrors);
bool certMatch = false; // Assume failure
byte[] certHash = cert.GetCertHash();
if (certHash.Length == apiCertHash.Length)
{
certMatch = true; // Now assume success.
for (int idx = 0; idx < certHash.Length; idx++)
{
if (certHash[idx] != apiCertHash[idx])
{
certMatch = false; // No match
break;
}
}
}
// Return true => allow unauthenticated server,
// false => disallow unauthenticated server.
return certMatch;
}
Estoy intentando conectarme a una API que usa un certificado SSL autofirmado. Lo hago utilizando los objetos HttpWebRequest y HttpWebResponse de .NET. Y estoy recibiendo 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 simplemente conectarme a la API de todos modos, malditos sean los ataques del hombre en el medio.
Entonces, ¿cómo hago para agregar una excepción para este certificado autofirmado? ¿O es el enfoque para decirle a HttpWebRequest / Response que no valide el certificado en absoluto? ¿Como podría hacerlo?
Agregue el certificado autofirmado a las autoridades de certificación raíz de confianza de la computadora local
Puede importar el certificado ejecutando MMC como administrador.
El alcance de la devolución de llamada de validación utilizada en la respuesta de Domster puede limitarse a una solicitud específica utilizando el parámetro del remitente en el delegado ServerCertificateValidationCallback
. La siguiente clase de ámbito simple utiliza esta técnica para conectar de forma temporal 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 usar 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();
}
Me encontré con el mismo problema que el OP donde la solicitud web lanzaría esa excepción exacta. Tenía todo configurado correctamente, pensé, el certificado estaba instalado, podía ubicarlo en la tienda de máquinas perfectamente y adjuntarlo a la solicitud web, y había desactivado la verificación de certificados en el contexto de solicitud.
Resultó que estaba ejecutando mi cuenta de usuario y que el certificado se había instalado en la tienda de máquinas. Esto provocó que la solicitud web arrojara esta excepción. Para resolver el problema, tenía que estar ejecutándose como administrador o instalar el certificado en la tienda del usuario y leerlo desde allí.
Parecería que C # puede encontrar el certificado en la tienda de máquinas aunque no se pueda usar con una solicitud web, y que esto genere la excepción del OP lanzada una vez que se emita la solicitud web.
Para agregar como posible ayuda a otra persona ... Si desea que 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: 12/12/2015 - Cambié StoreName.Root a StoreName.My - My se instalará en la tienda local de usuarios, en lugar de Root. La raíz en algunos sistemas no funcionará, incluso si "se ejecuta como administrador"
Resulta que, si solo desea deshabilitar la validación de certificados por completo, puede cambiar ServerCertificateValidationCallback en el ServicePointManager, así:
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
Esto validará todos los certificados (incluidos los no válidos, vencidos o autofirmados).
Solo en la respuesta del para incluir el tema y el emisor ... los comentarios son 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));
}
}
Tenga en cuenta que en .NET 4.5 puede anular la validación de SSL por HttpWebRequest (y no a través de un delegado global que afecte a todas las solicitudes):
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.ServerCertificateValidationCallback = delegate { return true; };
Una cosa a tener en cuenta es que tener ServicePointManager.ServerCertificateValidationCallback no parece significar que la comprobación de CRL y la validación del nombre de servidor no se realizan, solo proporciona un medio para anular su resultado. Por lo tanto, su servicio puede demorar un tiempo en obtener una CRL, solo sabrá luego que falló algunos controles.