X509Certificate2.使用 NCRYPT_ALLOW_PLAINTEXT

我有一个 PFX 证书,里面有 CNG 密钥(KSP 提供商信息在 PFX 中指定)。我找不到在 .NET 中导入证书的方法,以允许以纯文本(MS-CAPI 格式)导出私钥。

var cert = new X509Certificate2(pfxBytes,password,X509KeyStorageFlags.Exportable);

然后我使用此句柄通过调用带有启用标志以允许 CNG 密钥的CryptAcquireCertificatePrivateKey函数来获取私钥上下文。调用成功。

当我调用NCryptExportKey时,调用失败并出现 0x8009000b 错误:

密钥在指定状态下无效。

为了调试这个问题,我调用了NCryptGetProperty函数来获取导出策略,实际上,NCRYPT_ALLOW_EXPORT_FLAG标志已启用,但NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG没有启用。尝试调用NCryptSetProperty函数以在导出策略属性中启用此标志,但调用失败并出现相同的 0x8009000b 错误。

问题:如何从文件导入 .NET 中的 pfx 文件而不保留NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAGCNG 密钥的密钥和启用标志?

ps 由于某些原因,我仅限于 .NET 4.0/4.5。


慕码人8056858
浏览 82回答 0
0回答

慕田峪4524236

