Windows Server 2022中自签名证书出现ERR_SSL_KEY_USAGE_INCOMPATIBLE错误
问题:自签名证书在Windows Server 2022上出现ERR_SSL_KEY_USAGE_INCOMPATIBLE错误
我通过以下C#代码生成自签名证书:
public static X509Certificate2 CreateSelfSignedCertificate(string friendlyName, string password) { // create DN for subject and issuer var dnHostName = new CX500DistinguishedName(); // DN will be in format CN=machinename, DC=domain, DC=local for machinename.domain.local dnHostName.Encode(GetMachineDn()); var dnSubjectName = dnHostName; var dn = new CX500DistinguishedName(); dn.Encode("CN=" + friendlyName, X500NameFlags.XCN_CERT_NAME_STR_NONE); //var privateKey = new CX509PrivateKey(); 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; // key-bitness privateKey.Length = 2048; privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE; privateKey.MachineContext = true; // Don't allow export of private key privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_NONE; // use is not limited privateKey.Create(); // Use the stronger SHA512 hashing algorithm var hashobj = new CObjectId(); hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID, ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY, AlgorithmFlags.AlgorithmFlagsNone, "SHA512"); // add extended key usage if you want - look at MSDN for a list of possible OIDs var oid = new CObjectId(); oid.InitializeFromValue("1.3.6.1.5.5.7.3.1"); // SSL server var oidlist = new CObjectIds { oid }; var eku = new CX509ExtensionEnhancedKeyUsage(); eku.InitializeEncode(oidlist); // add all IPs of current machine as dns-names (SAN), so a user connecting to our wcf // service by IP still claim-trusts this server certificate 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); } // Create the self signing request //var cert = new CX509CertificateRequestCertificate(); 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 = dn; cert.Issuer = dn; // the issuer and the subject are the same cert.NotBefore = DateTime.Now.AddDays(-1); // this cert expires immediately. Change to whatever makes sense for you cert.NotAfter = DateTime.Today.AddYears(200); cert.X509Extensions.Add((CX509Extension)eku); // add the EKU cert.X509Extensions.Add((CX509Extension)objExtensionAlternativeNames); cert.HashAlgorithm = hashobj; // Specify the hashing algorithm cert.Encode(); // encode the certificate // Do the final enrollment process //var enroll = new CX509Enrollment(); 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\)!"); } // Use private key to initialize the certrequest... enroll.InitializeFromRequest(cert); enroll.CertificateFriendlyName = friendlyName; // Optional: add a friendly name var csr = enroll.CreateRequest(); // Output the request in base64 and install it back as the response enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate, csr, EncodingType.XCN_CRYPT_STRING_BASE64, password); // This will fail on Win2k8, some strange "Parameter is empty" error... Thus we search the // certificate by serial number with the managed X509Store-class // // output a base64 encoded PKCS#12 so we can import it back to the .Net security classes //var base64Encoded = enroll.CreatePFX(password, PFXExportOptions.PFXExportChainNoRoot, EncodingType.XCN_CRYPT_STRING_BASE64); //return new X509Certificate2(Convert.FromBase64String(base64Encoded), password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); //CertificateExists(friendlyName); var certFs = LoadCertFromStore(friendlyName); if (!certFs.HasPrivateKey) throw new InvalidOperationException("Created certificate has no private key!"); return certFs; }
该代码此前运行正常,但在Windows Server 2022服务器上,使用Chromium内核浏览器(Edge、Opera、Chrome)访问时出现ERR_SSL_KEY_USAGE_INCOMPATIBLE错误,Firefox浏览器则无此问题。
若通过PowerShell命令手动生成自签名证书:
New-SelfSignedCertificate -Subject "CN=WebServer 444" -CertStoreLocation "cert:\LocalMachine\My"
再通过cmd命令绑定SSL证书:
netsh http add sslcert ipport=0.0.0.0:444 appid={"blabla-bla-bla-bla-blabla"} certhash=<THUMBPRINT>
此时证书可正常工作。
环境详情:.NET Framework 4.6.2、Visual Studio 2022
问题原因及解决方法
原因分析
- 核心问题在于证书的**密钥用法(Key Usage)**字段缺失。你的C#代码仅配置了增强型密钥用法(EKU)(标识为SSL服务器的
1.3.6.1.5.5.7.3.1),但没有显式设置密钥用法。 - Windows Server 2022上的Chromium内核浏览器对证书合规性校验更严格,要求服务器证书必须包含
数字签名(Digital Signature)和密钥交换(Key Encipherment)这两个密钥用法标识,否则会触发ERR_SSL_KEY_USAGE_INCOMPATIBLE错误。 - PowerShell的
New-SelfSignedCertificate命令默认会自动添加符合服务器证书要求的密钥用法,因此手动生成的证书可以正常工作。
解决步骤
在C#代码中添加密钥用法扩展,具体操作如下:
- 创建
CX509ExtensionKeyUsage实例并设置所需的密钥用法标志:
// 添加密钥用法扩展 var keyUsage = new CX509ExtensionKeyUsage(); // 启用数字签名和密钥交换,满足SSL服务器证书要求 keyUsage.InitializeEncode(X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE | X509KeyUsageFlags.XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE);
- 将该扩展添加到证书请求的扩展集合中,插入到添加EKU扩展的代码之后:
cert.X509Extensions.Add((CX509Extension)eku); // 原有EKU扩展 cert.X509Extensions.Add((CX509Extension)keyUsage); // 新增密钥用法扩展 cert.X509Extensions.Add((CX509Extension)objExtensionAlternativeNames); // 原有SAN扩展
修改后的代码生成的证书会包含合规的密钥用法,即可在Chromium内核浏览器中正常使用。
内容的提问来源于stack exchange,提问作者Sahin
相关产品推荐
相关产品推荐

