internal static ChannelBinding?Build(SafeDeleteContext securityContext) { using (X509Certificate2? cert = CertificateValidationPal.GetRemoteCertificate(securityContext)) { if (cert == null) { return(null); } SafeChannelBindingHandle bindingHandle = new SafeChannelBindingHandle(ChannelBindingKind.Endpoint); byte[] bindingHash = GetHashForChannelBinding(cert); bindingHandle.SetCertHash(bindingHash); return(bindingHandle); } }
internal static ChannelBinding Build(SafeDeleteContext securityContext) { using (X509Certificate2 cert = CertificateValidationPal.GetRemoteCertificate(securityContext)) { if (cert == null) { return(null); } SafeChannelBindingHandle bindingHandle = new SafeChannelBindingHandle(ChannelBindingKind.Endpoint); using (HashAlgorithm hashAlgo = GetHashForChannelBinding(cert)) { byte[] bindingHash = hashAlgo.ComputeHash(cert.RawData); bindingHandle.SetCertHash(bindingHash); } return(bindingHandle); } }
/*++ * AcquireCredentials - Attempts to find Client Credential * Information, that can be sent to the server. In our case, * this is only Client Certificates, that we have Credential Info. * * How it works: * case 0: Cert Selection delegate is present * Always use its result as the client cert answer. * Try to use cached credential handle whenever feasible. * Do not use cached anonymous creds if the delegate has returned null * and the collection is not empty (allow responding with the cert later). * * case 1: Certs collection is empty * Always use the same statically acquired anonymous SSL Credential * * case 2: Before our Connection with the Server * If we have a cached credential handle keyed by first X509Certificate **content** in the passed collection, then we use that cached * credential and hoping to restart a session. * * Otherwise create a new anonymous (allow responding with the cert later). * * case 3: After our Connection with the Server (i.e. during handshake or re-handshake) * The server has requested that we send it a Certificate then * we Enumerate a list of server sent Issuers trying to match against * our list of Certificates, the first match is sent to the server. * * Once we got a cert we again try to match cached credential handle if possible. * This will not restart a session but helps minimizing the number of handles we create. * * In the case of an error getting a Certificate or checking its private Key we fall back * to the behavior of having no certs, case 1. * * Returns: True if cached creds were used, false otherwise. * * --*/ private bool AcquireClientCredentials(ref byte[] thumbPrint) { GlobalLog.Enter("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials"); // Acquire possible Client Certificate information and set it on the handle. X509Certificate clientCertificate = null; // This is a candidate that can come from the user callback or be guessed when targeting a session restart. ArrayList filteredCerts = new ArrayList(); // This is an intermediate client certs collection that try to use if no selectedCert is available yet. string[] issuers = null; // This is a list of issuers sent by the server, only valid is we do know what the server cert is. bool sessionRestartAttempt = false; // If true and no cached creds we will use anonymous creds. if (_certSelectionDelegate != null) { issuers = GetRequestCertificateAuthorities(); GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials() calling CertificateSelectionCallback"); X509Certificate2 remoteCert = null; try { X509Certificate2Collection dummyCollection; remoteCert = CertificateValidationPal.GetRemoteCertificate(_securityContext, out dummyCollection); clientCertificate = _certSelectionDelegate(_hostName, ClientCertificates, remoteCert, issuers); } finally { if (remoteCert != null) { remoteCert.Dispose(); } } if (clientCertificate != null) { if (_credentialsHandle == null) { sessionRestartAttempt = true; } filteredCerts.Add(clientCertificate); if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.net_log_got_certificate_from_delegate); } } else { if (ClientCertificates.Count == 0) { if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.net_log_no_delegate_and_have_no_client_cert); } sessionRestartAttempt = true; } else { if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.net_log_no_delegate_but_have_client_cert); } } } } else if (_credentialsHandle == null && _clientCertificates != null && _clientCertificates.Count > 0) { // This is where we attempt to restart a session by picking the FIRST cert from the collection. // Otherwise it is either server sending a client cert request or the session is renegotiated. clientCertificate = ClientCertificates[0]; sessionRestartAttempt = true; if (clientCertificate != null) { filteredCerts.Add(clientCertificate); } if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.Format(SR.net_log_attempting_restart_using_cert, (clientCertificate == null ? "null" : clientCertificate.ToString(true)))); } } else if (_clientCertificates != null && _clientCertificates.Count > 0) { // // This should be a server request for the client cert sent over currently anonymous sessions. // issuers = GetRequestCertificateAuthorities(); if (Logging.On) { if (issuers == null || issuers.Length == 0) { Logging.PrintInfo(Logging.Web, this, SR.net_log_no_issuers_try_all_certs); } else { Logging.PrintInfo(Logging.Web, this, SR.Format(SR.net_log_server_issuers_look_for_matching_certs, issuers.Length)); } } for (int i = 0; i < _clientCertificates.Count; ++i) { // // Make sure we add only if the cert matches one of the issuers. // If no issuers were sent and then try all client certs starting with the first one. // if (issuers != null && issuers.Length != 0) { X509Certificate2 certificateEx = null; X509Chain chain = null; try { certificateEx = MakeEx(_clientCertificates[i]); if (certificateEx == null) { continue; } GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials() root cert:" + certificateEx.Issuer); chain = new X509Chain(); chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreInvalidName; chain.Build(certificateEx); bool found = false; // // We ignore any errors happened with chain. // if (chain.ChainElements.Count > 0) { for (int ii = 0; ii < chain.ChainElements.Count; ++ii) { string issuer = chain.ChainElements[ii].Certificate.Issuer; found = Array.IndexOf(issuers, issuer) != -1; if (found) { GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials() matched:" + issuer); break; } GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials() no match:" + issuer); } } if (!found) { continue; } } finally { if (chain != null) { chain.Dispose(); } if (certificateEx != null && (object)certificateEx != (object)_clientCertificates[i]) { certificateEx.Dispose(); } } } if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.Format(SR.net_log_selected_cert, _clientCertificates[i].ToString(true))); } filteredCerts.Add(_clientCertificates[i]); } } bool cachedCred = false; // This is a return result from this method. X509Certificate2 selectedCert = null; // This is a final selected cert (ensured that it does have private key with it). clientCertificate = null; if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.Format(SR.net_log_n_certs_after_filtering, filteredCerts.Count)); if (filteredCerts.Count != 0) { Logging.PrintInfo(Logging.Web, this, SR.net_log_finding_matching_certs); } } // // ATTN: When the client cert was returned by the user callback OR it was guessed AND it has no private key, // THEN anonymous (no client cert) credential will be used. // // SECURITY: Accessing X509 cert Credential is disabled for semitrust. // We no longer need to demand for unmanaged code permissions. // EnsurePrivateKey should do the right demand for us. for (int i = 0; i < filteredCerts.Count; ++i) { clientCertificate = filteredCerts[i] as X509Certificate; if ((selectedCert = EnsurePrivateKey(clientCertificate)) != null) { break; } clientCertificate = null; selectedCert = null; } GlobalLog.Assert(((object)clientCertificate == (object)selectedCert) || clientCertificate.Equals(selectedCert), "AcquireClientCredentials()|'selectedCert' does not match 'clientCertificate'."); GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials() Selected Cert = " + (selectedCert == null ? "null" : selectedCert.Subject)); try { // Try to locate cached creds first. // // SECURITY: selectedCert ref if not null is a safe object that does not depend on possible **user** inherited X509Certificate type. // byte[] guessedThumbPrint = selectedCert == null ? null : selectedCert.GetCertHash(); SafeFreeCredentials cachedCredentialHandle = SslSessionsCache.TryCachedCredential(guessedThumbPrint, _sslProtocols, _serverMode, _encryptionPolicy); // We can probably do some optimization here. If the selectedCert is returned by the delegate // we can always go ahead and use the certificate to create our credential // (instead of going anonymous as we do here). if (sessionRestartAttempt && cachedCredentialHandle == null && selectedCert != null) { GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials() Reset to anonymous session."); // IIS does not renegotiate a restarted session if client cert is needed. // So we don't want to reuse **anonymous** cached credential for a new SSL connection if the client has passed some certificate. // The following block happens if client did specify a certificate but no cached creds were found in the cache. // Since we don't restart a session the server side can still challenge for a client cert. if ((object)clientCertificate != (object)selectedCert) { selectedCert.Dispose(); } guessedThumbPrint = null; selectedCert = null; clientCertificate = null; } if (cachedCredentialHandle != null) { if (Logging.On) { Logging.PrintInfo(Logging.Web, SR.net_log_using_cached_credential); } _credentialsHandle = cachedCredentialHandle; _selectedClientCertificate = clientCertificate; cachedCred = true; } else { _credentialsHandle = SslStreamPal.AcquireCredentialsHandle(selectedCert, _sslProtocols, _encryptionPolicy, _serverMode); thumbPrint = guessedThumbPrint; // Delay until here in case something above threw. _selectedClientCertificate = clientCertificate; } } finally { // An extra cert could have been created, dispose it now. if (selectedCert != null && (object)clientCertificate != (object)selectedCert) { selectedCert.Dispose(); } } GlobalLog.Leave("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials, cachedCreds = " + cachedCred.ToString(), Logging.ObjectToString(_credentialsHandle)); return(cachedCred); }
/*++ * VerifyRemoteCertificate - Validates the content of a Remote Certificate * * checkCRL if true, checks the certificate revocation list for validity. * checkCertName, if true checks the CN field of the certificate * --*/ //This method validates a remote certificate. //SECURITY: The scenario is allowed in semitrust StorePermission is asserted for Chain.Build // A user callback has unique signature so it is safe to call it under permission assert. // internal bool VerifyRemoteCertificate(RemoteCertValidationCallback remoteCertValidationCallback) { GlobalLog.Enter("SecureChannel#" + Logging.HashString(this) + "::VerifyRemoteCertificate"); SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None; // We don't catch exceptions in this method, so it's safe for "accepted" be initialized with true. bool success = false; X509Chain chain = null; X509Certificate2 remoteCertificateEx = null; try { X509Certificate2Collection remoteCertificateStore; remoteCertificateEx = CertificateValidationPal.GetRemoteCertificate(_securityContext, out remoteCertificateStore); _isRemoteCertificateAvailable = remoteCertificateEx != null; if (remoteCertificateEx == null) { GlobalLog.Leave("SecureChannel#" + Logging.HashString(this) + "::VerifyRemoteCertificate (no remote cert)", (!_remoteCertRequired).ToString()); sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNotAvailable; } else { chain = new X509Chain(); chain.ChainPolicy.RevocationMode = _checkCertRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck; chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; if (remoteCertificateStore != null) { chain.ChainPolicy.ExtraStore.AddRange(remoteCertificateStore); } sslPolicyErrors |= CertificateValidationPal.VerifyCertificateProperties( chain, remoteCertificateEx, _checkCertName, _serverMode, _hostName); } if (remoteCertValidationCallback != null) { success = remoteCertValidationCallback(_hostName, remoteCertificateEx, chain, sslPolicyErrors); } else { if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateNotAvailable && !_remoteCertRequired) { success = true; } else { success = (sslPolicyErrors == SslPolicyErrors.None); } } if (Logging.On) { if (sslPolicyErrors != SslPolicyErrors.None) { Logging.PrintInfo(Logging.Web, this, SR.net_log_remote_cert_has_errors); if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNotAvailable) != 0) { Logging.PrintInfo(Logging.Web, this, "\t" + SR.net_log_remote_cert_not_available); } if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch) != 0) { Logging.PrintInfo(Logging.Web, this, "\t" + SR.net_log_remote_cert_name_mismatch); } if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) != 0) { foreach (X509ChainStatus chainStatus in chain.ChainStatus) { Logging.PrintInfo(Logging.Web, this, "\t" + chainStatus.StatusInformation); } } } if (success) { if (remoteCertValidationCallback != null) { Logging.PrintInfo(Logging.Web, this, SR.net_log_remote_cert_user_declared_valid); } else { Logging.PrintInfo(Logging.Web, this, SR.net_log_remote_cert_has_no_errors); } } else { if (remoteCertValidationCallback != null) { Logging.PrintInfo(Logging.Web, this, SR.net_log_remote_cert_user_declared_invalid); } } } GlobalLog.Print("Cert Validation, remote cert = " + (remoteCertificateEx == null ? "<null>" : remoteCertificateEx.ToString(true))); } finally { // At least on Win2k server the chain is found to have dependencies on the original cert context. // So it should be closed first. if (chain != null) { chain.Dispose(); } if (remoteCertificateEx != null) { remoteCertificateEx.Dispose(); } } GlobalLog.Leave("SecureChannel#" + Logging.HashString(this) + "::VerifyRemoteCertificate", success.ToString()); return(success); }
private static SecurityStatusPal HandshakeInternal( SafeFreeCredentials credential, ref SafeDeleteSslContext?context, ReadOnlySpan <byte> inputBuffer, ref byte[]?outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) { Debug.Assert(!credential.IsInvalid); try { SafeDeleteSslContext?sslContext = ((SafeDeleteSslContext?)context); if ((null == context) || context.IsInvalid) { sslContext = new SafeDeleteSslContext((credential as SafeFreeSslCredentials) !, sslAuthenticationOptions); context = sslContext; if (!string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) && !sslAuthenticationOptions.IsServer) { Interop.AppleCrypto.SslSetTargetName(sslContext.SslContext, sslAuthenticationOptions.TargetHost); } if (sslAuthenticationOptions.CertificateContext == null && sslAuthenticationOptions.CertSelectionDelegate != null) { // certificate was not provided but there is user callback. We can break handshake if server asks for certificate // and we can try to get it based on remote certificate and trusted issuers. Interop.AppleCrypto.SslBreakOnCertRequested(sslContext.SslContext, true); } if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.RemoteCertRequired) { Interop.AppleCrypto.SslSetAcceptClientCert(sslContext.SslContext); } } if (inputBuffer.Length > 0) { sslContext !.Write(inputBuffer); } SafeSslHandle sslHandle = sslContext !.SslContext; SecurityStatusPal status = PerformHandshake(sslHandle); if (status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded) { // we should not be here if CertSelectionDelegate is null but better check before dereferencing.. if (sslAuthenticationOptions.CertSelectionDelegate != null) { X509Certificate2?remoteCert = null; try { string[] issuers = CertificateValidationPal.GetRequestCertificateAuthorities(context); remoteCert = CertificateValidationPal.GetRemoteCertificate(context); if (sslAuthenticationOptions.ClientCertificates == null) { sslAuthenticationOptions.ClientCertificates = new X509CertificateCollection(); } X509Certificate2 clientCertificate = (X509Certificate2)sslAuthenticationOptions.CertSelectionDelegate(sslAuthenticationOptions.TargetHost !, sslAuthenticationOptions.ClientCertificates, remoteCert, issuers); if (clientCertificate != null) { SafeDeleteSslContext.SetCertificate(sslContext.SslContext, SslStreamCertificateContext.Create(clientCertificate)); } } finally { remoteCert?.Dispose(); } } // We either got certificate or we can proceed without it. It is up to the server to decide if either is OK. status = PerformHandshake(sslHandle); } outputBuffer = sslContext.ReadPendingWrites(); return(status); } catch (Exception exc) { return(new SecurityStatusPal(SecurityStatusPalErrorCode.InternalError, exc)); } }