HandshakeType SendCertificateVerify(ref int offset) { #if NET45 || NET451 var key = new X509Certificate2(_clientCertificates[0]).PrivateKey; var keyDsa = key as DSACryptoServiceProvider; var keyRsa = key as RSACryptoServiceProvider; #else var keyRsa = new X509Certificate2(_clientCertificates[0].Export(X509ContentType.Cert)).GetRSAPrivateKey(); #endif byte[] signature = null, hash = null; #if NET45 || NET451 if (keyDsa != null) { if (_pendingConnState.TlsVersion == TlsVersion.TLSv1_2 && !_handshakeData.SupportedSignatureAlgorithms.Contains(Tuple.Create(TLSHashAlgorithm.SHA1, SignatureAlgorithm.DSA))) { SendAlertFatal(AlertDescription.HandshakeFailure, "Server does not support client certificate sha1-dsa signatures"); } hash = _handshakeData.CertificateVerifyHash_SHA1.Final(); signature = keyDsa.SignHash(hash, Utils.HashNameToOID["SHA1"]); // Convert to DER var r = new byte[21]; var s = new byte[21]; Array.Reverse(signature, 0, 20); Array.Reverse(signature, 20, 20); Buffer.BlockCopy(signature, 0, r, 0, 20); Buffer.BlockCopy(signature, 20, s, 0, 20); r = new BigInteger(r).ToByteArray(); s = new BigInteger(s).ToByteArray(); Array.Reverse(r); Array.Reverse(s); signature = new byte[r.Length + s.Length + 6]; signature[0] = 0x30; // SEQUENCE signature[1] = (byte)(signature.Length - 2); signature[2] = 0x02; // INTEGER signature[3] = (byte)r.Length; Buffer.BlockCopy(r, 0, signature, 4, r.Length); signature[4 + r.Length] = 0x02; // INTEGER signature[4 + r.Length + 1] = (byte)s.Length; Buffer.BlockCopy(s, 0, signature, 4 + r.Length + 2, s.Length); if (_pendingConnState.TlsVersion == TlsVersion.TLSv1_2) { _buf[offset++] = (byte)TLSHashAlgorithm.SHA1; _buf[offset++] = (byte)SignatureAlgorithm.DSA; } } else #endif if (keyRsa != null) { if (_pendingConnState.TlsVersion == TlsVersion.TLSv1_2 && !_handshakeData.SupportedSignatureAlgorithms.Contains(Tuple.Create(TLSHashAlgorithm.SHA1, SignatureAlgorithm.RSA))) { SendAlertFatal(AlertDescription.HandshakeFailure, "Server does not support client certificate sha1-rsa signatures"); } hash = _handshakeData.CertificateVerifyHash_SHA1.Final(); byte[] md5Hash = null; if (_handshakeData.CertificateVerifyHash_MD5 != null) { md5Hash = _handshakeData.CertificateVerifyHash_MD5.Final(); } // NOTE: It seems problematic to support other hash algorithms than SHA1 since the PrivateKey included in the certificate // often uses an old Crypto Service Provider (Microsoft Base Cryptographic Provider v1.0) instead of Microsoft Enhanced RSA and AES Cryptographic Provider. // The following out-commented code might work to change CSP. //var csp = new RSACryptoServiceProvider().CspKeyContainerInfo; //keyRsa = new RSACryptoServiceProvider(new CspParameters(csp.ProviderType, csp.ProviderName, keyRsa.CspKeyContainerInfo.KeyContainerName)); // TLS 1.0 and 1.1, export private key and calculate md5-sha1 hash and sign manually if (_pendingConnState.TlsVersion != TlsVersion.TLSv1_2) { var fullHash = new byte[36]; Buffer.BlockCopy(md5Hash, 0, fullHash, 0, 16); Buffer.BlockCopy(hash, 0, fullHash, 16, 20); signature = RsaPKCS1.SignRsaPKCS1(keyRsa, fullHash); // BigIntegers have no Dispose/Clear methods, but they contain sensitive data, so force a garbage collection to remove the data. GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, false); } else { #if NET45 || NET451 signature = keyRsa.SignHash(hash, Utils.HashNameToOID["SHA1"]); #else signature = keyRsa.SignHash(hash, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); #endif if (_pendingConnState.TlsVersion == TlsVersion.TLSv1_2) { _buf[offset++] = (byte)TLSHashAlgorithm.SHA1; _buf[offset++] = (byte)SignatureAlgorithm.RSA; } } } else { SendAlertFatal(AlertDescription.HandshakeFailure); } _handshakeData.CertificateVerifyHash_SHA1.Dispose(); _handshakeData.CertificateVerifyHash_SHA1 = null; if (_handshakeData.CertificateVerifyHash_MD5 != null) { _handshakeData.CertificateVerifyHash_MD5.Dispose(); _handshakeData.CertificateVerifyHash_MD5 = null; } #if NET45 || NET451 key.Dispose(); #endif offset += Utils.WriteUInt16(_buf, offset, (ushort)signature.Length); Buffer.BlockCopy(signature, 0, _buf, offset, signature.Length); offset += signature.Length; return HandshakeType.CertificateVerify; }
public void CloneTo(X509Certificate2Collection collection) { Debug.Assert(collection != null); if (!Directory.Exists(_storePath)) { return; } var loadedCerts = new HashSet<X509Certificate2>(); foreach (string filePath in Directory.EnumerateFiles(_storePath, PfxWildcard)) { try { var cert = new X509Certificate2(filePath); // If we haven't already loaded a cert .Equal to this one, copy it to the collection. if (loadedCerts.Add(cert)) { collection.Add(cert); } else { cert.Dispose(); } } catch (CryptographicException) { // The file wasn't a certificate, move on to the next one. } } }
private static void OnRequestSendingRequest(WinHttpRequestState state) { Debug.Assert(state != null, "OnRequestSendingRequest: state is null"); Debug.Assert(state.RequestHandle != null, "OnRequestSendingRequest: state.RequestHandle is null"); if (state.RequestMessage.RequestUri.Scheme != UriScheme.Https) { // Not SSL/TLS. return; } // Grab the channel binding token (CBT) information from the request handle and put it into // the TransportContext object. state.TransportContext.SetChannelBinding(state.RequestHandle); if (state.ServerCertificateValidationCallback != null) { IntPtr certHandle = IntPtr.Zero; uint certHandleSize = (uint)IntPtr.Size; if (!Interop.WinHttp.WinHttpQueryOption( state.RequestHandle, Interop.WinHttp.WINHTTP_OPTION_SERVER_CERT_CONTEXT, ref certHandle, ref certHandleSize)) { int lastError = Marshal.GetLastWin32Error(); WinHttpTraceHelper.Trace( "OnRequestSendingRequest: Error getting WINHTTP_OPTION_SERVER_CERT_CONTEXT, {0}", lastError); if (lastError == Interop.WinHttp.ERROR_WINHTTP_INCORRECT_HANDLE_STATE) { // Not yet an SSL/TLS connection. This occurs while connecting thru a proxy where the // CONNECT verb hasn't yet been processed due to the proxy requiring authentication. // We need to ignore this notification. Another notification will be sent once the final // connection thru the proxy is completed. return; } throw WinHttpException.CreateExceptionUsingError(lastError); } // Create a managed wrapper around the certificate handle. Since this results in duplicating // the handle, we will close the original handle after creating the wrapper. var serverCertificate = new X509Certificate2(certHandle); Interop.Crypt32.CertFreeCertificateContext(certHandle); X509Chain chain = null; SslPolicyErrors sslPolicyErrors; try { WinHttpCertificateHelper.BuildChain( serverCertificate, state.RequestMessage.RequestUri.Host, state.CheckCertificateRevocationList, out chain, out sslPolicyErrors); bool result = state.ServerCertificateValidationCallback( state.RequestMessage, serverCertificate, chain, sslPolicyErrors); if (!result) { throw WinHttpException.CreateExceptionUsingError( (int)Interop.WinHttp.ERROR_WINHTTP_SECURE_FAILURE); } } finally { if (chain != null) { chain.Dispose(); } serverCertificate.Dispose(); } } }
private static Tuple <SafeX509StackHandle, SafeX509StackHandle> LoadMachineStores( DirectoryInfo?rootStorePath, FileInfo?rootStoreFile) { Debug.Assert( Monitor.IsEntered(s_recheckStopwatch), "LoadMachineStores assumes a lock(s_recheckStopwatch)"); SafeX509StackHandle rootStore = Interop.Crypto.NewX509Stack(); Interop.Crypto.CheckValidOpenSslHandle(rootStore); SafeX509StackHandle intermedStore = Interop.Crypto.NewX509Stack(); Interop.Crypto.CheckValidOpenSslHandle(intermedStore); DateTime newFileTime = default; DateTime newDirTime = default; var uniqueRootCerts = new HashSet <X509Certificate2>(); var uniqueIntermediateCerts = new HashSet <X509Certificate2>(); bool firstLoad = (s_nativeCollections == null); if (rootStoreFile != null && rootStoreFile.Exists) { newFileTime = ContentWriteTime(rootStoreFile); ProcessFile(rootStoreFile); } bool hasStoreData = false; if (rootStorePath != null && rootStorePath.Exists) { newDirTime = ContentWriteTime(rootStorePath); hasStoreData = ProcessDir(rootStorePath); } if (firstLoad && !hasStoreData && s_defaultRootDir) { DirectoryInfo etcSslCerts = new DirectoryInfo("/etc/ssl/certs"); if (etcSslCerts.Exists) { DateTime tmpTime = ContentWriteTime(etcSslCerts); hasStoreData = ProcessDir(etcSslCerts); if (hasStoreData) { newDirTime = tmpTime; s_rootStoreDirectoryInfo = etcSslCerts; } } } bool ProcessDir(DirectoryInfo dir) { bool hasStoreData = false; foreach (FileInfo file in dir.EnumerateFiles()) { hasStoreData |= ProcessFile(file); } return(hasStoreData); } bool ProcessFile(FileInfo file) { bool readData = false; using (SafeBioHandle fileBio = Interop.Crypto.BioNewFile(file.FullName, "rb")) { // The handle may be invalid, for example when we don't have read permission for the file. if (fileBio.IsInvalid) { Interop.Crypto.ErrClearError(); return(false); } // Some distros ship with two variants of the same certificate. // One is the regular format ('BEGIN CERTIFICATE') and the other // contains additional AUX-data ('BEGIN TRUSTED CERTIFICATE'). // The additional data contains the appropriate usage (e.g. emailProtection, serverAuth, ...). // Because we don't validate for a specific usage, derived certificates are rejected. // For now, we skip the certificates with AUX data and use the regular certificates. ICertificatePal?pal; while (OpenSslX509CertificateReader.TryReadX509PemNoAux(fileBio, out pal) || OpenSslX509CertificateReader.TryReadX509Der(fileBio, out pal)) { readData = true; X509Certificate2 cert = new X509Certificate2(pal); // The HashSets are just used for uniqueness filters, they do not survive this method. if (StringComparer.Ordinal.Equals(cert.Subject, cert.Issuer)) { if (uniqueRootCerts.Add(cert)) { using (SafeX509Handle tmp = Interop.Crypto.X509UpRef(pal.Handle)) { if (!Interop.Crypto.PushX509StackField(rootStore, tmp)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } // The ownership has been transferred to the stack tmp.SetHandleAsInvalid(); } continue; } } else { if (uniqueIntermediateCerts.Add(cert)) { using (SafeX509Handle tmp = Interop.Crypto.X509UpRef(pal.Handle)) { if (!Interop.Crypto.PushX509StackField(intermedStore, tmp)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } // The ownership has been transferred to the stack tmp.SetHandleAsInvalid(); } continue; } } // There's a good chance we'll encounter duplicates on systems that have both one-cert-per-file // and one-big-file trusted certificate stores. Anything that wasn't unique will end up here. cert.Dispose(); } } return(readData); } foreach (X509Certificate2 cert in uniqueRootCerts) { cert.Dispose(); } foreach (X509Certificate2 cert in uniqueIntermediateCerts) { cert.Dispose(); } Tuple <SafeX509StackHandle, SafeX509StackHandle> newCollections = Tuple.Create(rootStore, intermedStore); Debug.Assert( Monitor.IsEntered(s_recheckStopwatch), "LoadMachineStores assumes a lock(s_recheckStopwatch)"); // The existing collections are not Disposed here, intentionally. // They could be in the gap between when they are returned from this method and not yet used // in a P/Invoke, which would result in exceptions being thrown. // In order to maintain "finalization-free" the GetNativeCollections method would need to // DangerousAddRef, and the callers would need to DangerousRelease, adding more interlocked operations // on every call. Volatile.Write(ref s_nativeCollections, newCollections); s_directoryCertsLastWrite = newDirTime; s_fileCertsLastWrite = newFileTime; s_recheckStopwatch.Restart(); return(newCollections); }
private static void OnRequestSendingRequest(WinHttpRequestState state) { Debug.Assert(state != null, "OnRequestSendingRequest: state is null"); Debug.Assert(state.RequestHandle != null, "OnRequestSendingRequest: state.RequestHandle is null"); if (state.RequestMessage.RequestUri.Scheme != UriScheme.Https) { // Not SSL/TLS. return; } // Grab the channel binding token (CBT) information from the request handle and put it into // the TransportContext object. state.TransportContext.SetChannelBinding(state.RequestHandle); if (state.ServerCertificateValidationCallback != null) { IntPtr certHandle = IntPtr.Zero; uint certHandleSize = (uint)IntPtr.Size; if (!Interop.WinHttp.WinHttpQueryOption( state.RequestHandle, Interop.WinHttp.WINHTTP_OPTION_SERVER_CERT_CONTEXT, ref certHandle, ref certHandleSize)) { int lastError = Marshal.GetLastWin32Error(); throw WinHttpException.CreateExceptionUsingError(lastError); } // Create a managed wrapper around the certificate handle. Since this results in duplicating // the handle, we will close the original handle after creating the wrapper. var serverCertificate = new X509Certificate2(certHandle); Interop.Crypt32.CertFreeCertificateContext(certHandle); X509Chain chain = null; SslPolicyErrors sslPolicyErrors; try { WinHttpCertificateHelper.BuildChain( serverCertificate, state.RequestMessage.RequestUri.Host, state.CheckCertificateRevocationList, out chain, out sslPolicyErrors); bool result = state.ServerCertificateValidationCallback( state.RequestMessage, serverCertificate, chain, sslPolicyErrors); if (!result) { throw WinHttpException.CreateExceptionUsingError( (int)Interop.WinHttp.ERROR_WINHTTP_SECURE_FAILURE); } } finally { if (chain != null) { chain.Dispose(); } serverCertificate.Dispose(); } } }