File: System\Security\Cryptography\X509Certificates\OpenSslPkcsFormatReader.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Win32.SafeHandles;
 
namespace System.Security.Cryptography.X509Certificates
{
    internal static class OpenSslPkcsFormatReader
    {
        internal static bool IsPkcs7(ReadOnlySpan<byte> rawData)
        {
            using (SafePkcs7Handle pkcs7 = Interop.Crypto.DecodePkcs7(rawData))
            {
                if (pkcs7.IsInvalid)
                {
                    Interop.Crypto.ErrClearError();
                }
                else
                {
                    return true;
                }
 
            }
 
            using (SafeBioHandle bio = Interop.Crypto.CreateMemoryBio())
            {
                Interop.Crypto.CheckValidOpenSslHandle(bio);
 
                if (Interop.Crypto.BioWrite(bio, rawData) != rawData.Length)
                {
                    Interop.Crypto.ErrClearError();
                }
 
                using (SafePkcs7Handle pkcs7 = Interop.Crypto.PemReadBioPkcs7(bio))
                {
                    if (pkcs7.IsInvalid)
                    {
                        Interop.Crypto.ErrClearError();
                        return false;
                    }
 
                    return true;
                }
            }
        }
 
        internal static bool IsPkcs7Der(SafeBioHandle fileBio)
        {
            using (SafePkcs7Handle pkcs7 = Interop.Crypto.D2IPkcs7Bio(fileBio))
            {
                if (pkcs7.IsInvalid)
                {
                    Interop.Crypto.ErrClearError();
                    return false;
                }
 
                return true;
            }
        }
 
        internal static bool IsPkcs7Pem(SafeBioHandle fileBio)
        {
            using (SafePkcs7Handle pkcs7 = Interop.Crypto.PemReadBioPkcs7(fileBio))
            {
                if (pkcs7.IsInvalid)
                {
                    Interop.Crypto.ErrClearError();
                    return false;
                }
 
                return true;
            }
        }
 
        internal static bool TryReadPkcs7Der(ReadOnlySpan<byte> rawData, out ICertificatePal? certPal)
        {
            return TryReadPkcs7Der(rawData, true, out certPal, out _);
        }
 
        internal static bool TryReadPkcs7Der(SafeBioHandle bio, out ICertificatePal? certPal)
        {
            return TryReadPkcs7Der(bio, true, out certPal, out _);
        }
 
        internal static bool TryReadPkcs7Der(ReadOnlySpan<byte> rawData, [NotNullWhen(true)] out List<ICertificatePal>? certPals)
        {
            return TryReadPkcs7Der(rawData, false, out _, out certPals);
        }
 
        internal static bool TryReadPkcs7Der(SafeBioHandle bio, [NotNullWhen(true)] out List<ICertificatePal>? certPals)
        {
            return TryReadPkcs7Der(bio, false, out _, out certPals);
        }
 
        private static bool TryReadPkcs7Der(
            ReadOnlySpan<byte> rawData,
            bool single,
            out ICertificatePal? certPal,
            [NotNullWhen(true)] out List<ICertificatePal>? certPals)
        {
            using (SafePkcs7Handle pkcs7 = Interop.Crypto.DecodePkcs7(rawData))
            {
                if (pkcs7.IsInvalid)
                {
                    certPal = null;
                    certPals = null;
                    Interop.Crypto.ErrClearError();
                    return false;
                }
 
                return TryReadPkcs7(pkcs7, single, out certPal, out certPals);
            }
        }
 
        private static bool TryReadPkcs7Der(
            SafeBioHandle bio,
            bool single,
            out ICertificatePal? certPal,
            [NotNullWhen(true)] out List<ICertificatePal>? certPals)
        {
            using (SafePkcs7Handle pkcs7 = Interop.Crypto.D2IPkcs7Bio(bio))
            {
                if (pkcs7.IsInvalid)
                {
                    certPal = null;
                    certPals = null;
                    Interop.Crypto.ErrClearError();
                    return false;
                }
 
                return TryReadPkcs7(pkcs7, single, out certPal, out certPals);
            }
        }
 
        internal static bool TryReadPkcs7Pem(ReadOnlySpan<byte> rawData, out ICertificatePal? certPal)
        {
            return TryReadPkcs7Pem(rawData, true, out certPal, out _);
        }
 
        internal static bool TryReadPkcs7Pem(SafeBioHandle bio, out ICertificatePal? certPal)
        {
            return TryReadPkcs7Pem(bio, true, out certPal, out _);
        }
 
        internal static bool TryReadPkcs7Pem(ReadOnlySpan<byte> rawData, [NotNullWhen(true)] out List<ICertificatePal>? certPals)
        {
            return TryReadPkcs7Pem(rawData, false, out _, out certPals);
        }
 
        internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out List<ICertificatePal>? certPals)
        {
            return TryReadPkcs7Pem(bio, false, out _, out certPals);
        }
 
