一些 Windows 2008 和 Windows 2008 R2 "原始" 版本与 CertEnroll.dll 上的接口实现方式发生了变化。我猜这也同样适用于某些 Windows 7 版本。为了让它(部分)正常运行,你需要使用
Activator.CreateInstance(Type.GetTypeFromProgID(<TypeName>)
实例化类;这将导致系统查找 HKLM:\SOFTWARE\Classes\Interface\ 中的引用以获取正确的类。
工作示例:
(此代码的一部分使用自
https://dev59.com/XWYr5IYBdhLWcg3wRoSW#13806300)。
using System;
using System.Collections.Generic;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using CERTENROLLLib;
public static X509Certificate2 CreateSelfSignedCertificate(string friendlyName, string password)
{
var dnHostName = new CX500DistinguishedName();
dnHostName.Encode(GetMachineDn());
var dnSubjectName = dnHostName;
var typeName = "X509Enrollment.CX509PrivateKey";
var type = Type.GetTypeFromProgID(typeName);
if (type == null)
{
throw new Exception(typeName + " is not available on your system: 0x80040154 (REGDB_E_CLASSNOTREG)");
}
var privateKey = Activator.CreateInstance(type) as IX509PrivateKey;
if (privateKey == null)
{
throw new Exception("Your certlib does not know an implementation of " + typeName +
" (in HKLM:\\SOFTWARE\\Classes\\Interface\\)!");
}
privateKey.ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider";
privateKey.ProviderType = X509ProviderType.XCN_PROV_RSA_AES;
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.MachineContext = true;
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_NONE;
privateKey.Create();
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone, "SHA512");
var oid = new CObjectId();
oid.InitializeFromValue("1.3.6.1.5.5.7.3.1");
var oidlist = new CObjectIds { oid };
var eku = new CX509ExtensionEnhancedKeyUsage();
eku.InitializeEncode(oidlist);
var objExtensionAlternativeNames = new CX509ExtensionAlternativeNames();
{
var altNames = new CAlternativeNames();
var dnsHostname = new CAlternativeName();
dnsHostname.InitializeFromString(AlternativeNameType.XCN_CERT_ALT_NAME_DNS_NAME, Environment.MachineName);
altNames.Add(dnsHostname);
foreach (var ipAddress in Dns.GetHostAddresses(Dns.GetHostName()))
{
if ((ipAddress.AddressFamily == AddressFamily.InterNetwork ||
ipAddress.AddressFamily == AddressFamily.InterNetworkV6) && !IPAddress.IsLoopback(ipAddress))
{
var dns = new CAlternativeName();
dns.InitializeFromString(AlternativeNameType.XCN_CERT_ALT_NAME_DNS_NAME, ipAddress.ToString());
altNames.Add(dns);
}
}
objExtensionAlternativeNames.InitializeEncode(altNames);
}
typeName = "X509Enrollment.CX509CertificateRequestCertificate";
type = Type.GetTypeFromProgID(typeName);
if (type == null)
{
throw new Exception(typeName + " is not available on your system: 0x80040154 (REGDB_E_CLASSNOTREG)");
}
var cert = Activator.CreateInstance(type) as IX509CertificateRequestCertificate;
if (cert == null)
{
throw new Exception("Your certlib does not know an implementation of " + typeName +
" (in HKLM:\\SOFTWARE\\Classes\\Interface\\)!");
}
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
cert.Subject = dnSubjectName;
cert.Issuer = dnHostName;
cert.NotBefore = DateTime.Now.AddDays(-1);
cert.NotAfter = DateTime.Now.AddYears(1);
cert.X509Extensions.Add((CX509Extension)eku);
cert.X509Extensions.Add((CX509Extension)objExtensionAlternativeNames);
cert.HashAlgorithm = hashobj;
cert.Encode();
typeName = "X509Enrollment.CX509Enrollment";
type = Type.GetTypeFromProgID(typeName);
if (type == null)
{
throw new Exception(typeName + " is not available on your system: 0x80040154 (REGDB_E_CLASSNOTREG)");
}
var enroll = Activator.CreateInstance(type) as IX509Enrollment;
if (enroll == null)
{
throw new Exception("Your certlib does not know an implementation of " + typeName +
" (in HKLM:\\SOFTWARE\\Classes\\Interface\\)!");
}
enroll.InitializeFromRequest(cert);
enroll.CertificateFriendlyName = friendlyName;
var csr = enroll.CreateRequest();
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate, csr,
EncodingType.XCN_CRYPT_STRING_BASE64, password);
var certFs = LoadCertFromStore(cert.SerialNumber);
if (!certFs.HasPrivateKey) throw new InvalidOperationException("Created certificate has no private key!");
return certFs;
}
private static string GetDomainDn()
{
var fqdnDomain = IPGlobalProperties.GetIPGlobalProperties().DomainName;
if (string.IsNullOrWhiteSpace(fqdnDomain)) return null;
var context = new DirectoryContext(DirectoryContextType.Domain, fqdnDomain);
var d = Domain.GetDomain(context);
var de = d.GetDirectoryEntry();
return de.Properties["DistinguishedName"].Value.ToString();
}
private static string GetMachineDn()
{
var machine = "CN=" + Environment.MachineName;
var dom = GetDomainDn();
return machine + (string.IsNullOrWhiteSpace(dom) ? "" : ", " + dom);
}
private static X509Certificate2 LoadCertFromStore(string serialNumber)
{
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.MaxAllowed);
try
{
var serialBytes = Convert.FromBase64String(serialNumber);
var serial = BitConverter.ToString(serialBytes.ToArray()).Replace("-", "");
var serialReversed = BitConverter.ToString(serialBytes.Reverse().ToArray()).Replace("-", "");
var serverCerts = store.Certificates.Find(X509FindType.FindBySerialNumber, serial, false);
if (serverCerts.Count == 0)
{
serverCerts = store.Certificates.Find(X509FindType.FindBySerialNumber, serialReversed, false);
}
if (serverCerts.Count == 0)
{
throw new KeyNotFoundException("No certificate with serial number <" + serial + "> or reversed serial <" + serialReversed + "> found!");
}
if (serverCerts.Count > 1)
{
throw new Exception("Found multiple certificates with serial <" + serial + "> or reversed serial <" + serialReversed + ">!");
}
return serverCerts[0];
}
finally
{
store.Close();
}
}
备注
为什么我说是“半途而废”呢?这是因为 certenroll.dll V. 6 存在问题,在 cert.InitializeFromPrivateKey 上构建会失败。在 certenroll.dll V 6.0 中,第二个参数必须是“CX509PrivateKey”类型,而在安装了Certenroll.dll V 10的Win10机器上,则是 IX509PrivateKey 类型:
error CS1503: Argument 2: cannot convert from 'CERTENROLLLib.IX509PrivateKey' to 'CERTENROLLLib.CX509PrivateKey'
你可能会想,那么简单啊,只要在 Activator.CreateInstance 中将 privateKey 强制转换为 CX509PrivateKey 就好了。但问题是,它会编译通过,但在普通 Win2k8 中不会给你类 (CX509...),而是接口 (IX509...),因此转换失败并返回 null。
我们通过在安装了 certenroll.dll V 10 的机器上编译 certenrollment 函数来解决这个问题。它能够在 Win2k8 上编译成功并正常工作。尽管如此,将其置于一个单独的项目中有点麻烦,因为如果使用 certenroll.dll V 6,就会在我们的构建服务器上构建失败。
regsvr32 c:\Windows\System32\CertEnroll.dll
并查看是否有任何差异吗?这可能是由于注册表损坏引起的,否则请尝试在32位下运行并查看是否会出现相同的错误。 - Ron Beyer