I'm trying to established an HTTPS connexion to a web server through SSL stream using google chrome (last version), I'm generating my certificate using CreateSelfSignedCertificate(string commonName)
and when I call https://localhost/
it allways raises a System.Security.Authentication.AuthenticationException : A call to SSPI failed, see inner exception -> Win32Exception: An unknown error occurred while processing the certificate
I'm using an Ephemeral Key and I don't want to store the certificate.
here's my code:
...
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);
}
}
UPDATE:
see github issue here
Also:
byte[] pfxData = certificate.Export(X509ContentType.Pkcs12, (string)null);
return new X509Certificate2(pfxData, (string)null, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.EphemeralKeySet);
is basically the same as just return certificate
.
The reason that people do the pkcs12 export and re-import is to NOT specify EphemeralKeySet (you should also not assert PersistKeySet, unless you really mean to). And you likely don’t need Exportable, either.
byte[] pfxData = certificate.Export(X509ContentType.Pkcs12, (string)null);
return new X509Certificate2(pfxData);
would work much better.
The issue that I was having was best described by an issue that has gotten locked due to old age on the github Issue linked here.
Thanks to Barton Jeremy Barton at Microsoft for helping me on this and pointing some light on this subject.
Either switch to a Linux-based Azure App Service using Azure Key Vault to manage your certificates. Azure Key Vault can securely store certificates and private keys and automatically handle renewals.
X509KeyStorageFlags.MachineKeySet
here is my code so far, this basically stores the self signed certificate once it has been created so your server is able to AuthenticateAsServer()
without throwing a Win32 Exception.
Function to create the selfsigned certificate on demand (free to tweak it as needed):
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);
}
}
code to store the certificate to the specific CSP before authenticating only if it doesn't exist yet (here we store it to the LocalMachine trusted certificate as Google shares those and have access to it)
...
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();
...
I hope it helped you
Thanks to @Charlieface for is help