我试图通过使用google chrome(最新版本)的SSL流建立到web服务器的HTTPS连接,我使用CreateSelfSignedCertificate(string commonName)
生成我的证书,当我调用https://localhost/
时,它总是引发System.Security.Authentication.AuthenticationException : A call to SSPI failed, see inner exception -> Win32Exception: An unknown error occurred while processing the certificate
我正在使用一个临时密钥,我不想存储证书。
这是我的代码:
...
ServerCertificate = CreateSelfSignedCertificate("localhost");
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
SslStream s = new SslStream(_stream, false, ValidateServerCertificate);
s.AuthenticateAsServer(ServerCertificate, false, SslProtocols.Tls12, false);
_stream = s;
...
...
public bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
public static X509Certificate2 CreateSelfSignedCertificate(string commonName)
{
X500DistinguishedName subjectName = new X500DistinguishedName($"CN={commonName}");
using (RSA rsa = RSA.Create(2048))
{
CertificateRequest certificateRequest = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
certificateRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
X509Certificate2 certificate = certificateRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(1));
byte[] pfxData = certificate.Export(X509ContentType.Pkcs12);
return new X509Certificate2(pfxData, "", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.EphemeralKeySet);
}
}
更新:- 必须使用SAN,因为google chrome需要SAN,并且从不使用CNN回退。
ANSWER
使用临时密钥进行身份验证在Windows上是不可能的,因为提供TLS/SSL的底层操作系统组件不能使用临时密钥。
查看github问题在这里
也:
byte[] pfxData = certificate.Export(X509ContentType.Pkcs12, (string)null);
return new X509Certificate2(pfxData, (string)null, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.EphemeralKeySet);
与return certificate
基本相同。
人们做pkcs12导出和重新导入的原因是而不是指定EphemeralKeySet(你也不应该断言PersistKeySet,除非你真的想这么做)。而且你可能也不需要Exportable。
byte[] pfxData = certificate.Export(X509ContentType.Pkcs12, (string)null);
return new X509Certificate2(pfxData);
会更好。
我遇到的问题最好用github issue上的一个由于年老而被锁定的问题来描述。
感谢微软的Barton Jeremy Barton在这方面的帮助,并指出了一些关于这个主题的亮点。
解决方案解决这个问题:
切换到使用Azure密钥库管理证书的基于linux的Azure应用服务。Azure密钥库可以安全地存储证书和私钥,并自动处理续订。
或
您必须使用X509KeyStorageFlags.MachineKeySet
将证书持久化到特定的CSP
这是我到目前为止的代码,这基本上存储了自签名证书,一旦它已经创建,所以你的服务器能够AuthenticateAsServer()
而不会抛出Win32异常。
按需创建自签名证书的函数(根据需要自由调整):
public void CreateSelfSignedCertificate()
{
string commonName = "My Authority CA";
using (RSA rsa = RSA.Create(2048))
{
// Create a subject name
X500DistinguishedName subjectName = new X500DistinguishedName($"CN={commonName}");
// Create a self-signed certificate
CertificateRequest certificateRequest = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
// Add a "Key Usage" extension to the certificate during its creation.
// The "Key Usage" extension defines the purposes for which the public key of the certificate can be used.
// X509KeyUsageFlags.DataEncipherment: The public key can be used to encrypt data, typically by encrypting a session key that is then used to encrypt the actual data.
// X509KeyUsageFlags.KeyEncipherment: The public key can be used to encrypt other keys, for example, in the TLS protocol during key exchange.
// X509KeyUsageFlags.DigitalSignature: The public key can be used to verify digital signatures.
// The second parameter of X509KeyUsageExtension specifies whether the extension is critical or not.
// If it is critical (true), applications that do not understand this extension must reject the certificate.
// If it is non-critical (false), applications that do not understand this extension can still accept the certificate.
certificateRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
// oid 1.3.6.1.5.5.7.3.1 = "Server Authentication"
// oid 1.3.6.1.5.5.7.3.2 = "Client Authentication"
// oid 1.3.6.1.5.5.7.3.3 = "Code Signing"
// ...
certificateRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));
// Add SAN extension (fallback)
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddDnsName("localhost");
sanBuilder.AddIpAddress(IPAddress.Parse("127.0.0.1"));
// Add all Machine IPv4 ou IPv6 configuration to SAN extension
foreach (var ipAddress in Dns.GetHostAddresses(Dns.GetHostName()))
if (ipAddress.AddressFamily == AddressFamily.InterNetwork || ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
sanBuilder.AddIpAddress(ipAddress);
certificateRequest.CertificateExtensions.Add(sanBuilder.Build());
// Set the certificate date and duration
X509Certificate2 certificate = certificateRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(1));
// Export the certificat
pfxData = certificate.Export(X509ContentType.Pkcs12);
}
}
代码将证书存储到特定的CSP,只有在它不存在的情况下才进行身份验证(这里我们将其存储到LocalMachine受信任的证书中,因为Google共享这些证书并可以访问它)
...
ServerCertificate = new X509Certificate2(pfxData, (string)null, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
bool certificateExists = false;
foreach (X509Certificate2 existingCert in store.Certificates)
{
if (existingCert.Subject == ServerCertificate.Subject && existingCert.HasPrivateKey == ServerCertificate.HasPrivateKey && existingCert.GetCertHashString() == ServerCertificate.GetCertHashString())
{
certificateExists = true;
break;
}
}
if (!certificateExists)
store.Add(new X509Certificate2(_certificateContent));
store.Close();
...
我希望它对你有帮助
感谢@Charlieface的帮助