将证书导出为具有适当签名链的 PFX

Export Certificate as PFX with proper chain of signing

我阅读了一些 post(它们不再存在)并提出了以下生成 PFX 证书的代码。它对创建此 self-signed 证书的部分工作正常。

我正在尝试扩展它以创建一个 self-signed 证书,并从那个证书创建它的“孩子”。我尝试了很多东西,但 none 结果实际上导出了带有证书链的证书。

当前代码已达到导出具有包含 CA 的 PFX 并导入它的程度,这将包括两个证书,但不会相互关联。

这是一段很长的代码,但该操作应该在它的最后一个“创建”功能上起作用。

using Sys = global::System;
using SysInterop = global::System.Runtime.InteropServices;
using SysCry509 = global::System.Security.Cryptography.X509Certificates;
using MdPFX = global::PFX;
public static class PFX
{
    #region NativeCode
    private const string DLL_Kernel = "kernel32.dll";
    private const string DLL_AdvApi = "AdvApi32.dll";
    private const string DLL_Crypt = "Crypt32.dll";

    [SysInterop.StructLayout(SysInterop.LayoutKind.Sequential)] private struct SystemTime
    {
        public short Year;
        public short Month;
        public short DayOfWeek;
        public short Day;
        public short Hour;
        public short Minute;
        public short Second;
        public short Milliseconds;
    }

    [SysInterop.StructLayout(SysInterop.LayoutKind.Sequential)] private struct CryptoApiBlob
    {
        public int DataLength;
        public Sys.IntPtr Data;

        public CryptoApiBlob(int dataLength, Sys.IntPtr data)
        {
            this.DataLength = dataLength;
            this.Data = data;
        }
    }

    [SysInterop.StructLayout(SysInterop.LayoutKind.Sequential)] private struct CryptKeyProviderInformation
    {
        [SysInterop.MarshalAs(SysInterop.UnmanagedType.LPWStr)] public string ContainerName;
        [SysInterop.MarshalAs(SysInterop.UnmanagedType.LPWStr)] public string ProviderName;
        public int ProviderType;
        public int Flags;
        public int ProviderParameterCount;
        public Sys.IntPtr ProviderParameters;
        public int KeySpec;
    }

