【问题标题】:Generating a self-signed X509Certificate2 certificate with its private key使用其私钥生成自签名 X509Certificate2 证书
【发布时间】:2015-08-26 12:16:07
【问题描述】:

我对@9​​87654321@ 不是最熟悉,但是我正在尝试生成一个自签名的 X509Certificate2 证书。

完整代码如下:

using System;
using System.Security;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Security.Cryptography.X509Certificates;

namespace Example
{

    [StructLayout(LayoutKind.Sequential)]
    public 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;
    }

    public static class MarshalHelper
    {
        public static void ErrorCheck(bool nativeCallSucceeded)
        {
            if (!nativeCallSucceeded)
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        }
    }

    public static class DateTimeExtensions
    {

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool FileTimeToSystemTime(ref long fileTime, out SystemTime systemTime);

        public static SystemTime ToSystemTime(this DateTime dateTime)
        {
            long fileTime = dateTime.ToFileTime();
            SystemTime systemTime;
            MarshalHelper.ErrorCheck(FileTimeToSystemTime(ref fileTime, out systemTime));
            return systemTime;
        }
    }

    class X509Certificate2Helper
    {

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        static extern bool CryptAcquireContextW(out IntPtr providerContext, string container, string provider, int providerType, int flags);

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool CryptReleaseContext(IntPtr providerContext, int flags);

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool CryptGenKey(IntPtr providerContext, int algorithmId, int flags, out IntPtr cryptKeyHandle);

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool CryptDestroyKey(IntPtr cryptKeyHandle);

        [DllImport("crypt32.dll", SetLastError = true)]
        static extern bool CertStrToNameW(int certificateEncodingType, IntPtr x500, int strType, IntPtr reserved, byte[] encoded, ref int encodedLength, out IntPtr errorString);

        [DllImport("crypt32.dll", SetLastError = true)]
        static extern IntPtr CertCreateSelfSignCertificate(IntPtr providerHandle, ref CryptoApiBlob subjectIssuerBlob, int flags, ref CryptKeyProviderInformation keyProviderInformation, IntPtr signatureAlgorithm, ref SystemTime startTime, ref SystemTime endTime, IntPtr extensions);

        [DllImport("crypt32.dll", SetLastError = true)]
        static extern bool CertFreeCertificateContext(IntPtr certificateContext);

        [DllImport("crypt32.dll", SetLastError = true)]
        static extern bool CertSetCertificateContextProperty(IntPtr certificateContext, int propertyId, int flags, ref CryptKeyProviderInformation data);

        public static X509Certificate2 GenerateSelfSignedCertificate(String name = "CN = Example", DateTime? startTime = null, DateTime? endTime = null)
        {
            if (name == null)
                name = String.Empty;
            var startSystemTime = default(SystemTime);
            if (startTime == null || (DateTime)startTime < DateTime.FromFileTimeUtc(0))
                startTime = DateTime.FromFileTimeUtc(0);
            var startSystemTime = ((DateTime)startTime).ToSystemTime();
            if (endTime == null)
                endTime = DateTime.MaxValue;
            var endSystemTime = ((DateTime)endTime).ToSystemTime();
            string containerName = Guid.NewGuid().ToString();
            GCHandle dataHandle = new GCHandle();
            IntPtr providerContext = IntPtr.Zero;
            IntPtr cryptKey = IntPtr.Zero;
            IntPtr certificateContext = IntPtr.Zero;
            IntPtr algorithmPointer = IntPtr.Zero;
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                MarshalHelper.ErrorCheck(CryptAcquireContextW(out providerContext, containerName, null, 1, 0x8));
                MarshalHelper.ErrorCheck(CryptGenKey(providerContext, 1, 0x8000001, out cryptKey));
                IntPtr errorStringPtr;
                int nameDataLength = 0;
                byte[] nameData;
                dataHandle = GCHandle.Alloc(name, GCHandleType.Pinned);
                if (!CertStrToNameW(0x00010001, dataHandle.AddrOfPinnedObject(), 3, IntPtr.Zero, null, ref nameDataLength, out errorStringPtr))
                {
                    string error = Marshal.PtrToStringUni(errorStringPtr);
                    throw new ArgumentException(error);
                }
                nameData = new byte[nameDataLength];
                if (!CertStrToNameW(0x00010001, dataHandle.AddrOfPinnedObject(), 3, IntPtr.Zero, nameData, ref nameDataLength, out errorStringPtr))
                {
                    string error = Marshal.PtrToStringUni(errorStringPtr);
                    throw new ArgumentException(error);
                }
                dataHandle.Free();
                dataHandle = GCHandle.Alloc(nameData, GCHandleType.Pinned);
                CryptoApiBlob nameBlob = new CryptoApiBlob { cbData = nameData.Length, pbData = dataHandle.AddrOfPinnedObject() };
                dataHandle.Free();
                CryptKeyProviderInformation keyProvider = new CryptKeyProviderInformation { pwszContainerName = containerName, dwProvType = 1, dwKeySpec = 1 };
                CryptAlgorithmIdentifier algorithm = new CryptAlgorithmIdentifier { pszObjId = "1.2.840.113549.1.1.13", Parameters = new CryptoApiBlob() };
                algorithmPointer = Marshal.AllocHGlobal(Marshal.SizeOf(algorithm));
                Marshal.StructureToPtr(algorithm, algorithmPointer, false);
                certificateContext = CertCreateSelfSignCertificate(providerContext, ref nameBlob, 0, ref keyProvider, algorithmPointer, ref startSystemTime, ref endSystemTime, IntPtr.Zero);
                MarshalHelper.ErrorCheck(certificateContext != IntPtr.Zero);
                return new X509Certificate2(certificateContext);
            }
            finally
            {
                if (dataHandle.IsAllocated)
                    dataHandle.Free();
                if (certificateContext != IntPtr.Zero)
                    CertFreeCertificateContext(certificateContext);
                if (cryptKey != IntPtr.Zero)
                    CryptDestroyKey(cryptKey);
                if (providerContext != IntPtr.Zero)
                    CryptReleaseContext(providerContext, 0);
                if (algorithmPointer != IntPtr.Zero)
                {
                    Marshal.DestroyStructure(algorithmPointer, typeof(CryptAlgorithmIdentifier));
                    Marshal.FreeHGlobal(algorithmPointer);
                }
            }
        }

        struct CryptoApiBlob
        {
            public Int32 cbData;
            public IntPtr pbData;
        }

        struct CryptAlgorithmIdentifier {
            public String pszObjId;
            public CryptoApiBlob Parameters;
        }

        struct CryptKeyProviderInformation
        {
            public String pwszContainerName;
            public String pwszProvName;
            public Int32 dwProvType;
            public Int32 dwFlags;
            public Int32 cProvParam;
            public IntPtr rgProvParam;
            public Int32 dwKeySpec;
        }
    }
}

