File: System\Security\Cryptography\X509Certificates\UnixExportProvider.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.Buffers;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography.Pkcs;
using Microsoft.Win32.SafeHandles;
using Internal.Cryptography;
 
namespace System.Security.Cryptography.X509Certificates
{
    internal abstract class UnixExportProvider : IExportPal
    {
        private static readonly Oid s_Pkcs12X509CertBagTypeOid = new Oid(Oids.Pkcs12X509CertBagType, null);
 
        protected ICertificatePalCore? _singleCertPal;
        protected X509Certificate2Collection? _certs;
 
        internal UnixExportProvider(ICertificatePalCore singleCertPal)
        {
            _singleCertPal = singleCertPal;
        }
 
        internal UnixExportProvider(X509Certificate2Collection certs)
        {
            _certs = certs;
        }
 
        public void Dispose()
        {
            // Don't dispose any of the resources, they're still owned by the caller.
            _singleCertPal = null;
            _certs = null;
        }
 
        protected abstract byte[] ExportPkcs7();
 
        protected abstract byte[] ExportPkcs8(
            ICertificatePalCore certificatePal,
            PbeParameters pbeParameters,
            ReadOnlySpan<char> password);
 
        public byte[]? Export(X509ContentType contentType, SafePasswordHandle password)
        {
            Debug.Assert(password != null);
            switch (contentType)
            {
                case X509ContentType.Cert:
                    return ExportX509Der();
                case X509ContentType.Pfx:
                    return ExportPkcs12(Helpers.Windows3desPbe, password);
                case X509ContentType.Pkcs7:
                    return ExportPkcs7();
                case X509ContentType.SerializedCert:
                case X509ContentType.SerializedStore:
                    throw new PlatformNotSupportedException(SR.Cryptography_Unix_X509_SerializedExport);
                default:
                    throw new CryptographicException(SR.Cryptography_X509_InvalidContentType);
            }
        }
 
        public byte[] ExportPkcs12(Pkcs12ExportPbeParameters exportParameters, SafePasswordHandle password)
        {
            PbeParameters pbeParameters = Helpers.MapExportParametersToPbeParameters(exportParameters);
            return ExportPkcs12(pbeParameters, password);
        }
 
        private byte[]? ExportX509Der()
        {
            if (_singleCertPal != null)
            {
                return _singleCertPal.RawData;
            }
 
            // Windows/Desktop compatibility: Exporting a collection (or store) as
            // X509ContentType.Cert returns the equivalent of FirstOrDefault(),
            // so anything past _certs[0] is ignored, and an empty collection is
            // null (not an Exception)
            if (_certs!.Count == 0)
            {
                return null;
            }
 
            return _certs[0].RawData;
        }
 
        public byte[] ExportPkcs12(PbeParameters exportParameters, SafePasswordHandle password)
        {
            bool gotRef = false;
 
            try
            {
                password.DangerousAddRef(ref gotRef);
                ReadOnlySpan<char> passwordSpan = password.DangerousGetSpan();
 
                int localKeyIdCounter = 1;
                Pkcs12Builder builder = new();
                Pkcs12SafeContents certContainer = new();
                Pkcs12SafeContents keyContainer = new();
 
                if (_singleCertPal is not null)
                {
                    AddCertPalToSafeContents(
                        certContainer,
                        keyContainer,
                        _singleCertPal,
                        passwordSpan,
                        ref localKeyIdCounter);
                }
                else
                {
                    Debug.Assert(_certs is not null);
 
                    // Add the certificates in reverse order for compat.
                    for (int i = _certs.Count - 1; i >= 0; i--)
                    {
                        X509Certificate2 cert = _certs[i];
                        AddCertPalToSafeContents(
                            certContainer,
                            keyContainer,
                            cert.Pal,
                            passwordSpan,
                            ref localKeyIdCounter);
                    }
                }
 
                builder.AddSafeContentsEncrypted(certContainer, passwordSpan, exportParameters);
                builder.AddSafeContentsUnencrypted(keyContainer);
                builder.SealWithMac(passwordSpan, exportParameters.HashAlgorithm, exportParameters.IterationCount);
                return builder.Encode();
            }
            finally
            {
                if (gotRef)
                {
                    password.DangerousRelease();
                }
            }
 
            void AddCertPalToSafeContents(
                Pkcs12SafeContents certContainer,
                Pkcs12SafeContents keyContainer,
                ICertificatePalCore certificatePal,
                ReadOnlySpan<char> password,
                ref int localKeyIdCounter)
            {
                Pkcs12CertBag certBag = new(s_Pkcs12X509CertBagTypeOid, PkcsHelpers.EncodeOctetString(certificatePal.RawData));
 
                if (certificatePal.HasPrivateKey)
                {
                    Span<byte> localKeyIdAttributeValue = stackalloc byte[sizeof(int)];
                    BinaryPrimitives.WriteInt32LittleEndian(localKeyIdAttributeValue, localKeyIdCounter);
                    Pkcs9LocalKeyId keyId = new(localKeyIdAttributeValue);
                    Pkcs12ShroudedKeyBag keyBag = new(ExportPkcs8(certificatePal, exportParameters, password), skipCopy: true);
 
                    certBag.Attributes.Add(keyId);
                    keyBag.Attributes.Add(keyId);
                    keyContainer.AddSafeBag(keyBag);
                    localKeyIdCounter++;
                }
 
                certContainer.AddSafeBag(certBag);
            }
        }
    }
}