Поделиться через


Programmatically create and configure a Client Certificate for use in your Windows Runtime based app

 

Often there is a need in your Windows Store app (including Windows Phone 8.1) to access resources (such as a WebServer) which requires Client certificate authentication.

You typically use the Windows.Web.Http.HttpClient class to send a HTTPs request to your WebServer and attach a Client Certificate with the request to access such resources.

This blog will go through the three main steps that are required to programmatically create and configure a Client Certificate for such usage.

The three steps that are involved in this process are:

Step 1.) Create a “key-pair” using the CertificateEnrollmentManager.CreateRequestAsync Windows Runtime API.

Step 2.) Submit the certificate request to a Certificate Authority (CA).

Step 3.) Install the response from the CA by calling the CertificateEnrollmentManager.ImportPfxDataAsync Windows Runtime API.

We will go through the details of each step below:

Step 1.) Create a “key-pair” using the CertificateEnrollmentManager.CreateRequestAsync Windows Runtime API.

This step generates a key pair (Private and Public key) on the system that is requesting the client certificate. This is the same system where the Windows Store app is running. The generation of the key pair is done by using the CertificateEnrollmentManager.CreateRequestAsync and a typical use of code is something like this:.

 CertificateRequestProperties certRequestProperties = new CertificateRequestProperties();
 certRequestProperties.Exportable = ExportOption.NotExportable; // default
 certRequestProperties.FriendlyName = "Test Certificate"; // use the friendly name from the form
 certRequestProperties.KeyProtectionLevel = KeyProtectionLevel.NoConsent; // default.
 certRequestProperties.KeyUsages = EnrollKeyUsages.Signing; // default
 certRequestProperties.Subject = "John Doe"; // use the subject from the form
  
 String _certificateRequest = await CertificateEnrollmentManager.CreateRequestAsync(certRequestProperties);

In the sample code that is provided with this blog, this logic is seen in the btnCreateCertRequest_Click event handler.

Step 2.) Submit the certificate request to a Certificate Authority (CA).

Calling CertificateEnrollmentManager.CreateRequestAsync will generate a Base64 encoded PKCS#10 request which you will need to submit to a Certificate Authority for generating a client certificate. You typically configure a Certificate Authority by installing the “Active Directory Certificate Services” role on a Windows Server system.

When the Certificate Authority has been configured, you can use the “CertCli 1.0 Type Library” to submit the generated certificate request to the Certificate Authority. This library is not available for a Windows Store app, so we can use a “Proxy” WebServer that can accept the certificate request from the Windows Store app, relay the request to the Certificate Authority and then relay back the response from the Certificate Authority to the Windows Store app.

In the sample code provided with this blog, I have created an ASP.NET MVC Web App and added a controller called “EnrollmentController.cs” to it. This WebServer project references the “CertCli 1.0 Type Library” and exposes a method called SubmitToCertificateAuthority which takes in a HTTP POST request from the Windows Store app with the parameter of “request” and then forwards the request to the Certficate Authority.

Typically the code to forward the request from this “proxy” WebServer to the Certificate authority looks as simple as this:

 const string CAConfig = "CA_Network_Name\\CA_Name";  // This is the name of your Certificate Authority
 ICertRequest CertRequest = new CCertRequest();
  
 // Submit the request to the certificate authority and check for the status
 int retVal = CertRequest.Submit(Constants.CR_IN_ENCODEANY, certificateRequest, "", CAConfig);
  
 // The return value of the Submit method determines whether the certificate was successfully generated or not. 
 switch (retVal)
 {
     case Constants.CR_DISP_ISSUED:  // This is the only successful case.
         String strCertificate = CertRequest.GetCertificate(Constants.CR_OUT_BASE64);
         break;
     //...every other case is pretty much an error!
 }

The strCertificate response that is returned back from the CertificateAuthority can then be relayed back to the client.

In the sample project, I am returning back the certificate information in a JSON structured output which includes any error information that will be useful to the client – to determine the status of the certificate request.

From the Windows Store app perspective, all you have to do is to send a HTTP request to this Proxy Web Server which will then forward the request to the CA. The sample code provided with this blog does this in the btnSubmitCertificate_Click event handler.

