Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WebRequest not sending client certificate

Tags:

rest

c#

ssl

I'm writing a client for a REST API and to authenticate to the API I must use a cert that was provided to me.

this code is as follows:

public string GetCustomer(int custId)
{
X509Certificate2 Cert = new X509Certificate2();
    Cert.Import(@"C:\users\foo\desktop\api\pubAndPrivateCert.pkcs12", "", X509KeyStorageFlags.PersistKeySet);

    ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertificate;
    HttpWebRequest req = (HttpWebRequest)WebRequest.Create("https://api.foo.net/api/customer/v1/" + custId);
    req.ClientCertificates.Add(Cert);

    req.UserAgent = "LOL API Client";
    req.Accept = "application/json";
    req.Method = WebRequestMethods.Http.Get;

    string result = null;
    using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse())
{
StreamReader reader = new StreamReader(resp.GetResponseStream());
result = reader.ReadToEnd();
}
return result;
}

Each time I make the request I get an error 400 and when using Fiddler to look at the response I get the following

<html>
<head><title>400 No required SSL certificate was sent</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<center>No required SSL certificate was sent</center>
<hr><center>nginx/0.6.32</center>
</body>
</html>

I see no reason for it not to send the cert but troubleshooting SSL isn't terribly easy. I did add some debug statements to add some detail and stopped using fiddler and he is what I got

These errors are from ValidateServerCertificate()

Certificate error: RemoteCertificateChainErrors
NotSignatureValid
The signature of the certificate cannot be verified.
1048576
Unknown error.

These are the errors from the WebExecption that is thrown.

Cought Exception ProtocolError
The remote server returned an error: (400) Bad Request.
BadRequest
<html>
<head><title>400 No required SSL certificate was sent</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<center>No required SSL certificate was sent</center>
<hr><center>nginx/0.6.32</center>
</body>
</html>

This is the ValidateServerCertificate() code..it always returns true to ignore any cert errors.

    public static bool ValidateServerCertificate(object sender, X509Certificate certificate,X509Chain chain,SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None)
            return true;

        if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors)
        {
            Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
            foreach (var chainstat in chain.ChainStatus)
            {
                Console.WriteLine("{0}", chainstat.Status);
                Console.WriteLine("{0}", chainstat.StatusInformation);
            }
            return true;
        }

        Console.WriteLine("Certificate error: {0}", sslPolicyErrors);

        // allow this client to communicate with unauthenticated servers. 
        return true;
    }
like image 245
user961346 Avatar asked May 03 '14 23:05

user961346


1 Answers

Your code loads the client certificate from a local file. You should have more success if you import the client certificate into the certificate store (which is highly recommended to protect the private key). Then your code should look more like this:

public string GetCustomer(int custId)
{
    // EDIT THIS TO MATCH YOUR CLIENT CERTIFICATE: the subject key identifier in hexadecimal.
    string subjectKeyIdentifier = "39b66c2a49b2059a15adf96e6b2a3cda9f4b0e3b";

    X509Store store = new X509Store("My", StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly);

    X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindBySubjectKeyIdentifier, subjectKeyIdentifier, true);
    X509Certificate2 certificate = certificates[0];

    HttpWebRequest req = (HttpWebRequest)WebRequest.Create("https://api.foo.net/api/customer/v1/" + custId);
    req.ClientCertificates.Add(certificate);

    req.UserAgent = "LOL API Client";
    req.Accept = "application/json";
    req.Method = WebRequestMethods.Http.Get;

    string result = null;
    using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse())
    {
        StreamReader reader = new StreamReader(resp.GetResponseStream());
        result = reader.ReadToEnd();
    }
    return result;
}

See Import a Certificate for instructions. The code assumes that you have imported the certificate with both public and private keys to the Personal certificates folder ("My") of the Current User.

You do not need to supply a ServicePointManager.ServerCertificateValidationCallback. That allows your application to change how the server certificate is validated. That does not influence how the server validates your client certificate.

like image 91
Joel Allison Avatar answered Nov 02 '22 02:11

Joel Allison