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>(); if (rootStoreFile != null && rootStoreFile.Exists) { newFileTime = rootStoreFile.LastWriteTimeUtc; ProcessFile(rootStoreFile); } if (rootStorePath != null && rootStorePath.Exists) { newDirTime = rootStorePath.LastWriteTimeUtc; foreach (FileInfo file in rootStorePath.EnumerateFiles()) { ProcessFile(file); } } void ProcessFile(FileInfo file) { 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; } // 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)) { 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(); } } } 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 LoadMachineStores() { Debug.Assert( Monitor.IsEntered(s_machineLoadLock), "LoadMachineStores assumes a lock(s_machineLoadLock)"); var rootStore = new List <X509Certificate2>(); var intermedStore = new List <X509Certificate2>(); DirectoryInfo rootStorePath = null; IEnumerable <FileInfo> trustedCertFiles; try { rootStorePath = new DirectoryInfo(Interop.Crypto.GetX509RootStorePath()); } catch (ArgumentException) { // If SSL_CERT_DIR is set to the empty string, or anything else which gives // "The path is not of a legal form", then the GetX509RootStorePath value is ignored. } if (rootStorePath != null && rootStorePath.Exists) { trustedCertFiles = rootStorePath.EnumerateFiles(); } else { trustedCertFiles = Array.Empty <FileInfo>(); } FileInfo rootStoreFile = null; try { rootStoreFile = new FileInfo(Interop.Crypto.GetX509RootStoreFile()); } catch (ArgumentException) { // If SSL_CERT_FILE is set to the empty string, or anything else which gives // "The path is not of a legal form", then the GetX509RootStoreFile value is ignored. } if (rootStoreFile != null && rootStoreFile.Exists) { trustedCertFiles = trustedCertFiles.Prepend(rootStoreFile); } HashSet <X509Certificate2> uniqueRootCerts = new HashSet <X509Certificate2>(); HashSet <X509Certificate2> uniqueIntermediateCerts = new HashSet <X509Certificate2>(); foreach (FileInfo file in trustedCertFiles) { 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(); continue; } ICertificatePal pal; // 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 corefx doesn't validate for a specific usage, derived certificates are rejected. // For now, we skip the certificates with AUX data and use the regular certificates. while (OpenSslX509CertificateReader.TryReadX509PemNoAux(fileBio, out pal) || OpenSslX509CertificateReader.TryReadX509Der(fileBio, out pal)) { 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)) { rootStore.Add(cert); continue; } } else { if (uniqueIntermediateCerts.Add(cert)) { intermedStore.Add(cert); 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(); } } } var rootStorePal = new CollectionBackedStoreProvider(rootStore); s_machineIntermediateStore = new CollectionBackedStoreProvider(intermedStore); // s_machineRootStore's nullarity is the loaded-state sentinel, so write it with Volatile. Debug.Assert(Monitor.IsEntered(s_machineLoadLock), "LoadMachineStores assumes a lock(s_machineLoadLock)"); Volatile.Write(ref s_machineRootStore, rootStorePal); }