Recently I decided that I wanted to see how easy it would be to build a Windows 8 application that consumed the Windows Azure Management API. It seemed like it should be an easy thing, and something that could potentially end up in a nice management/overview/dashboard kind of application. However, it isn’t quite that simple as I thought as Windows Azure uses certificates for authentication of the HTTP requests being used.
Using certificates for HTTP requests isn’t really that hard, at least not when working in .NET. But in Windows 8 apps, we are using WinRT, which is way more sandboxed, and to be honest, a bit more complicated, which makes it a little bit more complex…
Before I got started, I obviously Googled the problem, and ended up with a bunch of hits with more or less useful information about how to use certificates when doing HTTP requests. But in the end, it was Gaurav Mantri’s blog post that gave me the most information. It is a great blog post, covering most things you need to know, and a lot of the things I will talk about is covered din his blog post as well. However, he mentions a few limitations that it seems possible to get around… But despite that, I suggest reading his blog post as well.
Anyhow…how do we go about accessing the Azure Management API? Well, the first step is to generate a certificate to use for authentication. This is “easily” done by executing the following slightly arcane command in the Developer Command Prompt
makecert -r -pe -n "CN=[Cert Name]" -e 01/01/2099 –ss My
This will generate a new certificate and install it in the “My” certificate container.
Gaurav’s blog post mentions that you need to have an OID with the value “1.3.6.1.5.5.7.3.2”, which identifies the certificate as being an SSL certificate (I think). This is true, if you make the request in the way that he does, using ClientCertificateOption.Automatic. However, if you don’t, having, or not having, the OID makes very little difference. But if you want to add an OID, just open the certificate properties using the “Certificates” snap-in in mmc.exe and click the “Extended Validation”. On this tab, you can add the OID if needed…
Once the cert is in the “My” store, you need to use mmc.exe to export it twice. First, you need it in a pfx-format for the client. And secondly, you need it in cer-format for upload to Azure.
To export it as a pfx-file, find the cert in the Certificates snap-in, right click it and choose All Tasks > Export. Then choose “Next”, “Yes, export…”, “Next” and so on. The important thing being to export the private key… For more information about how to export it for use in Azure, look here.
Once you have your exported certificate, you can even delete it from the certificate store if you want to. What!? Don’t I need it in there!? Well, Windows 8 apps have limited access to the certificate store on the machine. And by “limited” I mean “none what so ever”. At least if you don’t enable the “sharedUserCertificates” capability… But in general, you aren’t allowed to roam around and use certs from the computers cert stores. So instead, you can install the certificates to use in the app’s shared certificates store instead. And because of this, it doesn’t need to be in the “My” store…more on that later…
Side note: Enabling the “sharedUserCertificates” capability will enable you to query and use certificates from the users “My” store, as well as the “Smart Card Trusted Roots” store. However, this capability is, as Microsoft puts it, “subject to store policy”, which I assume means that your app will be scrutinized even further than normal. So beware, don’t just check it because you want to… It is for very specific situations.
Now that we have both versions of the certificate, it is time to upload the cer-file to Azure. This is done by logging into the Azure Management portal and going to the “Settings” page. Here, you will find a tab called “MANAGEMENT CERTIFICATES”, which will allow you to upload the certificate… Remember that if you have more than one subscription, you will need to upload it for each one of the subscriptions you want to play with…
Ok, now that you have a certificate in the cloud, authorizing you to access the management API for one or more of your subscriptions, it is time to do the actual call.
At least that is what you would hope. Unfortunately there is one more step to perform before we can do this. We need to add the certificate to the applications certificate store.
Adding the cert to the store, is done using a static class called CertificateEnrollmentManager. It has a method called ImportPfxDataAsync(), which takes a bunch of parameters. More specifically, it takes a Base64 encoded string version of the cert, the password added when exporting it, a certificate name, as well as a couple of “config parameters” (More on those later)…
As I just said, the first parameter is a Base64 encoded string version of the cert, so I guess we need to get hold of that… In my case I will ask the user to pick the certificate to use. Like this
var filePicker = new FileOpenPicker();
filePicker.FileTypeFilter.Add(".pfx");
filePicker.ViewMode = PickerViewMode.List;
filePicker.SuggestedStartLocation = PickerLocationId.Desktop;
filePicker.CommitButtonText = "Open File to Process";
var file = await filePicker.PickSingleFileAsync();
if (file == null)
return;
var buffer = await FileIO.ReadBufferAsync(file);
var certificateData = CryptographicBuffer.EncodeToBase64String(buffer);
As you can see, I just let the user select the pfx file using a FileOpenPicker… I then open the file and convert it to a Base64 encoded string.
In this case, I let the client pick the pfx-file to use, but nothing stops you from getting that from a server, as a part of the application install or something else. But for now, letting the user pick it makes it easier to test…
Next I go about doing the actual import using the CertificateEnrollmentManager
await CertificateEnrollmentManager.ImportPfxDataAsync(
certificateData,
"password",
ExportOption.NotExportable,
KeyProtectionLevel.NoConsent,
InstallOptions.None,
"my certificate name");
As you can see, I pass in the certificate-string and the password as the 2 first parameters. The third parameter says that the private key should not be possible to export it. The fourth that the user does not need to give consent when the cert is installed and used. The fifth that I don’t need expired certs to be removed. And the final one is a name for the cert…
The KeyProtectionLevel includes a couple of options that might be interesting. In this case, I don’t need the user to give consent every time the cert is used, but if you wanted to, you could ask the user for a password or fingerprint when the cert is created, as well as every time it is used…
What actually happens when the cert is “installed”, is that it is added to a specific folder under the application package directory somewhere like
C:\Users\{UserName}\AppData\Local\Packages\{application GUID}\AC\Microsoft\SystemCertificates\My\Certificates
And why is this interesting? To be honest, I have no clue what so ever… But I like to know what really happens under the hood. It often help when you start doing stuff you shouldn’t…
Ok, so there is now one version of the cert is in the cloud, and another available in the app. What is next? Well, making the call would be a nice thing I guess… There are now 2 ways of doing this. Gaurav, and a bunch of other people on the web, use a bit of code that looks something like this
private const string XMsVersionName = "x-ms-version";
private const string XMsVersionValue = "2011-10-01";
private const string RequestUriFormat = https://management.core.windows.net/{subscription id};
var handler = new HttpClientHandler { ClientCertificateOptions = ClientCertificateOption.Automatic };
var client = new System.Net.Http.HttpClient(handler);
client.DefaultRequestHeaders.Add(XMsVersionName, XMsVersionValue);
var result = await client.GetAsync(new Uri(RequestUriFormat), HttpCompletionOption.ResponseContentRead);
var response = await result.Content.ReadAsStringAsync();
This works fine, as long as the OID is set to the value mentioned above. It means that the system will try to figure out what cert to use automagically… However, if your cert doesn’t have the correct OID, it won’t be recognized as a “proper” cert, and won’t be used... In that case, or if you just want more control, it is possible to be more explicit and handle it manually (the default). Like this:
private const string XMsVersionName = "x-ms-version";
private const string XMsVersionValue = "2011-10-01";
private const string RequestUriFormat = https://management.core.windows.net/{subscription id};
var filter = new HttpBaseProtocolFilter();
filter.ClientCertificate = GetCert();
var client = new HttpClient(filter);
client.DefaultRequestHeaders.Add(XMsVersionName, XMsVersionValue);
var response = await client.GetStringAsync(new Uri(RequestUriFormat));
It looks very similar, but instead of using ClientCertificateOption.Automatic it manually sets the certificate by calling the GetCert() method. Other than that, it is identical…
But what does the GetCert() method do? How do we get access to the cert? Well, you can query the shared certificate stores and find the cert you want. It is actually really simple, and looks like this:
var query = new CertificateQuery { FriendlyName = "my certificate name" };
var certs = await CertificateStores.FindAllAsync(query);
FindAllAsync() returns an IReadOnlyList<Certificate> containing any cert that fulfills the query, from any of the shared cert stores. In this case, that would only be one, but it could very easily be more than one depending on what you query by.
Ok, that is all there is to it… Create a cert, add it to the server end, add it to the applications shared certificate store, and use it when making the request by using a HttpBaseProtocolFilter.
The only funky thing when using certs in Windows 8 apps, except for it being more complicated than when doing it in .NET, is that I can’t find a way to delete the installed cert… The CertificateStores class has a method called GetStoreByName(), which returns a CertificateStore. The CertificateStore class offers a “Delete()” method, which should make it possible to delete certs I assume. But I don’t actually know… Why? Well, because of the weird detail that GetStoreByName() specifically does NOT allow you to pass in “MY”, so I can’t really find a way to get access to my cert store…and thus I can’t delete my cert… If anyone has any clue as to how to solve this, I would be more than happy if you could tell me!
That’s all for this time! Cheers!