        private static bool TryReadPkcs7Pem(
            ReadOnlySpan<byte> rawData,
            bool single,
            out ICertificatePal? certPal,
            [NotNullWhen(true)] out List<ICertificatePal>? certPals)
        {
            using (SafeBioHandle bio = Interop.Crypto.CreateMemoryBio())
            {
                Interop.Crypto.CheckValidOpenSslHandle(bio);
 
                if (Interop.Crypto.BioWrite(bio, rawData) != rawData.Length)
                {
                    Interop.Crypto.ErrClearError();
                }
 
                return TryReadPkcs7Pem(bio, single, out certPal, out certPals);
            }
        }
 
        private static bool TryReadPkcs7Pem(
            SafeBioHandle bio,
            bool single,
            out ICertificatePal? certPal,
            [NotNullWhen(true)] out List<ICertificatePal>? certPals)
        {
            using (SafePkcs7Handle pkcs7 = Interop.Crypto.PemReadBioPkcs7(bio))
            {
                if (pkcs7.IsInvalid)
                {
                    certPal = null;
                    certPals = null;
                    Interop.Crypto.ErrClearError();
                    return false;
                }
 
                return TryReadPkcs7(pkcs7, single, out certPal, out certPals);
            }
        }
 
        private static bool TryReadPkcs7(
            SafePkcs7Handle pkcs7,
            bool single,
            out ICertificatePal? certPal,
            [NotNullWhen(true)] out List<ICertificatePal> certPals)
        {
            List<ICertificatePal>? readPals = single ? null : new List<ICertificatePal>();
 
            using (SafeSharedX509StackHandle certs = Interop.Crypto.GetPkcs7Certificates(pkcs7))
            {
                int count = Interop.Crypto.GetX509StackFieldCount(certs);
 
                if (single)
                {
                    // In single mode for a PKCS#7 signed or signed-and-enveloped file we're supposed to return
                    // the certificate which signed the PKCS#7 file.
                    //
                    // X509Certificate2Collection::Export(X509ContentType.Pkcs7) claims to be a signed PKCS#7,
                    // but doesn't emit a signature block. So this is hard to test.
                    //
                    // TODO(2910): Figure out how to extract the signing certificate, when it's present.
                    throw new CryptographicException(SR.Cryptography_X509_PKCS7_NoSigner);
                }
 
                Debug.Assert(readPals != null); // null if single == true
 
                for (int i = 0; i < count; i++)
                {
                    // Use FromHandle to duplicate the handle since it would otherwise be freed when the PKCS7
                    // is Disposed.
                    IntPtr certHandle = Interop.Crypto.GetX509StackField(certs, i);
                    ICertificatePal pal = CertificatePal.FromHandle(certHandle);
                    readPals.Add(pal);
                }
            }
 
            certPal = null;
            certPals = readPals;
            return true;
        }
 
        internal static bool TryReadPkcs12(
            ReadOnlySpan<byte> rawData,
            SafePasswordHandle password,
            bool ephemeralSpecified,
            bool readingFromFile,
            [NotNullWhen(true)] out ICertificatePal? certPal,
            out Exception? openSslException)
        {
            return TryReadPkcs12(
                rawData,
                password,
                single: true,
                ephemeralSpecified,
                readingFromFile,
                out certPal!,
                out _,
                out openSslException);
        }
 
        internal static bool TryReadPkcs12(
            ReadOnlySpan<byte> rawData,
            SafePasswordHandle password,
            bool ephemeralSpecified,
            bool readingFromFile,
            [NotNullWhen(true)] out List<ICertificatePal>? certPals,
            out Exception? openSslException)
        {
            return TryReadPkcs12(
                rawData,
                password,
                single: false,
                ephemeralSpecified,
                readingFromFile,
                out _,
                out certPals!,
                out openSslException);
        }
 
        private static bool TryReadPkcs12(
            ReadOnlySpan<byte> rawData,
            SafePasswordHandle password,
            bool single,
            bool ephemeralSpecified,
            bool readingFromFile,
            out ICertificatePal? readPal,
            out List<ICertificatePal>? readCerts,
            out Exception? openSslException)
        {
            // DER-PKCS12
            OpenSslPkcs12Reader? pfx;
 
            if (!OpenSslPkcs12Reader.TryRead(rawData, out pfx, out openSslException))
            {
                readPal = null;
                readCerts = null;
                return false;
            }
 
            using (pfx)
            {
                return TryReadPkcs12(rawData, pfx, password, single, ephemeralSpecified, readingFromFile, out readPal, out readCerts);
            }
        }
 
        private static bool TryReadPkcs12(
            ReadOnlySpan<byte> rawData,
            OpenSslPkcs12Reader pfx,
            SafePasswordHandle password,
            bool single,
            bool ephemeralSpecified,
            bool readingFromFile,
            out ICertificatePal? readPal,
            out List<ICertificatePal>? readCerts)
        {
            X509Certificate.EnforceIterationCountLimit(ref rawData, readingFromFile, password.PasswordProvided);
            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;
        }
    }
}