我发现的最好的流程:打开 PFX 可导出(设置可导出位,但不设置明文可导出位)导出加密的 PKCS#8导入加密的 PKCS#8 并覆盖,无最终确定改变出口政策完成(提交覆盖)现在,如果您向证书询问其密钥,它是可以纯文本导出的。在 net45 中,这需要大量代码(值得庆幸的是,使用 oldschool .NET 导出 CNG RSA 证书的私钥(PKCS#8)为我做了很多工作)。netcoreapp30 会做得更好,只是 import+alter+finalize 仍然需要手动 P/Invoke。使用 ECDsa 进行测试,因为这是强制不使用 CNG->CAPI 桥的最简单方法:internal static partial class Program{    internal static void Main(string[] args)    {        X509Certificate2 cert = ImportExportable(ECDsaP256_DigitalSignature_Pfx_Windows, "Test", machineScope: false);        try        {            bool gotKey = NativeMethods.Crypt32.CryptAcquireCertificatePrivateKey(                cert.Handle,                NativeMethods.Crypt32.AcquireCertificateKeyOptions.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG,                IntPtr.Zero,                out SafeNCryptKeyHandle keyHandle,                out int keySpec,                out bool callerFree);            using (CngKey cngKey = CngKey.Open(keyHandle, 0))            {                Console.WriteLine(cngKey.ExportPolicy);                Console.WriteLine(                    Convert.ToBase64String(                        cngKey.Export(CngKeyBlobFormat.Pkcs8PrivateBlob)));            }        }        finally        {            cert.Reset();        }    }    private static X509Certificate2 ImportExportable(byte[] pfxBytes, string password, bool machineScope)    {        X509KeyStorageFlags flags = X509KeyStorageFlags.Exportable;        if (machineScope)        {            flags |= X509KeyStorageFlags.MachineKeySet;        }        else        {            flags |= X509KeyStorageFlags.UserKeySet;        }        X509Certificate2 cert = new X509Certificate2(pfxBytes, password, flags);        try        {            bool gotKey = NativeMethods.Crypt32.CryptAcquireCertificatePrivateKey(                cert.Handle,                NativeMethods.Crypt32.AcquireCertificateKeyOptions.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG,                IntPtr.Zero,                out SafeNCryptKeyHandle keyHandle,                out int keySpec,                out bool callerFree);            if (!gotKey)            {                keyHandle.Dispose();                throw new InvalidOperationException("No private key");            }            if (!callerFree)            {                keyHandle.SetHandleAsInvalid();                keyHandle.Dispose();                throw new InvalidOperationException("Key is not persisted");            }            using (keyHandle)            {                // -1 == CNG, otherwise CAPI                if (keySpec == -1)                {                    using (CngKey cngKey = CngKey.Open(keyHandle, CngKeyHandleOpenOptions.None))                    {                        // If the CNG->CAPI bridge opened the key then AllowPlaintextExport is already set.                        if ((cngKey.ExportPolicy & CngExportPolicies.AllowPlaintextExport) == 0)                        {                            FixExportability(cngKey, machineScope);                        }                    }                }            }        }        catch        {            cert.Reset();            throw;        }        return cert;    }    internal static void FixExportability(CngKey cngKey, bool machineScope)    {        string password = nameof(NativeMethods.Crypt32.AcquireCertificateKeyOptions);        byte[] encryptedPkcs8 = ExportEncryptedPkcs8(cngKey, password, 1);        string keyName = cngKey.KeyName;        using (SafeNCryptProviderHandle provHandle = cngKey.ProviderHandle)        {            ImportEncryptedPkcs8Overwrite(                encryptedPkcs8,                keyName,                provHandle,                machineScope,                password);        }    }    internal const string NCRYPT_PKCS8_PRIVATE_KEY_BLOB = "PKCS8_PRIVATEKEY";    private static readonly byte[] s_pkcs12TripleDesOidBytes =        System.Text.Encoding.ASCII.GetBytes("1.2.840.113549.1.12.1.3\0");    private static unsafe byte[] ExportEncryptedPkcs8(        CngKey cngKey,        string password,        int kdfCount)    {        var pbeParams = new NativeMethods.NCrypt.PbeParams();        NativeMethods.NCrypt.PbeParams* pbeParamsPtr = &pbeParams;        byte[] salt = new byte[NativeMethods.NCrypt.PbeParams.RgbSaltSize];        using (RandomNumberGenerator rng = RandomNumberGenerator.Create())        {            rng.GetBytes(salt);        }        pbeParams.Params.cbSalt = salt.Length;        Marshal.Copy(salt, 0, (IntPtr)pbeParams.rgbSalt, salt.Length);        pbeParams.Params.iIterations = kdfCount;        fixed (char* stringPtr = password)        fixed (byte* oidPtr = s_pkcs12TripleDesOidBytes)        {            NativeMethods.NCrypt.NCryptBuffer* buffers =                stackalloc NativeMethods.NCrypt.NCryptBuffer[3];            buffers[0] = new NativeMethods.NCrypt.NCryptBuffer            {                BufferType = NativeMethods.NCrypt.BufferType.PkcsSecret,                cbBuffer = checked(2 * (password.Length + 1)),                pvBuffer = (IntPtr)stringPtr,            };            if (buffers[0].pvBuffer == IntPtr.Zero)            {                buffers[0].cbBuffer = 0;            }            buffers[1] = new NativeMethods.NCrypt.NCryptBuffer            {                BufferType = NativeMethods.NCrypt.BufferType.PkcsAlgOid,                cbBuffer = s_pkcs12TripleDesOidBytes.Length,                pvBuffer = (IntPtr)oidPtr,            };            buffers[2] = new NativeMethods.NCrypt.NCryptBuffer            {                BufferType = NativeMethods.NCrypt.BufferType.PkcsAlgParam,                cbBuffer = sizeof(NativeMethods.NCrypt.PbeParams),                pvBuffer = (IntPtr)pbeParamsPtr,            };            var desc = new NativeMethods.NCrypt.NCryptBufferDesc            {                cBuffers = 3,                pBuffers = (IntPtr)buffers,                ulVersion = 0,            };            using (var keyHandle = cngKey.Handle)            {                int result = NativeMethods.NCrypt.NCryptExportKey(                    keyHandle,                    IntPtr.Zero,                    NCRYPT_PKCS8_PRIVATE_KEY_BLOB,                    ref desc,                    null,                    0,                    out int bytesNeeded,                    0);                if (result != 0)                {                    throw new Win32Exception(result);                }                byte[] exported = new byte[bytesNeeded];                result = NativeMethods.NCrypt.NCryptExportKey(                    keyHandle,                    IntPtr.Zero,                    NCRYPT_PKCS8_PRIVATE_KEY_BLOB,                    ref desc,                    exported,                    exported.Length,                    out bytesNeeded,                    0);                if (result != 0)                {                    throw new Win32Exception(result);                }                if (bytesNeeded != exported.Length)                {                    Array.Resize(ref exported, bytesNeeded);                }                return exported;            }        }    }    private static unsafe void ImportEncryptedPkcs8Overwrite(        byte[] encryptedPkcs8,        string keyName,        SafeNCryptProviderHandle provHandle,        bool machineScope,        string password)    {        SafeNCryptKeyHandle keyHandle;        fixed (char* passwordPtr = password)        fixed (char* keyNamePtr = keyName)        fixed (byte* blobPtr = encryptedPkcs8)        {            NativeMethods.NCrypt.NCryptBuffer* buffers = stackalloc NativeMethods.NCrypt.NCryptBuffer[2];            buffers[0] = new NativeMethods.NCrypt.NCryptBuffer            {                BufferType = NativeMethods.NCrypt.BufferType.PkcsSecret,                cbBuffer = checked(2 * (password.Length + 1)),                pvBuffer = new IntPtr(passwordPtr),            };            if (buffers[0].pvBuffer == IntPtr.Zero)            {                buffers[0].cbBuffer = 0;            }            buffers[1] = new NativeMethods.NCrypt.NCryptBuffer            {                BufferType = NativeMethods.NCrypt.BufferType.PkcsName,                cbBuffer = checked(2 * (keyName.Length + 1)),                pvBuffer = new IntPtr(keyNamePtr),            };            NativeMethods.NCrypt.NCryptBufferDesc desc = new NativeMethods.NCrypt.NCryptBufferDesc            {                cBuffers = 2,                pBuffers = (IntPtr)buffers,                ulVersion = 0,            };            NativeMethods.NCrypt.NCryptImportFlags flags =                NativeMethods.NCrypt.NCryptImportFlags.NCRYPT_OVERWRITE_KEY_FLAG |                NativeMethods.NCrypt.NCryptImportFlags.NCRYPT_DO_NOT_FINALIZE_FLAG;            if (machineScope)            {                flags |= NativeMethods.NCrypt.NCryptImportFlags.NCRYPT_MACHINE_KEY_FLAG;            }            int errorCode = NativeMethods.NCrypt.NCryptImportKey(                provHandle,                IntPtr.Zero,                NCRYPT_PKCS8_PRIVATE_KEY_BLOB,                ref desc,                out keyHandle,                new IntPtr(blobPtr),                encryptedPkcs8.Length,                flags);            if (errorCode != 0)            {                keyHandle.Dispose();                throw new Win32Exception(errorCode);            }            using (keyHandle)            using (CngKey cngKey = CngKey.Open(keyHandle, CngKeyHandleOpenOptions.None))            {                const CngExportPolicies desiredPolicies =                    CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport;                cngKey.SetProperty(                    new CngProperty(                        "Export Policy",                        BitConverter.GetBytes((int)desiredPolicies),                        CngPropertyOptions.Persist));                int error = NativeMethods.NCrypt.NCryptFinalizeKey(keyHandle, 0);                if (error != 0)                {                    throw new Win32Exception(error);                }            }        }    }}internal static class NativeMethods{    internal static class Crypt32    {        internal enum AcquireCertificateKeyOptions        {            None = 0x00000000,            CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG = 0x00040000,        }        [DllImport("crypt32.dll", SetLastError = true)]        internal static extern bool CryptAcquireCertificatePrivateKey(            IntPtr pCert,            AcquireCertificateKeyOptions dwFlags,            IntPtr pvReserved,            out SafeNCryptKeyHandle phCryptProvOrNCryptKey,            out int dwKeySpec,            out bool pfCallerFreeProvOrNCryptKey);    }    internal static class NCrypt    {        [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]        internal static extern int NCryptExportKey(            SafeNCryptKeyHandle hKey,            IntPtr hExportKey,            string pszBlobType,            ref NCryptBufferDesc pParameterList,            byte[] pbOutput,            int cbOutput,            [Out] out int pcbResult,            int dwFlags);        [StructLayout(LayoutKind.Sequential)]        internal unsafe struct PbeParams        {            internal const int RgbSaltSize = 8;            internal CryptPkcs12PbeParams Params;            internal fixed byte rgbSalt[RgbSaltSize];        }        [StructLayout(LayoutKind.Sequential)]        internal struct CryptPkcs12PbeParams        {            internal int iIterations;            internal int cbSalt;        }        [StructLayout(LayoutKind.Sequential)]        internal struct NCryptBufferDesc        {            public int ulVersion;            public int cBuffers;            public IntPtr pBuffers;        }        [StructLayout(LayoutKind.Sequential)]        internal struct NCryptBuffer        {            public int cbBuffer;            public BufferType BufferType;            public IntPtr pvBuffer;        }        internal enum BufferType        {            PkcsAlgOid = 41,            PkcsAlgParam = 42,            PkcsName = 45,            PkcsSecret = 46,        }        [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]        internal static extern int NCryptOpenStorageProvider(            out SafeNCryptProviderHandle phProvider,            string pszProviderName,            int dwFlags);        internal enum NCryptImportFlags        {            None = 0,            NCRYPT_MACHINE_KEY_FLAG = 0x00000020,            NCRYPT_OVERWRITE_KEY_FLAG = 0x00000080,            NCRYPT_DO_NOT_FINALIZE_FLAG = 0x00000400,        }        [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]        internal static extern int NCryptImportKey(            SafeNCryptProviderHandle hProvider,            IntPtr hImportKey,            string pszBlobType,            ref NCryptBufferDesc pParameterList,            out SafeNCryptKeyHandle phKey,            IntPtr pbData,            int cbData,            NCryptImportFlags dwFlags);        [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]        internal static extern int NCryptFinalizeKey(SafeNCryptKeyHandle hKey, int dwFlags);    }}
打开App,查看更多内容
随时随地看视频慕课网APP