    [SysInterop.DllImport(MdPFX.DLL_Kernel, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool FileTimeToSystemTime([SysInterop.In] ref long fileTime, out MdPFX.SystemTime systemTime);
    [SysInterop.DllImport(MdPFX.DLL_AdvApi, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CryptAcquireContextW(out Sys.IntPtr providerContext, [SysInterop.MarshalAs(SysInterop.UnmanagedType.LPWStr)] string container, [SysInterop.MarshalAs(SysInterop.UnmanagedType.LPWStr)] string provider, int providerType, int flags);
    [SysInterop.DllImport(MdPFX.DLL_AdvApi, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CryptReleaseContext(Sys.IntPtr providerContext, int flags);
    [SysInterop.DllImport(MdPFX.DLL_AdvApi, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CryptGenKey(Sys.IntPtr providerContext, int algorithmId, int flags, out Sys.IntPtr cryptKeyHandle);
    [SysInterop.DllImport(MdPFX.DLL_AdvApi, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CryptDestroyKey(Sys.IntPtr cryptKeyHandle);
    [SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CertStrToNameW(int certificateEncodingType, Sys.IntPtr x500, int strType, Sys.IntPtr reserved, [SysInterop.MarshalAs(SysInterop.UnmanagedType.LPArray)] [SysInterop.Out] byte[] encoded, ref int encodedLength, out Sys.IntPtr errorString);
    [SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)] private static extern Sys.IntPtr CertCreateSelfSignCertificate(Sys.IntPtr providerHandle, [SysInterop.In] ref MdPFX.CryptoApiBlob subjectIssuerBlob, int flags, [SysInterop.In] ref MdPFX.CryptKeyProviderInformation keyProviderInformation, Sys.IntPtr signatureAlgorithm, [SysInterop.In] ref MdPFX.SystemTime startTime, [SysInterop.In] ref MdPFX.SystemTime endTime, Sys.IntPtr extensions);
    [SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CertFreeCertificateContext(Sys.IntPtr certificateContext);
    [SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)] private static extern Sys.IntPtr CertOpenStore([SysInterop.MarshalAs(SysInterop.UnmanagedType.LPStr)] string storeProvider, int messageAndCertificateEncodingType, Sys.IntPtr cryptProvHandle, int flags, Sys.IntPtr parameters);
    [SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CertCloseStore(Sys.IntPtr certificateStoreHandle, int flags);
    [SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CertAddCertificateContextToStore(Sys.IntPtr certificateStoreHandle, Sys.IntPtr certificateContext, int addDisposition, out Sys.IntPtr storeContextPtr);
    [SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CertSetCertificateContextProperty(Sys.IntPtr certificateContext, int propertyId, int flags, [SysInterop.In] ref MdPFX.CryptKeyProviderInformation data);
    [SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool PFXExportCertStoreEx(Sys.IntPtr certificateStoreHandle, ref MdPFX.CryptoApiBlob pfxBlob, Sys.IntPtr password, Sys.IntPtr reserved, int flags);
    private static void Check(bool nativeCallSucceeded) { if (!nativeCallSucceeded) { SysInterop.Marshal.ThrowExceptionForHR(SysInterop.Marshal.GetHRForLastWin32Error()); } }

    private static MdPFX.SystemTime ToSystemTime(Sys.DateTime dateTime)
    {
        long fileTime = dateTime.ToFileTime();
        MdPFX.SystemTime systemTime = default(MdPFX.SystemTime);
        MdPFX.Check(MdPFX.FileTimeToSystemTime(ref fileTime, out systemTime));
        return systemTime;
    }
    #endregion

    public static byte[] Create(string commonName, Sys.DateTime startTime, Sys.DateTime endTime, Sys.Security.SecureString password)
    {
        byte[] pfxData;
        if (commonName == null) { commonName = string.Empty; }
        MdPFX.SystemTime startSystemTime = MdPFX.ToSystemTime(startTime);
        MdPFX.SystemTime endSystemTime = MdPFX.ToSystemTime(endTime);
        string containerName = Sys.Guid.NewGuid().ToString();
        SysInterop.GCHandle dataHandle = default(SysInterop.GCHandle);
        Sys.IntPtr providerContext = Sys.IntPtr.Zero;
        Sys.IntPtr cryptKey = Sys.IntPtr.Zero;
        Sys.IntPtr certContext = Sys.IntPtr.Zero;
        Sys.IntPtr certStore = Sys.IntPtr.Zero;
        Sys.IntPtr storeCertContext = Sys.IntPtr.Zero;
        Sys.IntPtr passwordPtr = Sys.IntPtr.Zero;
        Sys.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
        try
        {
            MdPFX.Check(MdPFX.CryptAcquireContextW(out providerContext, containerName, null, 1, 8));
            MdPFX.Check(MdPFX.CryptGenKey(providerContext, 1, 1, out cryptKey));
            Sys.IntPtr errorStringPtr = Sys.IntPtr.Zero;
            int nameDataLength = 0;
            byte[] nameData = null;
            dataHandle = SysInterop.GCHandle.Alloc(commonName, SysInterop.GCHandleType.Pinned);
            if (!MdPFX.CertStrToNameW(0x00010001, dataHandle.AddrOfPinnedObject(), 3, Sys.IntPtr.Zero, null, ref nameDataLength, out errorStringPtr)) { throw new Sys.ArgumentException(SysInterop.Marshal.PtrToStringUni(errorStringPtr)); }
            nameData = new byte[nameDataLength];
            if (!MdPFX.CertStrToNameW(0x00010001, dataHandle.AddrOfPinnedObject(), 3, Sys.IntPtr.Zero, nameData, ref nameDataLength, out errorStringPtr)) { throw new Sys.ArgumentException(SysInterop.Marshal.PtrToStringUni(errorStringPtr)); }
            dataHandle.Free();
            dataHandle = SysInterop.GCHandle.Alloc(nameData, SysInterop.GCHandleType.Pinned);
            MdPFX.CryptoApiBlob nameBlob = new MdPFX.CryptoApiBlob(nameData.Length, dataHandle.AddrOfPinnedObject());
            MdPFX.CryptKeyProviderInformation kpi = new MdPFX.CryptKeyProviderInformation();
            kpi.ContainerName = containerName;
            kpi.ProviderType = 1;
            kpi.KeySpec = 1;
            certContext = MdPFX.CertCreateSelfSignCertificate(providerContext, ref nameBlob, 0, ref kpi, Sys.IntPtr.Zero, ref startSystemTime, ref endSystemTime, Sys.IntPtr.Zero);
            MdPFX.Check(certContext != Sys.IntPtr.Zero);
            dataHandle.Free();
            certStore = MdPFX.CertOpenStore("Memory", 0, Sys.IntPtr.Zero, 0x2000, Sys.IntPtr.Zero);
            MdPFX.Check(certStore != Sys.IntPtr.Zero);
            MdPFX.Check(MdPFX.CertAddCertificateContextToStore(certStore, certContext, 1, out storeCertContext));
            MdPFX.CertSetCertificateContextProperty(storeCertContext, 2, 0, ref kpi);
            if (password != null) { passwordPtr = SysInterop.Marshal.SecureStringToCoTaskMemUnicode(password); }
            MdPFX.CryptoApiBlob pfxBlob = new MdPFX.CryptoApiBlob();
            MdPFX.Check(MdPFX.PFXExportCertStoreEx(certStore, ref pfxBlob, passwordPtr, Sys.IntPtr.Zero, 7));
            pfxData = new byte[pfxBlob.DataLength];
            dataHandle = SysInterop.GCHandle.Alloc(pfxData, SysInterop.GCHandleType.Pinned);
            pfxBlob.Data = dataHandle.AddrOfPinnedObject();
            MdPFX.Check(MdPFX.PFXExportCertStoreEx(certStore, ref pfxBlob, passwordPtr, Sys.IntPtr.Zero, 7));
            dataHandle.Free();
        }
        finally
        {
            if (passwordPtr != Sys.IntPtr.Zero) { SysInterop.Marshal.ZeroFreeCoTaskMemUnicode(passwordPtr); }
            if (dataHandle.IsAllocated) { dataHandle.Free(); }
            if (certContext != Sys.IntPtr.Zero) { MdPFX.CertFreeCertificateContext(certContext); }
            if (storeCertContext != Sys.IntPtr.Zero) { MdPFX.CertFreeCertificateContext(storeCertContext); }
            if (certStore != Sys.IntPtr.Zero) { MdPFX.CertCloseStore(certStore, 0); }
            if (cryptKey != Sys.IntPtr.Zero) { MdPFX.CryptDestroyKey(cryptKey); }
            if (providerContext != Sys.IntPtr.Zero)
            {
                MdPFX.CryptReleaseContext(providerContext, 0);
                MdPFX.CryptAcquireContextW(out providerContext, containerName, null, 1, 0x10);
            }
        }
        return pfxData;
    }

    public static Sys.Security.SecureString CreateSecurePassword(string insecurePassword)
    {
        if (!string.IsNullOrEmpty(insecurePassword))
        {
            Sys.Security.SecureString password = new Sys.Security.SecureString();
            foreach (char ch in insecurePassword) { password.AppendChar(ch); }
            password.MakeReadOnly();
            return password;
        } else { return null; }
    }

    public static byte[] Create(string commonName, Sys.DateTime startTime, Sys.DateTime endTime, string insecurePassword)
    {
        byte[] pfxData;
        Sys.Security.SecureString password = null;
        try
        {
            password = MdPFX.CreateSecurePassword(insecurePassword);
            pfxData = MdPFX.Create(commonName, startTime, endTime, password);
        } finally { if (password != null) { password.Dispose(); } }
        return pfxData;
    }

    public static byte[] Create(string commonName, int YearsValid, Sys.Security.SecureString password)
    {
        if (!commonName.StartsWith("CN=", Sys.StringComparison.OrdinalIgnoreCase)) { commonName = "CN=" + commonName; }
        return MdPFX.Create(commonName, Sys.DateTime.Now, Sys.DateTime.Now.AddYears(YearsValid), password);
    }

    public static void Create(Sys.IO.Stream save, string commonName, int YearsValid, Sys.Security.SecureString password)
    {
        byte[] certificateData = MdPFX.Create(commonName, 5, password);
        using (Sys.IO.BinaryWriter binWriter = new Sys.IO.BinaryWriter(save))
        {
            binWriter.Write(certificateData);
            binWriter.Flush();
        }
    }

    public static byte[] Create(string commonName, Sys.DateTime startTime, Sys.DateTime endTime) { return MdPFX.Create(commonName, startTime, endTime, (Sys.Security.SecureString)null); }
    public static byte[] Create(string commonName, int YearsValid, string insecurePassword) { using (Sys.Security.SecureString password = MdPFX.CreateSecurePassword(insecurePassword)) { return MdPFX.Create(commonName, YearsValid, password); } }
    public static void Create(Sys.IO.Stream save, string commonName, int YearsValid, string insecurePassword) { using (Sys.Security.SecureString password = MdPFX.CreateSecurePassword(insecurePassword)) { MdPFX.Create(save, commonName, YearsValid, password); } }
    public static void Create(string savePath, string commonName, int YearsValid, Sys.Security.SecureString password) { using (Sys.IO.FileStream fStream = Sys.IO.File.Open(savePath, Sys.IO.FileMode.Create)) { MdPFX.Create(fStream, commonName, YearsValid, password); } }
    public static void Create(string savePath, string commonName, int YearsValid, string insecurePassword) { using (Sys.Security.SecureString password = MdPFX.CreateSecurePassword(insecurePassword)) { using (Sys.IO.FileStream fStream = Sys.IO.File.Open(savePath, Sys.IO.FileMode.Create)) { MdPFX.Create(fStream, commonName, YearsValid, password); } } }

    public static byte[] Create(SysCry509.X509Certificate2 certificate, string insecurePassword, SysCry509.X509Certificate2 signingCert, SysCry509.X509Certificate2Collection chain = null)
    {
        SysCry509.X509Certificate2Collection col = new SysCry509.X509Certificate2Collection(certificate);
        if (chain != null) { col.AddRange(chain); }
        if (signingCert != null)
        {
            SysCry509.X509Certificate2 sigCertNoPK = new SysCry509.X509Certificate2(signingCert.Export(SysCry509.X509ContentType.Cert));
            col.Add(sigCertNoPK);
        }
        return col.Export(SysCry509.X509ContentType.Pfx, insecurePassword);
    }

    public static byte[] Create(string commonName, string insecurePassword, int YearsValid, SysCry509.X509Certificate2 signingCert, SysCry509.X509Certificate2Collection chain = null)
    {
        SysCry509.X509Certificate2 certificate = new SysCry509.X509Certificate2();
        certificate.Import(MdPFX.Create(commonName, YearsValid, insecurePassword), insecurePassword, (SysCry509.X509KeyStorageFlags.PersistKeySet | SysCry509.X509KeyStorageFlags.Exportable));
        return MdPFX.Create(certificate, insecurePassword, signingCert, chain: chain);
    }

    public static SysCry509.X509Certificate2 Load(byte[] rawData, string insecurePassword)
    {
        try
        {
            SysCry509.X509Certificate2 rCert = new SysCry509.X509Certificate2();
            rCert.Import(rawData, insecurePassword, (SysCry509.X509KeyStorageFlags.PersistKeySet | SysCry509.X509KeyStorageFlags.Exportable));
            return rCert;
        } catch { return null; }
    }

    public static byte[] Create(string commonName, string insecurePassword, int YearsValid, bool Signed, SysCry509.X509Certificate2Collection chain = null)
    {
        if (Signed)
        {
            SysCry509.X509Store store = new SysCry509.X509Store(typeof(MdPFX).FullName, SysCry509.StoreLocation.LocalMachine);
            store.Open(SysCry509.OpenFlags.ReadWrite);
            const string rCertN = "A.Root.Cert.Name";
            SysCry509.X509Certificate2 rCert = null;
            if (store.Certificates.Count > 0) { foreach (SysCry509.X509Certificate2 c in store.Certificates) { if (c.SubjectName.Name == rCertN) { rCert = c; break; } } }
            if (rCert == null)
            {
                rCert = MdPFX.Load(MdPFX.Create(rCertN, 10, "A.Root.Cert.Pass"), "A.Root.Cert.Pass");
                store.Add(rCert);
            }
            store.Close();
            return MdPFX.Create(commonName, insecurePassword, YearsValid, rCert, chain: chain);
        } else { return MdPFX.Create(commonName, YearsValid, insecurePassword); }
    }
}

然后,如果我 运行 喜欢这样,它不会将带有链的证书提供给创建的“CA”。

internal static class Program
{
    internal static void Main()
    {
        Sys.IO.File.WriteAllBytes("C:\Users\User\Desktop\cert.pfx", MdPFX.Create("My.Name", "Pass.123", 10, true, chain: null));
    }
}

更新问题:2022-01-10

有一件事,@bartonjs 建议我检查这个 link: 我相信我以前见过这个,但仍然继续它并将 soition 上的代码(按原样)复制到一个新的空白项目和 运行(使用密码将“导出”添加到 PFX)。

File.WriteAllBytes("C:\signed.pfx", cert.Export(X509ContentType.Pfx, "pwdpwdpwd"));

然后我导入了证书,但仍然不准确:我将 post 生成的证书(图像)和下面“正确”的证书 - windows 屏幕是在葡萄牙语中(因为它是我的 OS 语言),但是是每个证书的“证书路径”选项卡。

从示例代码创建的证书表明:

但应该是这样的:

我会说在开发证书中以这些品质为目标:

  • 根证书颁发机构文件,例如myRoot.ca
  • 密码保护的PKCS12文件(包含私钥+证书),root为上述CA,eg mySslCert p12.
  • 后者也可以是通配符证书,例如可用于*.mycompany.com下的多个子域,这在简单管理方面很有用。

创作

我个人更喜欢使用 OpenSSL 来创建证书,因为这是保护互联网安全的技术,而且我确信没有任何特定于颁发的证书的技术。

查看我的 certificates repositorymakeCerts.sh 文件,了解一些 OpenSSL 命令:

  • 创建根 CA 密钥对
  • 创建根证书
  • 创建 SSL 密钥对
  • 创建 SSL 证书签名请求(可以是通配符证书)
  • 创建 SSL 证书
  • 创建密码保护的 PKCS12 文件

如果您想使用C# 创建证书,那么您需要遵循相同的6 个步骤并生成相同的文件。希望这能让您的要求更清楚。

部署

如今在真实环境中,您可能最终会部署根 CA 文件(在我的示例中为 mycompany.ca.pem)和 PKCS12 文件(在我的示例中为mycompany.ssl.p12)。

这在专用网络中的专用 PKI 设置中很常见,因此在开发人员 PC 上进行模拟非常有用。我的 .NET Example API uses the certs issued, though in some cases I use tools such as cert-manager 自动发行。

PFX 文件不包含证书链,它们只包含证书(可能碰巧形成一个链)。

cert.Export(X509ContentType.Pfx, "pwdpwdpwd")

将一个证书导出为 PFX。 shorthand

X509Certificate2Collection coll = new X509Certificate2Collection();
coll.Add(cert);
coll.Export(X509ContentType.Pfx, "pwdpwdpwd");

鉴于此,将两个证书导出到一个 PFX 的方法应该是显而易见的:

X509Certificate2Collection coll = new X509Certificate2Collection();
coll.Add(issuer);
coll.Add(cert);
coll.Export(X509ContentType.Pfx, "pwdpwdpwd");

如果您将其导出为“链”,那么您可能不希望出现发行者的私钥,因此如果您还没有 public-only 版本证书,用new X509Certificate2(issuer.RawData)做一个