- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
有没有办法使用 C#/.NET Standard 以编程方式将证书和 key (均作为 Base64 字符串从服务中单独接收)组合到 .pfx 文件中?使用工具不是一种选择,因为我需要自动化它。
上下文:我需要将证书和私钥(没有页眉和页脚的单独 Base64 字符串)加载到 X509Certificate2
对象,以将其从 .NET Standard 1.6 库传递到应用程序.问题是,.NET Standard 中的 X509Certificate2
类中没有 PrivateKey
属性!因此,我将私钥实际加载到 X509Certificate2
对象中的唯一方法是将它与证书本身组合在一个 .pfx 文件中,然后像在构造函数中那样加载它。
最佳答案
对于框架类型,没有办法做到这一点。这可能与 BouncyCaSTLe 或其他库有关。
.NET Core 2.0 添加了通过扩展方法将证书和 key 对象合并在一起(合并到新的 X509Certificate2 对象中)的功能:
X509Certificate2 mergedCert = cert.CopyWithPrivateKey(rsaPrivateKey);
X509Certificate2 mergedCert = cert.CopyWithPrivateKey(dsaPrivateKey);
X509Certificate2 mergedCert = cert.CopyWithPrivateKey(ecdsaPrivateKey);
但这需要专门针对 netcoreapp20(而非 netstandard20)进行编译。
框架类型也没有办法从二进制表示中加载关键对象(CngKey.Import
除外,但它只适用于 Windows),只能从预解析的结构(RSAParameters
、DSAParameters
、ECParameters
)。
在 Linux 上实现此目标的最简单方法(如果 BouncyCaSTLe 无法帮助您)是使用 System.Process
生成类似于 openssl pkcs12 -export -out 的调用tmp.pfx -in tmp.cer -inkey tmp.key -password pass:""
.
在 Windows 上,您可以使用 CngKey.Import 和 P/Invoke CertSetCertificateContextProperty (对于 CERT_NCRYPT_KEY_HANDLE_PROP_ID (78))然后在变异证书上调用 cert.Export
。
更新(2020-09-30):对于 .NET 3.0,这相当简单(不回答“使用 .NET Standard”的原始问题,因为它需要针对 netcoreapp3 进行编译。 0 或更高版本),并且还可以轻松地使用 .NET 5.0 添加对 PEM 编码 key 的支持。
此代码使用 .NET 5.0 PemEncoding
类检查 key 文件是否经过 PEM 编码(而不是二进制/DER 编码),然后使用支持的格式加载私钥,匹配私钥到证书,然后导出。 .NET Core 3.0 中新增了关键的导入方法。
private enum KeyFileKinds
{
None = 0,
Pkcs8,
EncryptedPkcs8,
RsaPrivateKey,
Any = -1,
}
public static byte[] MakePfx(string certPath, string keyPath, string exportPassword)
{
using X509Certificate2 cert = new X509Certificate2(certPath);
byte[] keyBytes;
KeyFileKinds kinds;
ReadOnlySpan<char> keyFileText = File.ReadAllText(keyPath).AsSpan();
// PemEncoding.TryFind requires net5.0+
if (PemEncoding.TryFind(keyFileText, out PemFields pemFields))
{
keyBytes = new byte[pemFields.DecodedDataLength];
if (!Convert.TryFromBase64Chars(keyFileText[pemFields.Base64Data], keyBytes, out int written) ||
written != keyBytes.Length)
{
Debug.Fail("PemEncoding.TryFind and Convert.TryFromBase64Chars disagree on Base64 encoding");
throw new InvalidOperationException();
}
ReadOnlySpan<char> label = keyFileText[pemFields.Label];
if (label.SequenceEqual("PRIVATE KEY"))
{
kinds = KeyFileKinds.Pkcs8;
}
else if (label.SequenceEqual("ENCRYPTED PRIVATE KEY"))
{
kinds = KeyFileKinds.EncryptedPkcs8;
}
else if (label.SequenceEqual("RSA PRIVATE KEY"))
{
kinds = KeyFileKinds.RsaPrivateKey;
}
else
{
throw new NotSupportedException($"The PEM file type '{label.ToString()}' is not supported.");
}
}
else
{
kinds = KeyFileKinds.Any;
keyBytes = File.ReadAllBytes(keyPath);
}
RSA rsa = null;
ECDsa ecdsa = null;
DSA dsa = null;
switch (cert.GetKeyAlgorithm())
{
case "1.2.840.113549.1.1.1":
rsa = RSA.Create();
break;
case "1.2.840.10045.2.1":
ecdsa = ECDsa.Create();
break;
case "1.2.840.10040.4.1":
dsa = DSA.Create();
break;
default:
throw new NotSupportedException($"The certificate key algorithm '{cert.GetKeyAlgorithm()}' is unknown");
}
AsymmetricAlgorithm anyAlg = rsa ?? ecdsa ?? (AsymmetricAlgorithm)dsa;
bool loaded = false;
int bytesRead;
using (rsa)
using (ecdsa)
using (dsa)
{
if (!loaded && rsa != null && kinds.HasFlag(KeyFileKinds.RsaPrivateKey))
{
try
{
rsa.ImportRSAPrivateKey(keyBytes, out bytesRead);
loaded = bytesRead == keyBytes.Length;
}
catch (CryptographicException)
{
}
}
if (!loaded && kinds.HasFlag(KeyFileKinds.Pkcs8))
{
try
{
anyAlg.ImportPkcs8PrivateKey(keyBytes, out bytesRead);
loaded = bytesRead == keyBytes.Length;
}
catch (CryptographicException)
{
}
}
if (!loaded && kinds.HasFlag(KeyFileKinds.EncryptedPkcs8))
{
try
{
// This assumes that the private key was already exported
// with the same password that the PFX will be exported with.
// Not true? Add a parameter :).
anyAlg.ImportEncryptedPkcs8PrivateKey(exportPassword, keyBytes, out bytesRead);
loaded = bytesRead == keyBytes.Length;
}
catch (CryptographicException)
{
}
}
if (!loaded)
{
throw new InvalidOperationException("Could not load the key as any known format.");
}
X509Certificate2 withKey;
if (rsa != null)
{
withKey = cert.CopyWithPrivateKey(rsa);
}
else if (ecdsa != null)
{
withKey = cert.CopyWithPrivateKey(ecdsa);
}
else
{
Debug.Assert(dsa != null);
withKey = cert.CopyWithPrivateKey(dsa);
}
using (withKey)
{
return withKey.Export(X509ContentType.Pfx, exportPassword);
}
}
}
更新(2020-10-09):之前的更新显示了 .NET Core 3.1 中更好的代码,但也显示了 .NET 5 中的一些前瞻性代码。如果证书文件位于PEM 格式(-----BEGIN CERTIFICIATE-----
)并且 key 文件是 PEM 格式(BEGIN PRIVATE KEY/BEGIN RSA PRIVATE KEY/BEGIN EC PRIVATE KEY/BEGIN ENCRYPTED PRIVATE KEY) 那么 .NET 5 有一个更简单的方法:
using (X509Certificate2 certWithKey = X509Certificate2.CreateFromPemFile(certPath, keyPath))
{
return certWithKey.Export(X509ContentType.Pfx, exportPassword);
}
也可用作 CreateFromPem(loadedCertPem, loadedKeyPem)
、CreateFromEncryptedPem(loadedCertPem、loadedKeyPem、keyPassword)
和 CreateFromEncryptedPemFile(certPath, keyPath, keyPassword)
.
关于c# - .NET Standard - 以编程方式将证书和私钥合并到 .pfx 文件中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44465574/
我是一名优秀的程序员,十分优秀!