HttpClient - The message received was unexpected or badly formatted - c#

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.

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());
}

Keyset does not exist although the PrivateKey is set

I'm trying to sign a message using CmsSigner and attaching X509 certificate:
public static byte[] Sign(X509Certificate2 certificate, string keyXml)
{
ContentInfo contentInfo = new ContentInfo(new Oid("1.2.840.113549.1.7.1"), Encoding.ASCII.GetBytes("hello"));
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(keyXml);
using (certificate.GetRSAPrivateKey())
{
certificate.PrivateKey = rsa;
}
var signer = new CmsSigner(certificate);
signer.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1");
signer.SignedAttributes.Add(new Pkcs9SigningTime());
var signedCms = new SignedCms(contentInfo, false);
signedCms.ComputeSignature(signer);
var encodeByteBlock = signedCms.Encode();
return encodeByteBlock;
}
the certificate doesn't have a key (HasPrivateKey is false) so I set it with the right key generated using OpenSSL:
openssl req -new -sha256 -x509 -days 7300 -out ca.crt -keyout ca.key.pem -nodes
I convert the ca.key.pem to XML.
But when ComputeSignature is called, this exception is thrown:
System.Security.Cryptography.CryptographicException: 'Keyset does not
exist'
Stack trace:
at
System.Security.Cryptography.Pkcs.PkcsUtils.CreateSignerEncodeInfo(CmsSigner
signer, Boolean silent, SafeCryptProvHandle& hProv) at
System.Security.Cryptography.Pkcs.SignedCms.Sign(CmsSigner signer,
Boolean silent) at
System.Security.Cryptography.Pkcs.SignedCms.ComputeSignature(CmsSigner
signer, Boolean silent) at
System.Security.Cryptography.Pkcs.SignedCms.ComputeSignature(CmsSigner
signer) at ConsoleTest472.Program.Sign(X509Certificate2
certificate, String keyXml) in
D:\me\Projects\ConsoleTest472\ConsoleTest472\Program.cs:line 56 at
ConsoleTest472.Program.Main(String[] args) in
D:\me\Projects\ConsoleTest472\ConsoleTest472\Program.cs:line 63
What is wrong with the code I'm using? doesn't the keyset is set when the private key is..set?
What is wrong with the code I'm using?
using (certificate.GetRSAPrivateKey())
{
certificate.PrivateKey = rsa;
}
doesn't make much sense. You're acquiring the private key, then ignoring it, attempting to replace it, then disposing it.
A better version would be
var signedCms = new SignedCms(contentInfo, false);
using (X509Certificate2 certWithKey = certificate.CopyWithPrivateKey(rsa))
{
var signer = new CmsSigner(certWithKey);
signer.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1");
signer.SignedAttributes.Add(new Pkcs9SigningTime());
signedCms.ComputeSignature(signer);
}
var encodeByteBlock = signedCms.Encode();
return encodeByteBlock;
Which does the work of binding the private key to a copy of the certificate (rather than do mutation), and does whatever is necessary to make that work (in this case, it'll end up replacing the RSACryptoServiceProvider key with an RSACng key, because the platform only supports CNG for memory-only (ephemeral) keys).
The private key must be either:
in the MY Key Store of the user executing the code
in the MY Key Store of the Machine, with permission granted to the user
stored together with the certificate in a PFX/PKCS#12 file and loaded as X509Certificate2 with the password protecting it.

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().

Equalivent for openssl s_client -connect <hostname>:<port> in C#

When I execute a openssl command to connect a particular server [myadda.tie.fire.glass.... dummy server name] , it gives me some output.
openssl s_client -connect myadda.tie.fire.glass:443
It gives me some output which contains information like
server certificate
issuer information
And another command which required the certificate from above command to provide me details info about the certificate.
openssl x509 -in <Certificate_FileName.crt> -text -nout
It gives me output as information about the certificate
issued for server
Validity
I want similar kind of output using some C# classes. I am not sure how to solve this query. Can anyone help me out?
Well below code help me to retrieve the required information.
X509Certificate2 cert = null;
var client = new TcpClient(host, 443);
var certValidation = new RemoteCertificateValidationCallback(delegate (object snd, X509Certificate certificate, X509Chain chainLocal, SslPolicyErrors sslPolicyErrors)
{
//Accept every certificate, even if it's invalid
return true;
});
// Create an SSL stream and takeover client's stream
using (var sslStream = new SslStream(client.GetStream(), true, certValidation))
{
sslStream.AuthenticateAsClient(host);
var serverCertificate = sslStream.RemoteCertificate;
cert = new X509Certificate2(serverCertificate);
//Convert Raw Data to Base64String
var certBytes = cert.Export(X509ContentType.Cert);
var certAsString = Convert.ToBase64String(certBytes, Base64FormattingOptions.None);
}
Here vertAsString gives me the certificate whereas cert gives me the other required information.

How to import a signed SSL certificate using Bouncy Castle in C# (Mono/Xamarin)?

I used Bouncy Castle to generate a private key, as well as a PKCS10 CSR, which I then send to a remote server to be signed. I get back a standard base64 encoded signed SSL certificate in response as a string. The question is, how do I import the signed certificate from a string, and then save both the private key and signed certificate as a PKCS12 (.PFX) file?
Additionally, how do I bundle in a CA certificate to be included within the PFX file?
// Generate the private/public keypair
RsaKeyPairGenerator kpgen = new RsaKeyPairGenerator ();
CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator ();
kpgen.Init (new KeyGenerationParameters (new SecureRandom (randomGenerator), 2048));
AsymmetricCipherKeyPair keyPair = kpgen.GenerateKeyPair ();
// Generate the CSR
X509Name subjectName = new X509Name ("CN=domain.com/name=Name");
Pkcs10CertificationRequest kpGen = new Pkcs10CertificationRequest ("SHA256withRSA", subjectName, keyPair.Public, null, keyPair.Private);
string certCsr = Convert.ToBase64String (kpGen.GetDerEncoded ());
// ** certCsr is now sent to be signed **
// ** let's assume that we get "certSigned" in response, and also have the CA **
string certSigned = "[standard signed certificate goes here]";
string certCA = "[standard CA certificate goes here]";
// Now how do I import certSigned and certCA
// Finally how do I export everything as a PFX file?
Bouncy Castle is a very powerful library, however the lack of documentation makes it quite difficult to work with. After searching for much too long through all of the classes and methods I finally found what I was looking for. The following code will take the previously generated private key, bundle it together with the signed certificate and the CA, and then save it as a .PFX file:
// Import the signed certificate
X509Certificate signedX509Cert = new X509CertificateParser ().ReadCertificate (Encoding.UTF8.GetBytes (certSigned));
X509CertificateEntry certEntry = new X509CertificateEntry (signedX509Cert);
// Import the CA certificate
X509Certificate signedX509CaCert = new X509CertificateParser ().ReadCertificate (Encoding.UTF8.GetBytes (certCA ));
X509CertificateEntry certCaEntry = new X509CertificateEntry (signedX509CaCert);
// Prepare the pkcs12 certificate store
Pkcs12Store store = new Pkcs12StoreBuilder ().Build ();
// Bundle together the private key, signed certificate and CA
store.SetKeyEntry (signedX509Cert.SubjectDN.ToString () + "_key", new AsymmetricKeyEntry (keyPair.Private), new X509CertificateEntry[] {
certEntry,
certCaEntry
});
// Finally save the bundle as a PFX file
using (var filestream = new FileStream (#"CertBundle.pfx", FileMode.Create, FileAccess.ReadWrite)) {
store.Save (filestream, "password".ToCharArray (), new SecureRandom ());
}
Feedback and improvements are welcome!

Categories

Resources