Websocket fails to connect - c#

I have created websocket server and binded the self signed certificate with it , when websocket client fails to connect .
Eventviewer logs has this error.
"The TLS server credential's certificate does not have a private key information property attached to it. This most often occurs when a certificate is backed up incorrectly and then later restored. This message can also indicate a certificate enrollment failure."
Please find below code for creating self sign certificate.
var dn = new X500DistinguishedName("CN=" + Dns.GetHostName(), X500DistinguishedNameFlags.None);
var rsa = RSA.Create();
var req = new CertificateRequest(dn, rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
// key usage: Digital Signature and Key Encipherment
req.CertificateExtensions.Add(new X509KeyUsageExtension(System.Security.Cryptography.X509Certificates.X509KeyUsageFlags.KeyEncipherment, true));
// Enhanced key usages
req.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection {
new Oid("1.3.6.1.5.5.7.3.1") // TLS Server auth
}, false));
req.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(req.PublicKey, false));
cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(1));
cert.FriendlyName = "Test";
string CertPath = Path.Combine(certPath, Guid.NewGuid() + ".pfx");
File.WriteAllBytes(CertPath, cert.Export(X509ContentType.Pfx, ""));

Related

HttpClient with X509Certificate2 and ECDsa failing (server response "No required SSL certificate was sent")

Update
It turned out, that we have a problem with the SslStream (on which the HttpClient is based on). In the current version (7.xx) the certificate chain is not submitted to the server, when sent via the client. This is a known issue and discussed here and here.
I will leave this post online, since the code below might be helpful to others (it does not cause problems if you want to use the client certificate only in your requests).
I have spent a lot of time trying to find our what's wrong with the Client Certificate authentication using ECDsa based certificates with the native HttpClient of .Net Core (version 7.0.100 but also tried v.6xxx) but never got this thing running. (Btw. I used the same approach for RSA based Client Certificates without any problems).
Due to security reasons, I MUST use ECDsa client certificate + chain.
I can not understand or find information why this is not working / supported and the results are confusing to me.
When loading the certificate and the key and using them to sign and verify some data, all tests pass (see code).
In the end, the required Client Certificates are not sent to the server, resulting in an SSL exception (tested the same certificates with a Python script to verify that they are correct, there I had zero problems).
I can not imagine (at least I hope not) that these kind of Client Certificates are not supported. I would greatly appreciate any help or hints for alternative workarounds. It would be quite terrible to switch to some different language at this point :/
Reference Code
Contains a test certificate and key to play around with. The chain is missing in this example and can simply be attached to the certificate string
Part of the tests are taken from: Use X509Certificate2 to sign and validate ECDSA-SHA256 signatures
[Test]
// Just some test to better understand whether the certificate and key is working and belong together
public void TestEcdsaFunctionality()
{
var ecdsaCertificate = #"-----BEGIN CERTIFICATE-----
MIIBgzCCASoCCQD+iUUbrH+BOzAKBggqhkjOPQQDAjBKMQswCQYDVQQGEwJQRDEL
MAkGA1UECAwCQlcxEDAOBgNVBAcMB1BhbmRvcmExDzANBgNVBAoMBkFueU9yZzEL
MAkGA1UEAwwCNDIwHhcNMjIxMTIxMTE0MjExWhcNMjMxMTIxMTE0MjExWjBKMQsw
CQYDVQQGEwJQRDELMAkGA1UECAwCQlcxEDAOBgNVBAcMB1BhbmRvcmExDzANBgNV
BAoMBkFueU9yZzELMAkGA1UEAwwCNDIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AAT6vBU2iIcESep8UeQhfNFgfTArFYvtb2Pmlbk1+R9gdNaWEg1UK7dlt3/mH/X3
Mrg80JaTY3OPM92MY9e9gs7ZMAoGCCqGSM49BAMCA0cAMEQCIA3p2mMOYqGEzReY
br7nYLsLdF0+dV6iZSZaG1iMHwblAiA5UaJlVr5CsCkG+j1ZJEICSOnVMyx4DjA5
oZuoMYa42w==
-----END CERTIFICATE-----";
var ecdsaPrivateKey = #"MDECAQEEIM6BExC2G7P1KpViQmZ/Z65nukv8yQmvw6PqGGQcKn9boAoGCCqGSM49
AwEH";
var cert = X509Certificate2.CreateFromPem(ecdsaCertificate.ToCharArray());
var key = ECDsa.Create("ECDsa");
var keybytes = Convert.FromBase64String(ecdsaPrivateKey);
key.ImportECPrivateKey(keybytes, out _);
var helloBytes = Encoding.UTF8.GetBytes("Hello World");
// Sign data with the ECDsa key
var signed = key.SignData(helloBytes, 0, helloBytes.Count(), HashAlgorithmName.SHA256);
// Verify the data signature with the certificates public key
var verified = cert.GetECDsaPublicKey().VerifyData(helloBytes, signed, HashAlgorithmName.SHA256);
// Assume that everything went well and the data signature is valid
Assert.That(verified, Is.EqualTo(true));
// Additional tests with the X509Certificate2 object type
X509Certificate2 keyCert = ECDsaCertificateExtensions.CopyWithPrivateKey(cert, key);
// Sing using the certificate that contains the private key
using (ECDsa ecdsa = keyCert.GetECDsaPrivateKey())
{
if (ecdsa == null)
throw new ArgumentException("Cert must have an ECDSA private key", nameof(cert));
signed = ecdsa.SignData(helloBytes, HashAlgorithmName.SHA256);
}
// Verify signed data using the certificate that contains the private key
using (ECDsa ecdsa = keyCert.GetECDsaPublicKey())
{
if (ecdsa == null)
throw new ArgumentException("Cert must be an ECDSA cert", nameof(cert));
Assert.That(ecdsa.VerifyData(helloBytes, signed, HashAlgorithmName.SHA256), Is.EqualTo(true));
}
WorkshopRegistration(keyCert);
}
// This would be what I really want to use the client certificate for
private void WorkshopRegistration(X509Certificate2 clientCert)
{
var payload = "{somepayload}";
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = SslProtocols.Tls12;
handler.ClientCertificates.Add(clientCert);
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) =>
{
return true;
};
var content = new StringContent(payload, System.Text.Encoding.UTF8, "application/json");
var client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Accept", "application/json");
var result = client.PutAsync("https://someHostname.com/registration",
content).GetAwaiter().GetResult();
if (result.StatusCode != HttpStatusCode.OK)
throw new SuccessException("Registration failed with conent: " + result.Content.ToString());
}

