Using X509 Certificate with Web Service in ASP.Net

Recently, I needed to incorporate a java web service (not developed in-house) into my client’s existing ASP.Net website. The web service calls need to be secured by utilizing X509 certificate over SSL.

Surprisingly, I found very little information on this topic (there are some, but they are scattered here and there and the information was not as complete as I hoped). And although the code to implement web service call using certificate is trivial (a few lines), debugging can be quite challenging. Here I will share some my experience. This discussion applies to calling all web services (not just limited to jws) from ASP.Net using certificates. Do not confuse what we are trying to achieve here with WSE (Web Service Enhancement), as communication with web services using X509 certificate can be done with or without WSE, it is an orthogonal concept. Here we are just using the plain web service, no WSE.

  1. Importing certificates.

The import process differs slightly for Windows XP and Windows Server 2003. For either operating system, you will need to import the certificate to the local machine store instead of personal store. To do this, run mmc (from start->run), and choose Add Snap-in (Ctrl+M), select Add to add “Certificates” to the certificate manager. When prompted for location, select Computer Account, and then Local Computer (the default is user account). We assume that the certificate is imported to one of the root certificate authorities (e.g. Third-Party Root Certificate Authorities).

ASP.Net typically runs under ASPNET_WP user under Windows XP and IIS_WPG under Windows 2003 server by default. On a Windows XP machine, the above steps are sufficient for ASP.Net to utilize the certificate imported. But for Windows 2003 server, you will need to grant the rights to the IIS_WPG user so that ASP.Net can access the certificate. This can be achieved by using Microsoft’s winhttpcertcfg.exe utility (download here) as shown below:

winhttpcertcfg.exe -g -c LOCAL_MACHINE\Root -s "{certificate name}" -a "IIS_WPG"

Note that ASP.Net worker process might be running under an account other then the ones mentioned above (i.e. customized to run under a particular user account), and in this case, you will need to grant permission to whatever process the work process is running under.

  1. Access certificate in code

To use the certificate in code, only a few lines are needed (C#):

        X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);

        store.Open(OpenFlags.ReadOnly); X509Certificate2Collection col =

        store.Certificates.Find(X509FindType.FindBySerialNumber, "{serial number no space}", true);

        //service is the webservice that need to //be authenticated using X509 certificate

        TestWebService service = new TestWebService();

//Note, we should find the certificate from the the //root certificate store on local machine if the

//certificate is imported correctly and the serial //number is correct if (col.Count == 1) { //all we need to do is to add the certificate //after that we can use the webservice as usual service.ClientCertificates.Add(col[0]);

service.Test();

}

The code above assumes that you are using .Net 2.0, since quite a few earlier methods in framework 1.0 have been deprecated. The serial number of a certificate can be found in the certificate store by clicking on the properties of the certificate:

View Certificates

 

Note that you will need to remove all the spaces in the serial number. Alternatively, you may also use any other X509FindType’s (e.g. FindByIssuerName, FindBySubjectKey, etc.) to retrieve the certificate in the certificate store.

  1. Debugging

The first step in debugging is to make sure that you actually can access the certificate in code. Set a breakpoint at col.Count and make sure that it is 1. If col.Count is 0, you will need to go back and inspect whether step 1 and 2 were performed correctly.

So far we have verified that the certificate was imported correctly. Making sure that the certificate actually works would take a bit more patience if you are stuck with mysterious errors.

One of the most common problems when you try to run the program after utilizing thee certificate is the error

The request was aborted: Could not create SSL/TLS secure channel. The underlying connection was closed: An unexpected error occurred on a send.

This is typically caused by incorrect certificate (e.g. key length, certificate type mismatch) settings. To help debugging the exact cause of this problem, you may want to include some trace listeners in your web.config (or app.config) file (see Durgaprasad Gorti’s Blog).

<system.diagnostics>

<trace autoflush="true"/>

<sources>

<source name="System.Net" maxdatasize="1024">

<listeners>

<add name="TraceFile"/>

</listeners>

</source>

<source name="System.Net.Sockets" maxdatasize="1024">

<listeners>

<add name="TraceFile"/>

</listeners>

</source>

</sources>

<sharedListeners>

