private bool ValidateServerCert(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { if (certificate is X509Certificate2 c2) { PeerCertificateReceived?.Invoke(this, new TlsCertificateReceivedEventArgs(c2)); } if (_options.PinnedServerCertificate != null) { var retVal = certificate.Equals(_options.PinnedServerCertificate); if (!retVal) { WriteLog.To.Sync.W(Tag, "Server certificate did not match the pinned one!"); _validationException = new TlsCertificateException("The certificate does not match the pinned certificate", C4NetworkErrorCode.TLSCertUnknownRoot, X509ChainStatusFlags.ExplicitDistrust); } return(retVal); } // Mono doesn't pass chain information? if (chain?.ChainElements?.Count == 0 && certificate is X509Certificate2 cert2) { chain = X509Chain.Create(); chain.Build(cert2); } #if COUCHBASE_ENTERPRISE var onlySelfSigned = _options.AcceptOnlySelfSignedServerCertificate; #else var onlySelfSigned = false; #endif if (!onlySelfSigned && sslPolicyErrors != SslPolicyErrors.None) { WriteLog.To.Sync.W(Tag, $"Error validating TLS chain: {sslPolicyErrors}"); if (chain.ChainElements != null) { foreach (var element in chain.ChainElements) { if (element.ChainElementStatus != null) { foreach (var status in element.ChainElementStatus) { if (status.Status != X509ChainStatusFlags.NoError) { WriteLog.To.Sync.V(Tag, $"Error {status.Status} ({status.StatusInformation}) for certificate:{Environment.NewLine}{element.Certificate}"); if (status.Status == X509ChainStatusFlags.UntrustedRoot) { _validationException = new TlsCertificateException("The certificate does not terminate in a trusted root CA.", C4NetworkErrorCode.TLSCertUnknownRoot, X509ChainStatusFlags.UntrustedRoot); return(false); } } } } } } if (chain.ChainStatus != null) { foreach (var status in chain.ChainStatus) { if (status.Status == X509ChainStatusFlags.PartialChain) { _validationException = new TlsCertificateException("The certificate does not terminate in a trusted root CA.", C4NetworkErrorCode.TLSCertUnknownRoot, X509ChainStatusFlags.PartialChain); return(false); } } } } else if (onlySelfSigned) { if (chain.ChainElements.Count != 1) { _validationException = new TlsCertificateException("A non self-signed certificate was received in self-signed mode.", C4NetworkErrorCode.TLSCertUnknownRoot, X509ChainStatusFlags.ExplicitDistrust); return(false); } if (chain.ChainStatus[0].Status != X509ChainStatusFlags.UntrustedRoot && chain.ChainStatus[0].Status != X509ChainStatusFlags.PartialChain && chain.ChainStatus[0].Status != X509ChainStatusFlags.NoError) { _validationException = new TlsCertificateException("Certificate verification failed", C4NetworkErrorCode.TLSCertUnknownRoot, chain.ChainStatus[0].Status); return(false); } if (chain.ChainElements[0].Certificate.IssuerName.Name != chain.ChainElements[0].Certificate.SubjectName.Name) { _validationException = new TlsCertificateException("A non self-signed certificate was received in self-signed mode.", C4NetworkErrorCode.TLSCertUnknownRoot, X509ChainStatusFlags.ExplicitDistrust); return(false); } return(true); } if (sslPolicyErrors == SslPolicyErrors.None) { return(true); } _validationException = new TlsCertificateException("Certificate verification failed", C4NetworkErrorCode.TLSCertUntrusted, sslPolicyErrors); return(false); }
private void StartInternal() { // STEP 3: Create the WebSocket Upgrade HTTP request WriteLog.To.Sync.I(Tag, $"WebSocket connecting to {_logic.UrlRequest?.Host}:{_logic.UrlRequest?.Port}"); var rng = RandomNumberGenerator.Create() ?? throw new RuntimeException("Failed to create RandomNumberGenerator"); var nonceBytes = new byte[16]; rng.GetBytes(nonceBytes); var nonceKey = Convert.ToBase64String(nonceBytes); _expectedAcceptHeader = Base64Digest(String.Concat(nonceKey, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); foreach (var header in _options.Headers) { _logic[header.Key] = header.Value; } var cookieString = _options.CookieString; if (cookieString != null) { // https://github.com/couchbase/couchbase-lite-net/issues/974 // Don't overwrite a possible entry in the above headers unless there is // actually a value _logic["Cookie"] = cookieString; } // These ones should be overwritten. The user has no business setting them. _logic["Connection"] = "Upgrade"; _logic["Upgrade"] = "websocket"; _logic["Sec-WebSocket-Version"] = "13"; _logic["Sec-WebSocket-Key"] = nonceKey; var protocols = _options.Protocols; if (protocols != null) { _logic["Sec-WebSocket-Protocol"] = protocols; } if (_logic.UseTls) { var baseStream = _client?.GetStream(); if (baseStream == null) { WriteLog.To.Sync.W(Tag, "Failed to get network stream (already closed?). Aborting start..."); DidClose(C4WebSocketCloseCode.WebSocketCloseAbnormal, "Unexpected error in client logic"); return; } var stream = new SslStream(baseStream, false, ValidateServerCert); X509CertificateCollection clientCerts = null; if (_options.ClientCert != null) { clientCerts = new X509CertificateCollection(new[] { _options.ClientCert as X509Certificate }); } try { // STEP 3A: TLS handshake stream.AuthenticateAsClientAsync(_logic.UrlRequest?.Host, clientCerts, SslProtocols.Tls12, false) .ContinueWith( t => { if (_validationException != null) { DidClose(_validationException); _validationException = null; return; } if (t.Exception?.InnerException != null) { // Failed before the server validation step, probably client certificate error DidClose(new TlsCertificateException( "Authentication failed prior to server certificate (bad / missing client cert?)", C4NetworkErrorCode.TLSHandshakeFailed, t.Exception.InnerException)); return; } if (!NetworkTaskSuccessful(t)) { return; } _queue.DispatchAsync(OnSocketReady); }); } catch (Exception e) { // I can't believe I have to do this, but .NET Core will throw the exception // here on Linux instead of passing it to the continuation var toThrow = _validationException ?? new TlsCertificateException( "Authentication failed prior to server certificate (bad / missing client cert?)", C4NetworkErrorCode.TLSHandshakeFailed, e); DidClose(toThrow); _validationException = null; return; } NetworkStream = stream; } else { NetworkStream = _client?.GetStream(); OnSocketReady(); } }