Typically, the code will look like this:

 // the _certificateRequest that is generated in Step 1 is base64encoded to prevent running into formatting issues (for example the "+" gets converted to a space if not escaped).
 // ofcourse you need to base64Decode it on the target WebServer. You can also hand-encode all the escape sequences, but this approach is less time consuming :)
 String base64EncodedRequest = Convert.ToBase64String(Encoding.UTF8.GetBytes(_certificateRequest)); 
  
 HttpStringContent _stringContent = new Windows.Web.Http.HttpStringContent("request=" + base64EncodedRequest);
 _stringContent.Headers.ContentType = HttpMediaTypeHeaderValue.Parse("application/x-www-form-urlencoded");
  
 HttpResponseMessage _responseMessage = await _client.PostAsync(_serverUri, _stringContent);
 String _response = await _responseMessage.Content.ReadAsStringAsync();
  
 //... the _response is in a JSON Format, so we will use the DataContractJsonSerializer to de-serialize the response to a structured format.
 MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(_response));
 DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(CertificateSubmissionResult));
 CertificateSubmissionResult certificateSubmissionResult = (CertificateSubmissionResult)serializer.ReadObject(ms);
 ms.Dispose();
  
 //...certificateSubmissionResult.Certificate will contain the certificate. Look for the complete sample to see how the CertificateSubmissionResult class looks like

Step 3.) Install the response from the CA by calling the CertificateEnrollmentManager.ImportPfxDataAsync Windows Runtime API.

If the certificate generation by the Certificate Authority was successful, you can simply call the CertificateEnrollmentManager.ImportPfxDataAsync Windows Runtime API to install the certificate in the App container store (not the Current User certificate store).

Assuming that the response from Step 2 above was saved in a string called: strCertificate, the code to install the certificate will look something like this:

 // now install the certificate in the certificate store of the app
 await CertificateEnrollmentManager.ImportPfxDataAsync(_certificateSubmissionResult.Certificate, 
                                                       "", // the password is blank, but you can specify one here
                                                       ExportOption.NotExportable, // there is no reason to keep the certificate Exportable
                                                       KeyProtectionLevel.NoConsent, // whether any consent is required
                                                       InstallOptions.None, // no installation options
                                                       "Test Certificate"); // use the same friendly name as in the certificate request for future reference

 

That is it!

Once the certificate is installed in the App Container store, it is good for use (until it expires or some other error condition :)…).

You can then use the Windows.Web.Http.HttpClient class to programmatically attach the installed Client Certificate and send a request to a WebServer that requires Client Certificate authentication.

Note: For Windows Phone 8.1, you need to attach the Client Certificate programmatically. For Windows, once you install the Client Certificate to the app container store and do not attach the client certificate with the HttpClient request, the HttpClient class will automatically detect that there is a single certificate installed in the app container store and forward it to the server. However in the case of Windows Phone 8.1, there is no such “automatic” selection of the certificate and one MUST provide the certificate programmatically.

Below is a sample code of how would one typically make use of the certificate that was just installed and include it in subsequent HTTPs requests:

 Windows.Security.Cryptography.Certificates.CertificateQuery certQuery = new Windows.Security.Cryptography.Certificates.CertificateQuery();
 certQuery.FriendlyName = "Test Certificate";    // This is the friendly name of the certificate that was just installed.
 IReadOnlyList<Windows.Security.Cryptography.Certificates.Certificate> certificates = await Windows.Security.Cryptography.Certificates.CertificateStores.FindAllAsync(certQuery);
 if(certificates.Count == 1) 
 {
     Windows.Web.Http.Filters.HttpBaseProtocolFilter aBPF = new Windows.Web.Http.Filters.HttpBaseProtocolFilter();
     aBPF.ClientCertificate = certificates[0];   // This line is required for Windows Phone 8.1 app
     Windows.Web.Http.HttpClient aClient = new Windows.Web.Http.HttpClient(aBPF);
     await aClient.GetAsync(...);
 } 
 else 
 {
     // typically you should only have a single certificate that matches the certificate search criteria and since you only installed one such certificate!
 }

 

Hopefully this blog will make it easier for your Windows Runtime Based apps to make use of Client Certificates.

Don’t forget to follow the Windows Store Developer Solutions team on Twitter @wsdevsol. Comments are welcome, both below and on twitter.

 

- Prashant H Phadke.

CreateRequestAndImportCertificate.zip

Comments

  • Anonymous
    January 05, 2015
    Prashant, Great article! Im on the latest dev version of the Windows Phone - 8.10.14219.341. Im using StreamSocket and have to set client certificate for SSL. Is there any way to achieve that? I can not use HttpClient, as Im working with VPN api, which requires StreamSocket. regards, Victor

  • Anonymous
    November 03, 2015
    Hi, whats is the server uri? I host my web services in a https url, in this case, i use this url like the variable " _serverUri", but the PostAsync method return the HTML of my page.