<add name="TraceFile" type="System.Diagnostics.TextWriterTraceListener" initializeData="trace.log"/>

</sharedListeners>

<switches>

<add name="System.Net" value="Verbose" />

<add name="System.Net.Sockets" value="Verbose" />

</switches>

</system.diagnostics>

When you run your ASP.Net project with this configuration section, you will see a trace file (trace.log) generated in your project folder when you run your web project. When your code errors out with the exception

The request was aborted: Could not create SSL/TLS secure channel.

Take a look at trace file, you should see that the service found the certificate on your computer (assume that the certificate is imported correctly, if not go to step 1 and 2), and you might also find something similar to the following (just an example to give you an idea what to look for):

System.Net Information: 0 : [1916] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 6019cc0:1749d0, targetName = {IP Address}, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)

System.Net Information: 0 : [1916] InitializeSecurityContext(In-Buffer length=7, Out-Buffer length=0, returned code=IllegalMessage).

System.Net Error: 0 : [1916] Exception in the HttpWebRequest#37256635::EndGetResponse – The request was aborted: Could not create SSL/TLS secure channel.

The above trace information generally indicates that the certificate used is not correct. You might want to double check the RSA key length and certificate type to ensure that they are correct before investigating further. There is a lot of information in the trace file, so you might need to comb through it a couple of times before you find the problem. If your certificate is a test certificate you might want to change the last argument passed to store.Certificates.Find is set to false because the test certificate might not be valid.

Another common problem for this error is that the account under which ASP.Net is running does not have proper permissions to access the provided certificate. This situation typically happens on Windows 2003 server where ASP.Net is running under IIS_WPG. This situation can be confirmed from the following trace messages (occurs right before InitializeSecurityContext):

System.Net Information: 0 : [4608] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent = Outbound, scc = System.Net.SecureCredential)

System.Net Error: 0 : [4608] AcquireCredentialsHandle() failed with error 0X8009030D.

System.Net Information: 0 : [4608] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent = Outbound, scc = System.Net.SecureCredential)

System.Net Error: 0 : [4608] Exception in the HttpWebRequest#34090260::EndGetResponse – The request was aborted: Could not create SSL/TLS secure channel.

If you get the AcquireCredentialsHandle() failed message, you might want to double check step 1 to see if you have granted the ASP.Net worker process the correct permission (using winhttpcertcfg.exe).

A successful hand shake looks like this (only a small portion is shown):

System.Net Information: 0 : [3048] SecureChannel#56552832 – Certificate is of type X509Certificate2 and contains the private key.

System.Net Information: 0 : [3048] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent = Outbound, scc = System.Net.SecureCredential)

System.Net Information: 0 : [3048] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = (null), targetName = {IP Address}, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)

System.Net Information: 0 : [3048] InitializeSecurityContext(In-Buffer length=0, Out-Buffer length=70, returned code=ContinueNeeded).

System.Net.Sockets Verbose: 0 : [3048] Socket#49783578::Send()

System.Net.Sockets Verbose: 0 : [3048] Data from Socket#49783578::Send

System.Net.Sockets Verbose: 0 : [3048] 00000000 : 16 03 01 00 41 01 00 00-3D 03 01 45 70 5F AF FD : ….A…=..Ep_..

System.Net.Sockets Verbose: 0 : [3048] 00000010 : BA F6 F1 25 72 36 E5 48-A6 A6 C0 8D C7 67 C9 C8 : …%r6.H…..g..

System.Net.Sockets Verbose: 0 : [3048] 00000020 : DE 6F C1 D2 23 C3 37 66-9B 0F 7A 00 00 16 00 04 : .o..#.7f..z…..

System.Net.Sockets Verbose: 0 : [3048] 00000030 : 00 05 00 0A 00 09 00 64-00 62 00 03 00 06 00 13 : …….d.b……

System.Net.Sockets Verbose: 0 : [3048] 00000040 : 00 12 00 63 01 00 : …c..

System.Net.Sockets Verbose: 0 : [3048] Exiting Socket#49783578::Send() -> 70#70

System.Net.Sockets Verbose: 0 : [3048] Socket#49783578::Receive()

System.Net.Sockets Verbose: 0 : [3048] Data from Socket#49783578::Receive

