1

I am working on connection with hashicorp. We need to call there decrypt api in .net. for calling decrypt API, we need to pass token in it. But token call is different which is using client certificate and key for authentication. We are calling token generation url from .net application but getting error ""{"errors":["client certificate must be supplied"]}\n"".

var allKeyytes =  File.ReadAllBytes(@"file.key");
        var privateKey = new X509Certificate2(allKeyytes, "XXXXXX").PrivateKey as DSACryptoServiceProvider;
        var certificate2 = new X509Certificate2(@"file.crt");
        certificate2.CopyWithPrivateKey(privateKey);
        HttpClientHandler handler = new HttpClientHandler();
        handler.ClientCertificates.Add(certificate2);

        using (HttpClient client = new HttpClient(handler))
        {
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, vaultUrl);
            HttpResponseMessage response = client.SendAsync(request).Result;

            if (response.IsSuccessStatusCode)
            {
                var result = response.Content.ReadAsStringAsync().Result;

            }
        }

After adding above line of code getting error "System.Security.Cryptography.CryptographicException: 'Cannot find the requested object."

Please let me know what I am doing wrong!

Thank you in advance.

  • `certificate2.CopyWithPrivateKey(provider);` you are not using the result. Also missing `using` for `provider` `certificate2` `request` and `response`. And calling `.Result` is likely to cause a deadlock, use `await` instead – Charlieface Jul 05 '22 at 12:41

3 Answers3

1

You cannot create a X509Certificate2 with just a private key. You need to read it in with DSA.ImportFromEncryptedPem.

You are not actually using the result of CopyWithPrivateKey which returns a new certificate, it does not modify the original.

You are also missing using on various objects, and you also need to use await rather than .Result otherwise you may deadlock

var allKeyBytes =  File.ReadAllText(@"file.key");
using (var crt = new X509Certificate2(@"file.crt"))
using (var dsa = DSA.Create())
{
    dsa.ImportFromEncryptedPem(allKeyBytes.AsSpan(), "XXXXXX".AsSpan());
    using (var certificate2 = crt.CopyWithPrivateKey(dsa))
    {
        HttpClientHandler handler = new HttpClientHandler();
        handler.ClientCertificates.Add(certificate2);

        using (var client = new HttpClient(handler))
        using (var request = new HttpRequestMessage(HttpMethod.Post, vaultUrl))
        {
            using (var response = await client.SendAsync(request))
            {
                if (response.IsSuccessStatusCode)
                {
                    var result = await response.Content.ReadAsStringAsync();
                }
            }
        }
    }
}

If the private key is actually RSA then you will need to cast to RSA.Create() etc instead.

Ideally the HttpClient would be cached in a static . For that you would only dispose crt and privateKey not certificate2.

Community
  • 1
  • 1
Charlieface
  • 52,284
  • 6
  • 19
  • 43
  • Thank you for replying. I tried the solution you gave and added the mising code but i am getting exception "System.Security.Cryptography.CryptographicException: 'Cannot find the requested object." on line using (var privateKey = new X509Certificate2(allKeyytes, "123456")). – Vaibhav Srivastava Jul 06 '22 at 06:25
  • Is it RSA or DSA? – Charlieface Jul 06 '22 at 08:19
  • It must we DSA and in .key file it started with "-----BEGIN PRIVATE KEY----- MXXXXXXXXEvAIBADANBgkqhkiXXXXXXX.................. and ended with == -----END PRIVATE KEY----- – Vaibhav Srivastava Jul 06 '22 at 15:08
  • See latest edit – Charlieface Jul 06 '22 at 15:12
  • Tried but getting "No supported key formats were found. Check that the input represents the contents of a PEM-encoded key file, not the path to such a file. (Parameter 'input')". – Vaibhav Srivastava Jul 06 '22 at 16:37
  • Just thinking: if it begins `-----BEGIN PRIVATE KEY-----` then I don't think it's encrypted anyway. But why don't you just use a tool such as `openssl` to combine them into a single pfx file? – Charlieface Jul 06 '22 at 17:39
  • I tried to create pfx file but it was not creating. And i am working on console application which will later install on server after changes. I don't think so the openssl will work. When i will move to prod. – Vaibhav Srivastava Jul 08 '22 at 16:25
  • Getting new Exception : "No credentials are available in the security package" – Vaibhav Srivastava Jul 13 '22 at 07:57
  • Thank you all for your valuable inputs. With the help of solution found: .https://stackoverflow.com/questions/44465574/net-standard-merge-a-certificate-and-a-private-key-into-a-pfx-file-programma – Vaibhav Srivastava Jul 18 '22 at 12:00
0

Code is not right. On line 2, you are reading privatekey in first parameter, it should have been encrypted certificate containing private key. Also, this method is obsolete.

I did the below code and it runs successfully. If you are using DSA, you can replace RSA with DSA

using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

int bytesread;
var allKeyytes = File.ReadAllBytes(@"tls.key");
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.ImportFromPem(File.ReadAllText("tls.key"));
var certificate2 = new X509Certificate2(@"tls.cer");
certificate2.CopyWithPrivateKey(provider);
HttpClientHandler handler = new HttpClientHandler();
handler.ClientCertificates.Add(certificate2);

using (HttpClient client = new HttpClient(handler))
{
    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://www.google.com");
    HttpResponseMessage response = client.SendAsync(request).Result;

    if (response.IsSuccessStatusCode)
    {
        var result = response.Content.ReadAsStringAsync().Result;

    }
}
SmartCoder
  • 856
  • 6
  • 11
0

Thank you all for helping. I was able to solve the issue.

How it Solved: 1: Created Pem file using .crt and .key file. 2: Exported the certificate using pfx type and passphrase and got byte array. 3: Created new certificate using byte array of pfx file and Add int HttpClientHandler. 4: Passed clientHandler object in HttpClient.

using (X509Certificate2 certWithKey = X509Certificate2.CreateFromPemFile(certificateionPath, key))
        {
            byte[] pfxRawData = certWithKey.Export(X509ContentType.Pfx, "123456");

            using (X509Certificate2 pfxCertWithKey = new X509Certificate2(pfxRawData, "123456"))
            {
                HttpClientHandler handler = new HttpClientHandler();
                handler.ClientCertificateOptions = ClientCertificateOption.Manual;
                handler.ServerCertificateCustomValidationCallback = (a, b, c, d) => { return true; };
                handler.ClientCertificates.Add(pfxCertWithKey);

                using (var client = new HttpClient(handler))
                using (var request = new HttpRequestMessage(HttpMethod.Post, vaultUrl))
                {
                    client.DefaultRequestHeaders.Add("X-Vault-Namespace", vaultNamespace);
                    var response = client.SendAsync(request).GetAwaiter().GetResult();
                    var result = response.Content.ReadAsStringAsync();

                }
            }
        }

Very useful link found: .NET Standard - Merge a certificate and a private key into a .pfx file programmatically This link helped me a lot!.

Thank you everyone.