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);
        }
Exemple #9
0
 internal static partial ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
 {
     return(OpenSslX509CertificateReader.FromFile(fileName, password, keyStorageFlags));
 }
Exemple #10
0
 internal static partial ICertificatePal FromBlob(ReadOnlySpan <byte> rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
 {
     return(OpenSslX509CertificateReader.FromBlob(rawData, password, keyStorageFlags));
 }
Exemple #11
0
 internal static partial ICertificatePal FromOtherCert(X509Certificate copyFrom)
 {
     return(OpenSslX509CertificateReader.FromOtherCert(copyFrom));
 }
Exemple #12
0
 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);
                }
            }
        }
Exemple #14
0
        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;
        }
Exemple #15
0
        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();
        }