HttpClient - The message received was unexpected or badly formatted

I am trying to connect to an API that requires two way SSL configured. I have the the certificate and the private key which I configured in postman as shown in the below image.
This works fine in Postman, but SSL fails when I try to implement it in C# using HttpClient. The error that I get is "The message received was unexpected or badly formatted.". I believe it has something to do with incorrect configuration.
I have referred this StackOverflow post to implement my code: Associate a private key with the X509Certificate2 class in .net
Below is what I have tried:
byte[] publicCertificateBytes = File.ReadAllBytes("<Public Certificate>");
var publicCertificate = new X509Certificate2(publicCertificateBytes);
byte[] privateKey = Convert.FromBase64String(File.ReadAllText("<private key file>").Replace("-----BEGIN PRIVATE KEY-----", "").Replace("-----END PRIVATE KEY-----", ""));
using (var rsa = RSA.Create())
{
rsa.ImportPkcs8PrivateKey(privateKey, out _);
publicCertificate = publicCertificate.CopyWithPrivateKey(rsa);
publicCertificate = new X509Certificate2(publicCertificate.Export(X509ContentType.Pkcs12));
}
var httpService = new GenericUtilityManager().ResolveUtility<IHttpService>();
var handler = new HttpClientHandler();
handler.SslProtocols = SslProtocols.Tls12;
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(publicCertificate);
handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;
httpService.InitializeHttpClientHandler(handler);
Please help.
Try to load the certicate using the CreateFromPem static function, this method returns a new certificate with the private key.
var certificateCrt = File.ReadAllText("<Public Certificate>");
var privateKey = File.ReadAllText("<private key file>");
using var certificate = X509Certificate2.CreateFromPem(certificateCrt, privateKey);
If that doesn't work, try to creating a .pfx certificate using OpenSSL:
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt
I had this problem when I needed to use a certificate on a Windows OS, to work I had to create a .pfx certificate, but on other OS like Mac and Linux it worked fine without a .pfx certificate.

Is it possible to generate on the fly SSL certificates from a root CA in C#?

