C# 在windows server中导出和导入证书时出现问题

C# 在windows server中导出和导入证书时出现问题,c#,certificate,C#,Certificate,我在C#的证书编程中遇到了一个问题,详细信息如下。 1-我需要一个导出证书(私钥和公钥)并将此证书导入其他服务器。所以我将其导出并导入到新服务器中(导出和导入在MMC中完成)。 2-我执行以下代码以确保导出和导入的正确性 var signedCer = CertificateHelper.GetCertificateFromStoreBySubject("MySubject", StoreName.My, StoreLocation.LocalMachine); if (signedCer !=

我在C#的证书编程中遇到了一个问题,详细信息如下。 1-我需要一个导出证书(私钥和公钥)并将此证书导入其他服务器。所以我将其导出并导入到新服务器中(导出和导入在MMC中完成)。 2-我执行以下代码以确保导出和导入的正确性

var signedCer = CertificateHelper.GetCertificateFromStoreBySubject("MySubject", StoreName.My, StoreLocation.LocalMachine);
if (signedCer != null)
{
   var rsapk = signedCer.GetRSAPrivateKey();
   var rsaParameter=rsapk.ExportParameters(true);
   var z = rsapk.ToXmlString(true);
}
上述代码在旧服务器中正常工作, 但当我在新服务器中执行时,会发生以下错误

The requested operation is not supported.

   at System.Security.Cryptography.NCryptNative.ExportKey(SafeNCryptKeyHandle key, String format)
   at System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat format)
   at System.Security.Cryptography.RSACng.ExportParameters(Boolean includePrivateParameters)
   at System.Security.Cryptography.RSA.ToXmlString(Boolean includePrivateParameters)
谁能帮我找到解决这个问题的办法

谢谢。

本期的背景

  • 我们无法提取pfx的RSA参数的原因 证书使用
    certificate.GetRSAPrivateKey.ExportParameters(true)
    是因为 证书在内部使用CNG而不是CryptoAPI(CAPI)

  • 下一代密码术(CNG)是长期的替代品 用于加密API(CAPI)。NG设计为可在多个位置扩展 级别和密码在行为上是不可知的

  • CNG出口有两种类型,“出口”和 “明文导出”

  • 大多数情况下,默认的出口政策是“出口”,以便我们 获取还必须设置策略的RSA参数 “AllowPlaintextExport”

  • 为了设置“AllowPlainTextExport”,我们必须访问非托管 C语言的C++库,


目标细分

 static void Main(string[] args)
 {
      var certName = "CN=localhost";
      var certBytes = GetByteArrayCertificateFromStore(certName, StoreName.My, StoreLocation.CurrentUser);
      X509Certificate2 cert = ImportExportable(certBytes, "password", machineScope: false);
      var rsaPrivateParams = cert.GetRSAPrivateKey().ExportParameters(true);
      store.Close();

  }
  • 从证书存储中找到要导出其rsa参数的现有pfx
  • 调用
    GetByteArrayCertificateFromStore
    ,返回所述证书的byteArray
  • 以字节数组作为参数导出证书
  • x509keystrageflags的枚举设置为
    Exportable
  • 导入导出的证书
  • 设置导出策略“明文导出”
  • 调用
    GetRSAPrivatekey()
    ExportParameters()
  • 关店

Program.cs

using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;



namespace certificate
{
 public class Program
 {
    public static X509Store store; public static SafeNCryptKeyHandle keyHandler;


    static void Main(string[] args)
    {
        var certName = "CN=localhost";
        var certBytes = GetByteArrayCertificateFromStore(certName, StoreName.My, StoreLocation.CurrentUser);
        X509Certificate2 cert = ImportExportable(certBytes, "password", machineScope: false);
        var rsaPrivateParams = cert.GetRSAPrivateKey().ExportParameters(true);
        store.Close();

    }


    public static byte [] GetByteArrayCertificateFromStore(string certName, StoreName sname, StoreLocation sLoc)
    {
        store = new X509Store(sname, sLoc);
        try
        {
            store.Open(OpenFlags.MaxAllowed);

            X509Certificate2Collection certCollection = store.Certificates;
            X509Certificate2Collection currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
            X509Certificate2Collection signingCert = currentCerts.Find(X509FindType.FindBySubjectDistinguishedName, certName, true);
            if (signingCert.Count == 0)
                throw new InvalidOperationException();
            return  signingCert.Export(X509ContentType.Pfx, "password");

        }
        finally
        {
            //store.close();
        }
    }

    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);
                        }
                    }
                }
            }
            keyHandler = keyHandle;
        }
        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);
    }
}
}

输出

参考文献:

注意:要使编译器接受不安全的代码,请导航到Project> 属性>生成>“允许不安全代码”

信贷:感谢巴顿JS对这一问题的详细回答 话题


哪一行出错?谢谢Clint,这一行:var rsapameter=rsapk.ExportParameters(true);问题是,当您导入PFX且密钥进入CNG时,它将变为“可导出”而不是“可明文导出”,因此系统将不允许导出它。有一个一次性的变更解决方案,已经得到了回答。(提议的副本即将出现)这是否回答了你的问题@user2352554您好,我只是想知道答案是否能够帮助您找到解决方案。我能够提取私钥;我需要把旗子调准。当我设定为“真”时,它fails@user2352554您好,我只是想知道答案是否能够帮助您找到解决方案。因为我能够用这个解决方案提取私钥