views:

1624

answers:

6

Hello, I'm trying to authenticate myself against WebService using my client certificate, but, for some reasons (I explain), I don't want to load certificate from store, rather read it from disc.

The following:

// gw is teh WebService client
X509Certificate cert = new X509Certificate(PathToCertificate);
_gw.ClientCertificates.Add(ClientCertificate());
ServicePointManager.ServerCertificateValidationCallback = (a,b,c,d) => true;
_gw.DoSomeCall();

returns always 403 - the Service doesn't authorize me. But, when I save that certificate into CertStore, it works. (As stated in MSDN.)

Is it possible to use certificate not in store?

(the reason is, that I got windows service(client) sometimes calling webservice(server), and after unspecified amount of time the service 'forgets' my certificates and doesnt authorize against server, with no apparent reason)

A: 

check this out

http://support.microsoft.com/kb/901183

Regards, Vivek.

Vivek
+5  A: 

What type of file is PathToCertificate? If it's just a .cer file, it will not contain the private key for the certificate and trying to use that certificate for SSL/TLS will fail.

However, if you have a PKCS7 or PKCS12 file that includes the public and private key for the certificate, your code will work (you might need to use the overload that takes a password if the private key has one).

To test this, I went to http://www.mono-project.com/UsingClientCertificatesWithXSP and created my client.p12 file following those instructions. I also created a simple HTTPS server using HttpListener for testing.

Then I compiled the following program into 'client.exe' and run like:

 client.exe https://<MYSSLSERVER>/ client.p12 password

where client.p12 is the PKCS12 file generated before and 'password' is the password I set for the private key of the certificate.

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

public class HttpWebRequestClientCertificateTest : ICertificatePolicy {

    public bool CheckValidationResult (ServicePoint sp, X509Certificate certificate,
            WebRequest request, int error)
    {
            return true; // server certificate's CA is not known to windows.
    }

    static void Main (string[] args)
    {
            string host = "https://localhost:1234/";
            if (args.Length > 0)
                    host = args[0];

            X509Certificate2 certificate = null;
            if (args.Length > 1) {
                    string password = null;
                    if (args.Length > 2)
                            password = args [2];
                    certificate = new X509Certificate2 (args[1], password);
            }

            ServicePointManager.CertificatePolicy = new HttpWebRequestClientCertificateTest ();

            HttpWebRequest req = (HttpWebRequest) WebRequest.Create (host);
            if (certificate != null)
                    req.ClientCertificates.Add (certificate);

            WebResponse resp = req.GetResponse ();
            Stream stream = resp.GetResponseStream ();
            StreamReader sr = new StreamReader (stream, Encoding.UTF8);
            Console.WriteLine (sr.ReadToEnd ());
    }

}

Let me know if you want me to upload the server code and the certificates used on both sides of the test.

Gonzalo
A: 

Hi.

Make sure that the account that runs the Windows Service has access to your certificate file.

Verify it by using SysInternals process monitor, filter on your file name and review the status when the services accesses the file.

Magnus Johansson
A: 

Do you need a password for the certificate? If so, there is a field for it in the constructor.

X509Certificate cert = new X509Certificate(PathToCertificate,YourPassword);
jle
A: 

You have the potential for at least two problems...

First...

Your client certificate file cannot contain a private key unless it's accessed with a password. You should be using a PKCS #12 (*.pfx) certificate with a password so that your client has access to the private key. You client code will have to provide the password when opening the certificate as others have already posted. There are several ways to create this, the easiest is to use the following command-line to first generate the certificate, then use the MMC certificate manager to export the certificates private key:

Process p = Process.Start(
    "makecert.exe",
    String.Join(" ", new string[] {
        "-r",//                     Create a self signed certificate
        "-pe",//                    Mark generated private key as exportable
        "-n", "CN=" + myHostName,// Certificate subject X500 name (eg: CN=Fred Dews)
        "-b", "01/01/2000",//       Start of the validity period; default to now.
        "-e", "01/01/2036",//       End of validity period; defaults to 2039
        "-eku",//                   Comma separated enhanced key usage OIDs
        "1.3.6.1.5.5.7.3.1," +//    Server Authentication (1.3.6.1.5.5.7.3.1)
        "1.3.6.1.5.5.7.3.2", //     Client Authentication (1.3.6.1.5.5.7.3.2)
        "-ss", "my",//              Subject's certificate store name that stores the output certificate
        "-sr", "LocalMachine",//    Subject's certificate store location.
        "-sky", "exchange",//       Subject key type <signature|exchange|<integer>>.
        "-sp",//                    Subject's CryptoAPI provider's name
        "Microsoft RSA SChannel Cryptographic Provider",
        "-sy", "12",//              Subject's CryptoAPI provider's type
        myHostName + ".cer"//       [outputCertificateFile]
    })
);

Second...

Your next problem is going to be server-side. The server has to allow this certificate. You have the right logic, but on the wrong side of the wire, move this line to the web server handling the request. If you cannot, you must then take the '.cer' file saved above to the server and add it to the server computer's trust list:

ServicePointManager.ServerCertificateValidationCallback = (a,b,c,d) => true;
csharptest.net
-1. The server does not need to know the private key of the client certificate. It only needs to trust the CA that signed the client certificate. When negotiating SSL3/TLS, the server sends a list with the DN of all the CAs that are allowed. At that point the client should see that the CA of the client cert. is in the list and send it.
Gonzalo
Correct, I never said the SERVER needs the private key. It must trust the certificate or explicitly allow it with code. As for the rest of your comment "...the server sends a list with the DN of all the CAs..." that is simply not true.
csharptest.net
Not true? See http://tools.ietf.org/html/rfc5246#page-53 , section 7.4.4: certificate_authorities A list of the distinguished names [X501] of acceptable certificate_authorities, represented in DER-encoded format. These distinguished names may specify a desired distinguished name for a root CA or for a subordinate CA; thus, this message can be used to describe known roots as well as a desired authorization space. If the certificate_authorities list is empty, then the client MAY send any certificate of the appropriate ClientCertificateType, unles
Gonzalo
Oh, and, yes. If you install the client certificate as you said and trust it in the server, it's equivalent to installing the CA that signed the cert. How can I remove the -1?
Gonzalo
Grrr. It does not let me unless you make a change in the response.
Gonzalo
No worries on the -1 thing... I'm reading the rfc now link you posted.
csharptest.net
Ah, your correct that the server CAN send a list of CAs, I was unaware of that portion of the nego... sorry ;)
csharptest.net
A: 

The potential problem could be caching of SSL sessions (Schannel cache). Only first request negotiates the SSL handshake. Subsequent requests will use the same session ID and hope that the server accept it. If the server clears the SessionId, the requests will fail with 403 error. To disable local ssl session caching (and force SSL negotiation for each request) you have to open windows registry folder:

[HKEY_LOCAL_MACHINE][System][CurrentControlSet][Control][SecurityProviders][SCHANNEL]

and add the key named ClientCacheTime (DWORD) with value 0.

This issue is covered here:

http://support.microsoft.com/?id=247658

PanJanek