// // SECURITY: we open a private key container on behalf of the caller // and we require the caller to have permission associated with that operation. // private X509Certificate2 EnsurePrivateKey(X509Certificate certificate) { if (certificate == null) { return(null); } if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.Format(SR.net_log_locating_private_key_for_certificate, certificate.ToString(true))); } try { string certHash = null; // Protecting from X509Certificate2 derived classes. X509Certificate2 certEx = MakeEx(certificate); certHash = certEx.Thumbprint; if (certEx != null) { if (certEx.HasPrivateKey) { if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.net_log_cert_is_of_type_2); } return(certEx); } if ((object)certificate != (object)certEx) { certEx.Dispose(); } } X509Certificate2Collection collectionEx; // ELSE Try the MY user and machine stores for private key check. // For server side mode MY machine store takes priority. X509Store store = CertificateValidationPal.EnsureStoreOpened(_serverMode); if (store != null) { collectionEx = store.Certificates.Find(X509FindType.FindByThumbprint, certHash, false); if (collectionEx.Count > 0 && collectionEx[0].HasPrivateKey) { if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.Format(SR.net_log_found_cert_in_store, (_serverMode ? "LocalMachine" : "CurrentUser"))); } return(collectionEx[0]); } } store = CertificateValidationPal.EnsureStoreOpened(!_serverMode); if (store != null) { collectionEx = store.Certificates.Find(X509FindType.FindByThumbprint, certHash, false); if (collectionEx.Count > 0 && collectionEx[0].HasPrivateKey) { if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.Format(SR.net_log_found_cert_in_store, (_serverMode ? "CurrentUser" : "LocalMachine"))); } return(collectionEx[0]); } } } catch (CryptographicException) { } if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.net_log_did_not_find_cert_in_store); } return(null); }
/*++ * 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); }
public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(SR.GetString(SR.net_noseek)); }