I'm setting up a server under my own root CA to generate SSL certificates on the fly under .NET Core.
I'm able to generate self-signed certificates using the CertificateRequest class. However, these certs obviously aren't trusted by clients with my own root CA. I'm using the CertificateRequest.CreateSelfSigned() method to do this. I cannot use my root CA to sign these new certs, however. Using the CertificateRequest.Create() method will generate my new cert, but it will not provide a private key.
public static X509Certificate2 CreateSelfSignedCertificate(string domain)
{
SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddDnsName(domain);
X500DistinguishedName distinguishedName = new X500DistinguishedName($"CN=On-The-Fly Generated Cert");
using (RSA rsa = RSA.Create(2048))
{
var request = new CertificateRequest(distinguishedName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));
request.CertificateExtensions.Add(sanBuilder.Build());
var ca = new X509Certificate2(File.ReadAllBytes(#"E:\testing_ca_certificate.pfx"), "password"); //Open my root CA cert, generated in OpenSSL
//Generates a cert, but does not provide a private key.
var certificate = request.Create(ca, new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(365)), new byte[] { 0, 1, 2, 3 });
//Generates a usable cert, but is not under my root CA
//var certificate = request.CreateSelfSigned(new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(365)));
return new X509Certificate2(certificate.Export(X509ContentType.Pfx, "password"), "password", X509KeyStorageFlags.DefaultKeySet);
}
}
Using the CertificateRequest.Create() method, I get a valid cert without a private key. I should have this private key so I can encrypt SSL traffic.
Using the CertificateRequest.Create() method, I get a valid cert without a private key.
Real certificate authorities should not have the private key of the CA and the private key matching the certificate to be created in the same place at the same time. Aside from the fact that there is currently no way to decode a Certification Request (CSR), this method assumes it's being used with a public-only key provided to the CertificateRequest constructor.
Following the flow, the "expected" model is:
Client determines the need for a new certificate
Client generates public/private keypair
Client sends public key and other necessary information to CA (PKCS#10 CertificationRequest, or other means)
CA validates request
CA builds up the CertificateRequest object using the client public key
CA produces a certfificate with CertificateRequest.Create()
CA sends the certificate back to the client (cert.RawData)
Client instantiates X509Certificate2 instance (only has public key)
Client uses the CopyWithPrivateKey (extension) method to associate the private key to a new certificate object.
Client does whatever client wants now.
So, after
//Generates a cert, but does not provide a private key.
var certificate = request.Create(ca, new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(365)), new byte[] { 0, 1, 2, 3 });
you should add
certificate = certificate.CopyWithPrivateKey(rsa);
(Though, really, you should have your certificate objects in using statements so they get disposed when no longer needed, in which case you'd need a second variable to hold the cert-with-key)
Given the certificate request was created using your rsa instance, you should be able to export the private key from it. RSA.ToXmlString() allows for exporting the key to a format that later that can be later imported using RSA.FromXmlString().

How to use client certificate for HTTPs requests in UWP app

I'm writing an app that needs to make some HTTPs requests that use a client certificate. However, I can't find any documents on how to install the certificate and then load it for use. I know that you can use the certificate by making a HttpBaseProtocolFilter and adding a certificate but how do you load the certificate for use here? And if you have a .pfx file with your client certificate, how do you install it with your package?
Thanks in advance!
For what it's worth, I ended up figuring this out using a mix of the Portable.BouncyCastle NuGet package and some UWP APIs. Some sample (pseudo-ish) code for what I did is below:
// Asymmetric key pair
RsaKeyPairGenerator keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(
new KeyGenerationParameters(
new SecureRandom(new CryptoApiRandomGenerator()), 2048));
AsymmetricCipherKeyPair keyPair = keyPairGenerator.GenerateKeyPair();
// Create certificate
X509V3CertificateGenerator generator = new X509V3CertificateGenerator();
generator.SetSubjectDN("foo");
generator.SetIssuerDN("foo");
generator.SetSerialNumber(new BigInteger("12345").Abs());
generator.SetNotBefore(DateTime.UtcNow);
generator.SetNotAfter(DateTime.UtcNow + TimeSpan.FromYears(1));
generator.SetPublicKey(keyPair.Public);
BouncyCastleX509Certificate certificate =
generator.Generate(
new Asn1SignatureFactory("SHA1WithRSA", keyPair.Private));
// Create PKCS12 certificate bytes.
Pkcs12Store store = new Pkcs12Store();
X509CertificateEntry certificateEntry = new X509CertificateEntry(certificate);
string friendlyName = "Friendly Name";
string password = "password";
store.SetCertificateEntry(friendlyName, certificateEntry);
store.SetKeyEntry(
friendlyName,
new AsymmetricKeyEntry(keyPair.Private),
new X509CertificateEntry[] { certificateEntry });
string pfxData;
using (MemoryStream memoryStream = new MemoryStream(512))
{
store.Save(memoryStream, password.ToCharArray(), this.SecureRandom);
pfxData = CryptographicBuffer.EncodeToBase64String(memoryStream.ToArray().AsBuffer());
}
// Add the certificate to the cert store
await CertificateEnrollmentManager.ImportPfxDataAsync(
pfxData,
password,
ExportOption.NotExportable,
KeyProtectionLevel.NoConsent,
InstallOptions.DeleteExpired,
friendlyName);
// Read the UWP cert from the cert store
Certificate uwpCertificate =
(await CertificateStores.FindAllAsync(
new CertificateQuery { FriendlyName = friendlyName }))[0];
// Create the UWP HTTP client.
HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
filter.ClientCertificate = uwpCertificate;
HttpClient httpClient = new HttpClient(filter);
// Profit!

Signing X509 Certs w/BouncyCastle - invalid digital signature [duplicate]

This question already has an answer here:
Closed 11 years ago.
Possible Duplicate:
Generated signed X.509 client certificate is invalid (no certificate chain to its CA)
I followed the example at:
http://www.bouncycastle.org/wiki/display/JA1/X.509+Public+Key+Certificate+and+Certification+Request+Generation
But the resulting signed client certificate has the following error when opened in windows:
"This file is invalid for use as the following: Security Certificate"
If I install it anyway and view it with certmgr, the certification path looks OK - I see my self-signed Certificate Authority (which is fine, no problems there) but the client cert has the following status:
"This certificate has an invalid digital signature."
If I call X509Certificate.Verify() it throws the following exception:
"Public key presented not for certificate signature"
Yet I'm using the same exact public key extracted from the Pkcs10CertificationRequest and when I called Verify() on that it's fine.
Any ideas? After days of struggling through this, I've got all the pieces working except this last one - and what's really confusing is that my self-signed CA cert is fine. There's just something going on with the client cert. Here's the entire block of code:
TextReader textReader = new StreamReader("certificaterequest.pkcs10");
PemReader pemReader = new PemReader(textReader);
Pkcs10CertificationRequest certificationRequest = (Pkcs10CertificationRequest)pemReader.ReadObject();
CertificationRequestInfo certificationRequestInfo = certificationRequest.GetCertificationRequestInfo();
SubjectPublicKeyInfo publicKeyInfo = certificationRequestInfo.SubjectPublicKeyInfo;
RsaPublicKeyStructure publicKeyStructure = RsaPublicKeyStructure.GetInstance(publicKeyInfo.GetPublicKey());
RsaKeyParameters publicKey = new RsaKeyParameters(false, publicKeyStructure.Modulus, publicKeyStructure.PublicExponent);
bool certIsOK = certificationRequest.Verify(publicKey);
// public key is OK here...
// get the server certificate
Org.BouncyCastle.X509.X509Certificate serverCertificate = DotNetUtilities.FromX509Certificate(System.Security.Cryptography.X509Certificates.X509Certificate.CreateFromCertFile("servermastercertificate.cer"));
// get the server private key
byte[] privateKeyBytes = File.ReadAllBytes("serverprivate.key");
AsymmetricKeyParameter serverPrivateKey = PrivateKeyFactory.CreateKey(privateKeyBytes);
// generate the client certificate
X509V3CertificateGenerator generator = new X509V3CertificateGenerator();
generator.SetSerialNumber(BigInteger.ProbablePrime(120, new Random()));
generator.SetIssuerDN(serverCertificate.SubjectDN);
generator.SetNotBefore(DateTime.Now);
generator.SetNotAfter(DateTime.Now.AddYears(5));
generator.SetSubjectDN(certificationRequestInfo.Subject);
generator.SetPublicKey(publicKey);
generator.SetSignatureAlgorithm("SHA512withRSA");
generator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(serverCertificate));
generator.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(publicKey));
var newClientCert = generator.Generate(serverPrivateKey);
newClientCert.Verify(publicKey); // <-- this blows up
return DotNetUtilities.ToX509Certificate(newClientCert).Export(X509ContentType.Pkcs12, "user password");
I figured this out. If you call X509Certificate.Verify(publicKey) you have to pass the CA's public key, not the client's public key from the Pkcs10CertificationRequest.

Categories

Resources