c# - windows server 导出和导入证书的问题
问题描述
我在 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)。CryptographyAPI Next Generation (CNG) 是 CryptoAPI(CAPI) 的长期替代品。NG 被设计为可在多个级别上进行扩展,并且在行为上与密码学无关。
CNG 导出有 2 种类型,“Export”和 “PlainTextExport”。
大多数情况下,默认导出策略是“Export”,为了让我们获得 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
返回所述证书的字节数组 - 以字节数组为参数导出证书
- 将枚举设置
X509KeyStorageFlags
为Exportable
- 导入导出的证书
- 设置导出策略“PlainTextExport”
- 调用
GetRSAPrivatekey()
和ExportParameters()
- 关闭商店
程序.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);
}
}
}
输出:
参考:
https://docs.microsoft.com/en-us/windows/win32/seccng/cng-portal
X509Certificate2.Import 与 NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG
注意:要让编译器接受不安全代码,请导航至 Project > Properties > Build > “Allow UnsafeCode”
致谢:感谢Bartonjs对此主题的许多详细回答
推荐阅读
- javascript - ExtJS 4 - 如何使用菜单项上传文件
- apache-spark - 如何从数据框中有效地删除单值列
- php - 为什么不在 nojejs xml-c14n 中使用规范化器 XML
- bluetooth - 蓝牙 HC-05 停止接收数据
- google-apps-script - 如何将已发送电子邮件添加到状态列并在脚本再次运行时排除这些行?
- git - Git push 会将我的更改推送到哪个分支?
- regex - 在小数点 10,4 的整数部分将逗号输入正则表达式
- python - Python字符串递归,字符串索引超出范围
- java - Spring:如何在组件中创建一些非异步方法
- python-3.x - Python/Selenium - 确认弹出接受