System.Net.Sockets Verbose: 0 : [3048] 00000000 : 16 03 01 00 2A : ….*

System.Net.Sockets Verbose: 0 : [3048] Exiting Socket#49783578::Receive() -> 5#5

System.Net.Sockets Verbose: 0 : [3048] Socket#49783578::Receive()

System.Net.Sockets Verbose: 0 : [3048] Data from Socket#49783578::Receive

System.Net.Sockets Verbose: 0 : [3048] 00000005 : 02 00 00 26 03 01 61 66-83 6C 31 45 99 CE EC 91 : …&..af.l1E….

System.Net.Sockets Verbose: 0 : [3048] 00000015 : 32 C8 7D 7B 23 41 36 8B-04 C9 99 33 97 6E B3 9D : 2.}{#A6….3.n..

System.Net.Sockets Verbose: 0 : [3048] 00000025 : 32 79 2C F7 93 94 00 00-04 00 : 2y,…….

System.Net.Sockets Verbose: 0 : [3048] Exiting Socket#49783578::Receive() -> 42#42

System.Net Information: 0 : [3048] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 6008b48:f6028, targetName = {IP Address}, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)

System.Net Information: 0 : [3048] InitializeSecurityContext(In-Buffer length=47, Out-Buffer length=0, returned code=ContinueNeeded).

Also, consuming web services via SSL using X509 certificate does not require the web services being accessed via port 443 (standard SSL port), it can be any port as long as the URL is prefixed with https. Nor does it require the whole website being secured by SSL. It is perfectly fine for a non secure website to access a secure web service. The certificate in our scenario is used to authenticate the caller of a web service and the service being called does not care whether or not the call is originated from a secure website as long as the certificate presented is valid.

Even though calling web services can be done securely using X509 certificates without using WSE (see link at the end), it is highly recommended that WSE be used whenever it is possible because WSE implements more security features then just the initial handshake.

Here are some related links you might find worth reading:

Windows HTTP Services Certificate Configuration Tool (WinHttpCertCfg.exe)

Certificate Revocation and Status Checking

How to: Configure an XML Web Service for Windows Authentication

PRB: "System.Net.WebException. The Underlying Connection Was Closed. Could Not Establish Trust Relationship with Remote Server." Error Message When You Upgrade the .NET Framework

Web Services Enhancements 3.0

How to call a Web service by using a client certificate for authentication in an ASP.NET Web application

Building Secure ASP.NET Applications: Authentication, Authorization, and Secure Communication

Securing XML Web Services Created Using ASP.NET

How to: Perform Custom Authentication Using SOAP Headers

HTTP Security and ASP.NET Web Services

How to send a client certificate by using the HttpWebRequest and HttpWebResponse classes in Microsoft Visual C# .NET

Be Sociable, Share!