以下是使用它生成新 X509Certificate2 的方法:

var certificate = X509Certificate2Helper.GenerateSelfSignedCertificate();

但是,您可以看到尝试通过certificate.PrivateKey 获取私钥会抛出Keyset does not exist。我试图查阅文档,但我无法弄清楚为什么证书上下文在加载为 X509Certificate2 时没有设置其私钥。有没有人有任何想法?是否存在导致密钥无法设置的问题?我的意思是,我在这里有点困惑,因为我希望自签名证书始终携带其私钥,因为它使用它自己签名,还是不是这种情况?

【问题讨论】:

    标签: c# .net winapi pki x509certificate2


    【解决方案1】:

    问题在于CryptKeyProviderInformation 结构签名。它缺少 CharSet(带有 Auto 或 Unicode)属性,因为容器和提供者名称应该是 unicode(在编组之后)。更新结构定义如下:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    struct CryptKeyProviderInformation
    {
        public String pwszContainerName;
        public String pwszProvName;
        public Int32 dwProvType;
        public Int32 dwFlags;
        public Int32 cProvParam;
        public IntPtr rgProvParam;
        public Int32 dwKeySpec;
    }
    

    然后密钥应该是可访问的。

    【讨论】:

    • 谢谢你,我不敢相信我错过了。这对我来说是非常棘手的。只是出于好奇,如果将来发生这种情况,您是否知道找到此类编组问题的最佳方法是什么?
    • 没有本地资源是不可能的。但是,参数名称和/或文档中可能有指示。由于 CryptoAPI 在其名称中使用匈牙利符号,它确实提供了一些线索。例如,LPSTR 类型是 ANSI 字符串,而 LPWSTR(注意 'W' 字符)表示 Unicode。参数名称包含以下信息:“psz”或“pwsz”。第一个是ANSI,第二个是Unicode。并且文档(在这个例子中)说它需要 Unicode 字符串。 HTH
    • 有时,结构可能有冲突的字符串类型。一个字段是 ANSI,另一个是 Unicode。您可以通过单独分配字段来解决此问题:[MarshalAs(UnmanagedType.LPStr] 用于 ANSI,[MarshalAs(UnmanagedType.LPWStr)] 用于 Unicode 字符串,
    • 我的意思是问,除了检查是否存在返回值之外,还有其他方法可以检测封送处理无法封送回值的情况吗?
    • 实际上并没有失败,问题是关于输入数据和编组规则。如果该函数不严格检查输入数据,则没有通用的方法来确定问题来源。
    猜你喜欢
    • 2019-06-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多