Próbuję połączyć się z interfejsem API, który używa certyfikatu SSL z podpisem własnym. Robię to przy użyciu obiektów HttpWebRequest i HttpWebResponse platformy .NET. Otrzymuję wyjątek, który:
Połączenie bazowe zostało zamknięte: nie można ustanowić relacji zaufania dla bezpiecznego kanału SSL / TLS.
Rozumiem, co to oznacza. I rozumiem, dlaczego .NET uważa, że powinien mnie ostrzec i zamknąć połączenie. Ale w tym przypadku i tak chciałbym po prostu połączyć się z API, przeklęte ataki typu man-in-the-middle.
Jak więc dodać wyjątek dla tego certyfikatu z podpisem własnym? Czy jest podejściem nakazującym HttpWebRequest / Response, aby w ogóle nie sprawdzał poprawności certyfikatu? Jak miałbym to zrobić?
źródło
Okazuje się, że jeśli chcesz po prostu całkowicie wyłączyć weryfikację certyfikatu, możesz zmienić ServerCertificateValidationCallback w ServicePointManager, na przykład:
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
Spowoduje to zweryfikowanie wszystkich certyfikatów (w tym nieważnych, wygasłych lub z podpisem własnym).
źródło
Zauważ, że w .NET 4.5 możesz przesłonić walidację SSL dla samego HttpWebRequest (a nie przez delegata globalnego, który wpływa na wszystkie żądania):
http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.servercertificatevalidationcallback.aspx
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri); request.ServerCertificateValidationCallback = delegate { return true; };
źródło
Dodaj certyfikat z podpisem własnym do urzędów certyfikacji zaufanych głównych komputerów lokalnych
Certyfikat można zaimportować, uruchamiając program MMC jako administrator.
Instrukcje: wyświetlanie certyfikatów za pomocą przystawki MMC
źródło
Zakres wywołania zwrotnego walidacji używanego w odpowiedzi Domstera można ograniczyć do konkretnego żądania przy użyciu parametru nadawcy w
ServerCertificateValidationCallback
delegacie. Poniższa prosta klasa zakresu używa tej techniki do tymczasowego połączenia wywołania zwrotnego walidacji, które jest wykonywane tylko dla danego obiektu żądania.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; } }
Powyższa klasa może służyć do ignorowania wszystkich błędów certyfikatu dla określonego żądania w następujący sposób:
var request = WebRequest.Create(uri); using (new ServerCertificateValidationScope(request, delegate { return true; })) { request.GetResponse(); }
źródło
SslPolicyErrors.None
w przypadku, gdy nie było poprzedniego wywołania zwrotnego, oznacza, że w końcu nadpisujemy domyślną zasadę zasadą „zaakceptuj wszystko”? por. to pytanie i różne odpowiedzi: stackoverflow.com/q/9058096 . Byłbym bardzo szczęśliwy, gdyby powiedziano mi, dlaczego się mylę, a ten kod jest w porządku!Po prostu opierając się na odpowiedzi od devstuff, aby uwzględnić temat i wydawcę ... komentarze mile widziane ...
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)); } }
źródło
Aby dodać jako ewentualną pomoc komuś innemu ... Jeśli chcesz, aby monitował użytkownika o zainstalowanie certyfikatu z podpisem własnym, możesz użyć tego kodu (zmodyfikowanego powyżej).
Nie wymaga uprawnień administratora, instaluje się na zaufanych profilach użytkowników lokalnych:
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; }
Wydaje się, że działa to dobrze w przypadku naszej aplikacji i jeśli użytkownik naciśnie „nie”, komunikacja nie będzie działać.
Aktualizacja: 2015-12-11 - Zmieniono StoreName.Root na StoreName.My - Moja instalacja zostanie zainstalowana w lokalnym sklepie użytkowników zamiast Root. Root w niektórych systemach nie będzie działał, nawet jeśli „uruchomisz jako administrator”
źródło
Jedną z rzeczy, o których należy pamiętać, jest to, że posiadanie ServicePointManager.ServerCertificateValidationCallback nie wydaje się oznaczać, że sprawdzanie listy CRL i walidacja nazwy serwera nie zostały zakończone, a jedynie zapewnia możliwość zastąpienia ich wyniku. Uzyskanie listy CRL może zająć Twojej usłudze jeszcze trochę czasu, dopiero potem będziesz wiedział, że niektóre testy nie powiodły się.
źródło
Miałem ten sam problem, co w przypadku OP, w którym żądanie sieciowe wyrzuciło dokładnie ten wyjątek. Pomyślałem, że wszystko zostało skonfigurowane poprawnie, certyfikat został zainstalowany, mogłem go bez problemu zlokalizować w magazynie komputera i dołączyć do żądania internetowego, a także wyłączyłem weryfikację certyfikatów w kontekście żądania.
Okazało się, że działam na koncie użytkownika, a certyfikat został zainstalowany w magazynie maszyny. To spowodowało, że żądanie sieci Web zgłosiło ten wyjątek. Aby rozwiązać problem, musiałem albo działać jako administrator, albo zainstalować certyfikat w magazynie użytkownika i odczytać go z tego miejsca.
Wydawałoby się, że C # jest w stanie znaleźć certyfikat w magazynie komputera, mimo że nie można go użyć z żądaniem sieci Web, a to powoduje, że wyjątek OP jest generowany po wysłaniu żądania internetowego.
źródło
Po pierwsze - przepraszam, bo korzystałem z rozwiązania, które opisał @devstuff. Jednak znalazłem kilka sposobów, aby to poprawić.
Oto moja modyfikacja:
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; }
ustawianie certyfikatów:
caCertificate2 = new X509Certificate2("auth/ca.crt", ""); var clientCertificate2 = new X509Certificate2("auth/client.pfx", "");
przekazywanie metody delegata
client.pfx
jest generowany za pomocą KEY i CERT jako takich:openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfx
źródło