internal static bool TryReadX509Der(SafeBioHandle bio, [NotNullWhen(true)] out ICertificatePal?fromBio) { SafeX509Handle cert = Interop.Crypto.ReadX509AsDerFromBio(bio); if (cert.IsInvalid) { cert.Dispose(); fromBio = null; Interop.Crypto.ErrClearError(); return(false); } fromBio = new OpenSslX509CertificateReader(cert); return(true); }
internal static bool TryReadX509PemNoAux(SafeBioHandle bio, [NotNullWhen(true)] out ICertificatePal?certPal) { SafeX509Handle cert = Interop.Crypto.PemReadX509FromBio(bio); if (cert.IsInvalid) { cert.Dispose(); certPal = null; Interop.Crypto.ErrClearError(); return(false); } certPal = new OpenSslX509CertificateReader(cert); return(true); }
internal static bool TryReadX509Der(ReadOnlySpan <byte> rawData, [NotNullWhen(true)] out ICertificatePal?certPal) { SafeX509Handle certHandle = Interop.Crypto.DecodeX509( ref MemoryMarshal.GetReference(rawData), rawData.Length); if (certHandle.IsInvalid) { certHandle.Dispose(); certPal = null; Interop.Crypto.ErrClearError(); return(false); } certPal = new OpenSslX509CertificateReader(certHandle); return(true); }
private static bool TryReadPkcs12( OpenSslPkcs12Reader pfx, SafePasswordHandle password, bool single, bool ephemeralSpecified, out ICertificatePal?readPal, out List <ICertificatePal>?readCerts) { pfx.Decrypt(password, ephemeralSpecified); if (single) { UnixPkcs12Reader.CertAndKey certAndKey = pfx.GetSingleCert(); OpenSslX509CertificateReader pal = (OpenSslX509CertificateReader)certAndKey.Cert !; if (certAndKey.Key != null) { pal.SetPrivateKey(OpenSslPkcs12Reader.GetPrivateKey(certAndKey.Key)); } readPal = pal; readCerts = null; return(true); } readPal = null; List <ICertificatePal> certs = new List <ICertificatePal>(pfx.GetCertCount()); foreach (UnixPkcs12Reader.CertAndKey certAndKey in pfx.EnumerateAll()) { OpenSslX509CertificateReader pal = (OpenSslX509CertificateReader)certAndKey.Cert !; if (certAndKey.Key != null) { pal.SetPrivateKey(OpenSslPkcs12Reader.GetPrivateKey(certAndKey.Key)); } certs.Add(pal); } readCerts = certs; return(true); }
private void AddCertToStore(ICertificatePal certPal) { // This may well be the first time that we've added something to this store. Directory.CreateDirectory(_storePath); uint userId = Interop.Sys.GetEUid(); EnsureDirectoryPermissions(_storePath, userId); OpenSslX509CertificateReader cert = (OpenSslX509CertificateReader)certPal; using (X509Certificate2 copy = new X509Certificate2(cert.DuplicateHandles())) { string thumbprint = copy.Thumbprint; bool findOpenSlot; // The odds are low that we'd have a thumbprint collision, but check anyways. string?existingFilename = FindExistingFilename(copy, _storePath, out findOpenSlot); if (existingFilename != null) { if (!copy.HasPrivateKey) { return; } try { using (X509Certificate2 fromFile = new X509Certificate2(existingFilename)) { if (fromFile.HasPrivateKey) { // We have a private key, the file has a private key, we're done here. return; } } } catch (CryptographicException) { // We can't read this file anymore, but a moment ago it was this certificate, // so go ahead and overwrite it. } } const UnixFileMode UserReadWrite = UnixFileMode.UserRead | UnixFileMode.UserWrite; string destinationFilename; FileStreamOptions options = new() { Mode = FileMode.CreateNew, UnixCreateMode = UserReadWrite, Access = FileAccess.Write }; if (existingFilename != null) { destinationFilename = existingFilename; options.Mode = FileMode.Create; // Before we open the file for writing the certificate, // ensure it is only accessible to the owner. try { File.SetUnixFileMode(existingFilename, UserReadWrite); } catch (IOException) // Ignore errors. We verify permissions when we've opened the file. { } } else if (findOpenSlot) { destinationFilename = FindOpenSlot(thumbprint); } else { destinationFilename = Path.Combine(_storePath, thumbprint + PfxExtension); } using (FileStream stream = new FileStream(destinationFilename, options)) { // Verify the file can only be read/written to by the owner. UnixFileMode actualMode = File.GetUnixFileMode(stream.SafeFileHandle); if (actualMode != UserReadWrite) { throw new CryptographicException(SR.Format(SR.Cryptography_InvalidFilePermissions, stream.Name)); } byte[] pkcs12 = copy.Export(X509ContentType.Pkcs12) !; stream.Write(pkcs12, 0, pkcs12.Length); } } }
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 bool AddCachedCrlCore(string crlFile, SafeX509StoreHandle store, DateTime verificationTime) { using (SafeBioHandle bio = Interop.Crypto.BioNewFile(crlFile, "rb")) { if (bio.IsInvalid) { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.CrlCacheOpenError(); } Interop.Crypto.ErrClearError(); return(false); } // X509_STORE_add_crl will increase the refcount on the CRL object, so we should still // dispose our copy. using (SafeX509CrlHandle crl = Interop.Crypto.PemReadBioX509Crl(bio)) { if (crl.IsInvalid) { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.CrlCacheDecodeError(); } Interop.Crypto.ErrClearError(); return(false); } // If crl.LastUpdate is in the past, downloading a new version isn't really going // to help, since we can't rewind the Internet. So this is just going to fail, but // at least it can fail without using the network. // // If crl.NextUpdate is in the past, try downloading a newer version. IntPtr nextUpdatePtr = Interop.Crypto.GetX509CrlNextUpdate(crl); DateTime nextUpdate; // If there is no crl.NextUpdate, this indicates that the CA is not providing // any more updates to the CRL, or they made a mistake not providing a NextUpdate. // We'll cache it for a few days to cover the case it was a mistake. if (nextUpdatePtr == IntPtr.Zero) { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.CrlCacheFileBasedExpiry(); } try { nextUpdate = File.GetLastWriteTime(crlFile).AddDays(3); } catch { // We couldn't determine when the CRL was last written to, // so consider it expired. Debug.Fail("Failed to get the last write time of the CRL file"); return(false); } } else { nextUpdate = OpenSslX509CertificateReader.ExtractValidityDateTime(nextUpdatePtr); } // OpenSSL is going to convert our input time to universal, so we should be in Local or // Unspecified (local-assumed). Debug.Assert( verificationTime.Kind != DateTimeKind.Utc, "UTC verificationTime should have been normalized to Local"); // In the event that we're to-the-second accurate on the match, OpenSSL will consider this // to be already expired. if (nextUpdate <= verificationTime) { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.CrlCacheExpired(nextUpdate, verificationTime); } return(false); } if (!Interop.Crypto.X509StoreAddCrl(store, crl)) { // Ignore error "cert already in store", throw on anything else. In any case the error queue will be cleared. if (X509_R_CERT_ALREADY_IN_HASH_TABLE == Interop.Crypto.ErrPeekLastError()) { Interop.Crypto.ErrClearError(); } else { throw Interop.Crypto.CreateOpenSslCryptographicException(); } } if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.CrlCacheAcceptedFile(nextUpdate); } return(true); } } }
private static string?GetCdpUrl(SafeX509Handle cert) { ArraySegment <byte> crlDistributionPoints = OpenSslX509CertificateReader.FindFirstExtension(cert, Oids.CrlDistributionPoints); if (crlDistributionPoints.Array == null) { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.NoCdpFound(cert); } return(null); } try { AsnValueReader reader = new AsnValueReader(crlDistributionPoints, AsnEncodingRules.DER); AsnValueReader sequenceReader = reader.ReadSequence(); reader.ThrowIfNotEmpty(); while (sequenceReader.HasData) { DistributionPointAsn.Decode(ref sequenceReader, crlDistributionPoints, out DistributionPointAsn distributionPoint); // Only distributionPoint is supported // Only fullName is supported, nameRelativeToCRLIssuer is for LDAP-based lookup. if (distributionPoint.DistributionPoint.HasValue && distributionPoint.DistributionPoint.Value.FullName != null) { foreach (GeneralNameAsn name in distributionPoint.DistributionPoint.Value.FullName) { if (name.Uri != null) { if (Uri.TryCreate(name.Uri, UriKind.Absolute, out Uri? uri) && uri.Scheme == "http") { return(name.Uri); } else { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.NonHttpCdpEntry(name.Uri); } } } } if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.NoMatchingCdpEntry(); } } } } catch (CryptographicException) { // Treat any ASN errors as if the extension was missing. } catch (AsnContentException) { // Treat any ASN errors as if the extension was missing. } finally { // The data came from a certificate, so it's public. CryptoPool.Return(crlDistributionPoints.Array, clearSize: 0); } return(null); }
internal static partial ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) { return(OpenSslX509CertificateReader.FromFile(fileName, password, keyStorageFlags)); }
internal static partial ICertificatePal FromBlob(ReadOnlySpan <byte> rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) { return(OpenSslX509CertificateReader.FromBlob(rawData, password, keyStorageFlags)); }
internal static partial ICertificatePal FromOtherCert(X509Certificate copyFrom) { return(OpenSslX509CertificateReader.FromOtherCert(copyFrom)); }
internal static partial ICertificatePal FromHandle(IntPtr handle) { return(OpenSslX509CertificateReader.FromHandle(handle)); }
private void AddCertToStore(ICertificatePal certPal) { // This may well be the first time that we've added something to this store. Directory.CreateDirectory(_storePath); uint userId = Interop.Sys.GetEUid(); EnsureDirectoryPermissions(_storePath, userId); OpenSslX509CertificateReader cert = (OpenSslX509CertificateReader)certPal; using (X509Certificate2 copy = new X509Certificate2(cert.DuplicateHandles())) { string thumbprint = copy.Thumbprint; bool findOpenSlot; // The odds are low that we'd have a thumbprint collision, but check anyways. string?existingFilename = FindExistingFilename(copy, _storePath, out findOpenSlot); if (existingFilename != null) { if (!copy.HasPrivateKey) { return; } try { using (X509Certificate2 fromFile = new X509Certificate2(existingFilename)) { if (fromFile.HasPrivateKey) { // We have a private key, the file has a private key, we're done here. return; } } } catch (CryptographicException) { // We can't read this file anymore, but a moment ago it was this certificate, // so go ahead and overwrite it. } } string destinationFilename; FileMode mode = FileMode.CreateNew; if (existingFilename != null) { destinationFilename = existingFilename; mode = FileMode.Create; } else if (findOpenSlot) { destinationFilename = FindOpenSlot(thumbprint); } else { destinationFilename = Path.Combine(_storePath, thumbprint + PfxExtension); } using (FileStream stream = new FileStream(destinationFilename, mode)) { EnsureFilePermissions(stream, userId); byte[] pkcs12 = copy.Export(X509ContentType.Pkcs12) !; stream.Write(pkcs12, 0, pkcs12.Length); } } }
private static ILoaderPal FromBio( string fileName, SafeBioHandle bio, SafePasswordHandle password, bool ephemeralSpecified) { int bioPosition = Interop.Crypto.BioTell(bio); Debug.Assert(bioPosition >= 0); ICertificatePal?singleCert; if (OpenSslX509CertificateReader.TryReadX509Pem(bio, out singleCert)) { return(SingleCertToLoaderPal(singleCert)); } // Rewind, try again. OpenSslX509CertificateReader.RewindBio(bio, bioPosition); if (OpenSslX509CertificateReader.TryReadX509Der(bio, out singleCert)) { return(SingleCertToLoaderPal(singleCert)); } // Rewind, try again. OpenSslX509CertificateReader.RewindBio(bio, bioPosition); List <ICertificatePal>?certPals; if (OpenSslPkcsFormatReader.TryReadPkcs7Pem(bio, out certPals)) { return(ListToLoaderPal(certPals)); } // Rewind, try again. OpenSslX509CertificateReader.RewindBio(bio, bioPosition); if (OpenSslPkcsFormatReader.TryReadPkcs7Der(bio, out certPals)) { return(ListToLoaderPal(certPals)); } // Rewind, try again. OpenSslX509CertificateReader.RewindBio(bio, bioPosition); // Capture the exception so in case of failure, the call to BioSeek does not override it. Exception?openSslException; byte[] data = File.ReadAllBytes(fileName); if (OpenSslPkcsFormatReader.TryReadPkcs12(data, password, ephemeralSpecified, out certPals, out openSslException)) { return(ListToLoaderPal(certPals)); } // Since we aren't going to finish reading, leaving the buffer where it was when we got // it seems better than leaving it in some arbitrary other position. // // Use BioSeek directly for the last seek attempt, because any failure here should instead // report the already created (but not yet thrown) exception. if (Interop.Crypto.BioSeek(bio, bioPosition) < 0) { Interop.Crypto.ErrClearError(); } Debug.Assert(openSslException != null); throw openSslException; }
public X509ContentType GetCertContentType(string fileName) { // If we can't open the file, fail right away. using (SafeBioHandle fileBio = Interop.Crypto.BioNewFile(fileName, "rb")) { Interop.Crypto.CheckValidOpenSslHandle(fileBio); int bioPosition = Interop.Crypto.BioTell(fileBio); Debug.Assert(bioPosition >= 0); // X509ContentType.Cert { ICertificatePal?certPal; if (OpenSslX509CertificateReader.TryReadX509Der(fileBio, out certPal)) { certPal.Dispose(); return(X509ContentType.Cert); } OpenSslX509CertificateReader.RewindBio(fileBio, bioPosition); if (OpenSslX509CertificateReader.TryReadX509Pem(fileBio, out certPal)) { certPal.Dispose(); return(X509ContentType.Cert); } OpenSslX509CertificateReader.RewindBio(fileBio, bioPosition); } // X509ContentType.Pkcs7 { if (OpenSslPkcsFormatReader.IsPkcs7Der(fileBio)) { return(X509ContentType.Pkcs7); } OpenSslX509CertificateReader.RewindBio(fileBio, bioPosition); if (OpenSslPkcsFormatReader.IsPkcs7Pem(fileBio)) { return(X509ContentType.Pkcs7); } OpenSslX509CertificateReader.RewindBio(fileBio, bioPosition); } } // X509ContentType.Pkcs12 (aka PFX) { OpenSslPkcs12Reader?pkcs12Reader; if (OpenSslPkcs12Reader.TryRead(File.ReadAllBytes(fileName), out pkcs12Reader)) { pkcs12Reader.Dispose(); return(X509ContentType.Pkcs12); } } // Unsupported format. // Windows throws new CryptographicException(CRYPT_E_NO_MATCH) throw new CryptographicException(); }