31 Comments

  1. Stilgar says:

    Very helpful, thank you. Most other tutorials suggest using WSE. At least the top Google hits do. It may be worth mentioning that you can load certificate directly from file without installing it. I chose this method to simplify deployment.

  2. Marcos says:

    Thanks a lot. Your article have make me worked out all my problems regarding certificates issues.
    Best regards

  3. Alex says:

    Thanks very much for this, was struggling for hours!

  4. Nelson says:

    Hello,

    I’m developing a ASP.NET that must need to access an Webservice/SOAP over SSL.
    The other side was sent a certificate to apply in my application, but was generated in Java and have the .keystore extension.

    Maybe do you know how can I install/use this cert in IIS 6.0 or ASP code.

    Thaks in advance.

  5. Neil says:

    That has hit the spot!

    Great article – exactly what I needed.

    : n)

  6. Bjarke says:

    Thanks it really helped me out.

    One thing though.. I dont know if it depends on which version of win2003 server you are running, but my ASP worker process used the user called ASPNET.

  7. Brian Brinley says:

    Thank you for all your help. I also ran into the acquirecredentials problem on asp.net 2.0 vb.

    Your code example “X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);

    by looking in storename.root this will look in the trusted root certificates. Due to a security risk you are not allowed to send certificates directly from the trusted root certificates store. You need to only load the certificate in the localcomputer\store and should use this code instead.

    X509Store store = new X509Store(StoreLocation.LocalMachine);

    I spent several weeks debugging this issue only to contact microsoft and after several hours find this to be the cause.

    Your previous code worked normal on .net framework 2.0.50727.42 but did NOT work on 2.0.50727.832

    somewhere between these versions the secrity risk was addressed and you can no longer send directly from trusted root certificates.

  8. Vijayan says:

    The XP IIS user is “ASPNET”, not “ASPNET_WP” as stated. (The process name is aspnet_wp.exe.)

    Also, I had to give ASPNET permissions on XP, though your article says its unnecessary. (I had imported my cert via IE7 to my Personal store, and copied it via the MMC panel to the Local Machine store.)

    Thanks for the most helpful article I could find on Google.

    /v

  9. Mike Jensen says:

    Thanks for this article; it was very helpful.

    I found that on my 2003 Server, it was necessary for me to grant permission to the NETWORK SERVICE account to access the cert:
    winhttpcertcfg -g -c LOCAL_MACHINE\My -s XXXXXXX -a NETWORKSERVICE

  10. Kevin H says:

    I got the following error when I tried to run winhttpcertcfg.exe on Windows 2003. Any boday got the same issue? Thanks for any help!

    Access was not successfully obtained for the private key. This can only be done by the user who installed the certificate.

  11. ken c says:

    I have followed all the advice but i am still getting the

    System.Net Information: 0 : [2872] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent = Outbound, scc = System.Net.SecureCredential)
    System.Net Error: 0 : [2872] AcquireCredentialsHandle() failed with error 0X8009030D.

    error. However further up the trace i seem to get a successful hand shake?

  12. [...] to translate powershell to asp.net. I used this post to get my asp.net webservice caller working: http://www.kerrywong.com/2006/12/01/…vice-in-aspnet It doesn’t sound like you need a certificate but the post includes the necessary web.config [...]

  13. rmhurt says:

    i was getting the “AcquireCredentialsHandle() failed with error 0X8009030D” error on my XP Pro SP2 workstation running an ASP.NET 2.0 app under “anonymous access” with a specific user/password (and “Integrated Windows authentication” checked). i tried the “winhttpcertcfg” command with this identity but that changed nothing. i found this page and tried ASPNET_WS as the account and found out that ASPNET_WS is not an account on my XP. however, a quick check of User Accounts in the control panel showed an “ASPNET” account, and the following solved my problem:

    winhttpcertcfg -g -c LOCAL_MACHINE\My -s {certificate_name} -a ASPNET [NOTE: "My" and not "Root"]
    iisreset

    thanks!

  14. Rakesh says:

    we need to also Import a Certificate into Trusted Store also for windows-7
    because CA certificate also need that

  15. Rakesh says:

    you can directly set a Permission in VISTA,2003/2008,windows7 using
    winhttpcertcfg -g -c LOCAL_MACHINE\MY -s “certificate-subject” -a “IIS_WPG”
    and it works fine.

  16. Marshall says:

    Thanks for posting this Kerry, this helped out your old pals a lot!

  17. Adam says:

    Any advice for this on Server 2008? I’m using the built in ‘manage private keys’ in the Certificates MMC Snap In but I’m not quite sure which user account needs permissions.

    Thanks

  18. Claudio says:

    You made my day!
    the paragraph “Importing certificates” was all I needed

    thanks x 1000

  19. [...] remerciement à l’auteur de ce post qui m’a bien aidé pour résoudre cette erreur. :web [...]

  20. [...] solved by using the Windows HTTP Services Certificate Configuration Tool and information I obtained here. Tagged: ccertificatequestionsSSL /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING [...]

  21. Mike Rollins says:

    I was also getting the error:

    System.Net Error: 0 : [4608] AcquireCredentialsHandle() failed with error 0X8009030D.

    I tried adding IIS_WPG and it still didn’t work, but your comment about that error indicating a missing permission to the certificate got me thinking. I checked Task Manager to see what was name the process W2WP.exe was running under – which is the process we attach to when debugging. Turns out it was “ASP.NET v4.0 Classic”, so I added that to the certificate permissions and presto… it worked.

  22. Dan Davis says:

    I think it’s hilarious when I’m researching a development issue for work(ahem..) and end up here. Thanks for the post, Kerry.

Leave a Reply