Utilizzo di un certificato autofirmato con HttpWebRequest / Response di .NET

Sto provando a connettermi a un’API che utilizza un certificato SSL autofirmato. Lo sto facendo usando gli oggetti HttpWebRequest e HttpWebResponse di .NET. E sto ricevendo un’eccezione che:

La connessione sottostante è stata chiusa: imansible stabilire una relazione di trust per il canale protetto SSL / TLS.

Capisco cosa significa. E capisco perché .NET ritiene che dovrebbe avvisarmi e chiudere la connessione. Ma in questo caso, mi piacerebbe solo collegarmi all’API in ogni caso, dannazione agli attacchi man-in-the-middle.

Quindi, come faccio ad aggiungere un’eccezione per questo certificato autofirmato? O è l’approccio per dire a HttpWebRequest / Response di non convalidare il certificato? Come potrei farlo?

@Domster: funziona, ma potresti voler applicare un po ‘di sicurezza controllando se l’hash del certificato corrisponde a ciò che ti aspetti. Quindi una versione espansa ha un aspetto simile a questo (basato su un codice live che stiamo usando):

static readonly byte[] apiCertHash = { 0xZZ, 0xYY, ....}; ///  /// Somewhere in your application's startup/init sequence... ///  void InitPhase() { // Override automatic validation of SSL server certificates. ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertficate; } ///  /// Validates the SSL server certificate. ///  /// An object that contains state information for this /// validation. /// The certificate used to authenticate the remote party. /// The chain of certificate authorities associated with the /// remote certificate. /// One or more errors associated with the remote /// certificate. /// Returns a boolean value that determines whether the specified /// certificate is accepted for authentication; true to accept or false to /// reject. 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; } 

Risulta, se si desidera disabilitare completamente la convalida dei certificati, è ansible modificare ServerCertificateValidationCallback sul ServicePointManager, in questo modo:

 ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; 

Ciò convaliderà tutti i certificati (compresi quelli non validi, scaduti o autofirmati).

Aggiungi il certificato autofirmato alle autorità di certificazione delle fonti attendibili del computer locale

È ansible importare il certificato eseguendo MMC come amministratore.

Procedura: visualizzare i certificati con lo snap-in MMC

Si noti che in .NET 4.5 è ansible sovrascrivere la convalida SSL per HttpWebRequest stesso (e non tramite delegato globale che influisce su tutte le richieste):

http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.servercertificatevalidationcallback.aspx

 HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri); request.ServerCertificateValidationCallback = delegate { return true; }; 

L’ambito del callback di convalida utilizzato nella risposta di Domster può essere limitato a una richiesta specifica utilizzando il parametro sender sul delegato ServerCertificateValidationCallback . La seguente class scope semplice utilizza questa tecnica per cablare temporaneamente un callback di convalida che viene eseguito solo per un determinato object richiesta.

 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 suddetta class può essere utilizzata per ignorare tutti gli errori di certificato per una richiesta specifica come segue:

 var request = WebRequest.Create(uri); using (new ServerCertificateValidationScope(request, delegate { return true; })) { request.GetResponse(); } 

Aggiungere come ansible aiuto a qualcun altro … Se si desidera che l’utente installi il certificato autofirmato, è ansible utilizzare questo codice (modificato dall’alto).

Non richiede diritti di amministratore, installa i profili di fiducia degli utenti locali:

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

Questo sembra funzionare bene per la nostra applicazione, e se l’utente preme no, la comunicazione non funzionerà.

Aggiornamento: 11-12-2015 – Modificato StoreName.Root in StoreName.My – Il mio verrà installato nell’archivio utenti locale, anziché in Root. Il root su alcuni sistemi non funzionerà, anche se “si esegue come amministratore”

Basandosi solo sulla risposta di devstuff per includere sobject ed emittente … commenti benvenuto …

 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 __knownSelfSignedCertificates = new List { 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; } } ///  /// Validates the SSL server certificate. ///  /// An object that contains state information for this /// validation. /// The certificate used to authenticate the remote party. /// The chain of certificate authorities associated with the /// remote certificate. /// One or more errors associated with the remote /// certificate. /// Returns a boolean value that determines whether the specified /// certificate is accepted for authentication; true to accept or false to /// reject. 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)); } } 

Una cosa da tenere a mente è che avere ServicePointManager.ServerCertificateValidationCallback non sembra significare che il controllo CRL e la validazione del servername non sono eseguiti, ma fornisce solo un mezzo per sovrascrivere il risultato. Quindi il tuo servizio potrebbe richiedere ancora un po ‘di tempo per ottenere un CRL, in seguito saprai solo che ha fallito alcuni controlli.

Stavo incontrando lo stesso problema dell’OP in cui la richiesta web genererebbe quell’eccezione esatta. Avevo impostato tutto correttamente, ho pensato, il certificato è stato installato, ho potuto localizzarlo nell’archivio della macchina e collegarlo alla richiesta web, e ho disabilitato la verifica dei certificati nel contesto della richiesta.

È risultato che ero in esecuzione con il mio account utente e che il certificato è stato installato nell’archivio della macchina. Ciò ha causato la richiesta web di lanciare questa eccezione. Per risolvere il problema, dovevo essere in esecuzione come amministratore o installare il certificato nell’archivio utente e leggerlo da lì.

Sembrerebbe che C # sia in grado di trovare il certificato nell’archivio della macchina anche se non può essere usato con una richiesta web e che ciò comporta l’eccezione dell’OP che viene lanciata una volta che la richiesta web è stata emessa.