【问题标题】:Export Certificate as PFX with proper chain of signing使用适当的签名链将证书导出为 PFX
【发布时间】:2021-12-17 12:17:15
【问题描述】:

我阅读了一些帖子(不再存在)并想出了以下生成 PFX 证书的代码。它适用于创建此自签名证书的部分。

我正在尝试将其扩展为创建一个自签名证书,并从该证书中创建它的“子级”。我尝试了很多东西,但结果都没有真正导出证书及其链。

当前的代码可以导出带有包含 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 推荐我检查这个链接:https://stackoverflow.com/a/48210587/6535399 我相信我以前见过这个,但仍然继续它并将代码(原样)复制到一个新的空白项目并运行(使用密码将“导出”添加到 PFX)。

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

然后我导入了证书,但仍然不准确:我将在下方发布生成的证书(图像)和“正确”的证书 - Windows 屏幕是葡萄牙语(因为它是我的操作系统语言),而是每个证书的“证书路径”选项卡。

从示例代码创建的证书指出了这一点:

但是应该是这样的:

【问题讨论】:

  • 我在这里忘记了什么吗?
  • 我建议切换到 BouncyCastle 库来完成这种工作,而不是 Win32 CryptApi。 BouncyCastle 是更流行的库,在 C# 或 Java 中有更多示例stackoverflow.com/questions/3770233/…
  • 我明白,但是对于这个来说,这个库太多了——我是从头开始编写的(几乎,只使用标准的 .Net Framework 库,没有包)——这就是重点,缺少什么那里?
  • 与其 P/Invoking 到 Win32 CertCreateSelfSignCertificate,为什么不直接使用专为此设计的 .NET CertificateRequest API? stackoverflow.com/a/48210587/6535399
  • 我会调查一下 - 这个实现是在较旧的 .Net 版本上,如果可以的话我会转移 - 我会先试试看 - 谢谢

标签: c# self-signed ca pfx


【解决方案1】:

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

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

将一个证书导出为 PFX。这是

的简写
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");

如果您将其导出为“链”,那么您可能不希望存在颁发者的私钥,因此如果您还没有该证书的公开版本,请使用 new X509Certificate2(issuer.RawData)做一个。

【讨论】:

  • 确实有效 - 我将修改原始代码,看看我可以在哪里实现它(如果在旧版本的 .Net Framework 上可能的话) - 谢谢
【解决方案2】:

我会说在开发证书中瞄准这些品质:

  • 根证书授权文件,例如myRoot.ca
  • 受密码保护的 PKCS12 文件(包含私钥 + 证书),其根为上述 CA,例如 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 使用颁发的证书,但在某些情况下我使用诸如cert-manager 之类的工具来自动颁发证书。

【讨论】:

  • 我会阅读它以了解更多信息,我看到你给出了一个很好且解释清楚的答案,但还不是我想要的 - 你看,我没有为网站,我将在内部使用此证书,对于我的每个用户,只需要确定我是发出它的人 - 因此让我的自签名证书(从该代码创建)为“userX”、Y 和Z(或任何用户名)并部署 - 我不想为此依赖第三方库,因为代码几乎就在那里......只需将签名CA添加到它(但我得到想法)
  • 如果您想向用户颁发客户端证书,除了不同的证书扩展属性外,流程基本相同 - this repo 有一个示例。您需要找到每个步骤的 C# 代码。然后,您需要将根 CA 部署到每个用户,这可能很难管理。
猜你喜欢
  • 2014-10-21
  • 1970-01-01
  • 2016-12-14
  • 2013-09-18
  • 2010-11-13
  • 1970-01-01
  • 2016-10-20
  • 1970-01-01
  • 2011-08-12
相关资源
最近更新 更多