How to use a Shared User Certificate for HTTPS authentication in an Enterprise application.
In Update 1 for Windows Phone 8.1, the ability to use the sharedUserCertificates capability was granted to Enterprise signed applications. The 'sharedUserCertificates' capability grants an application permission to access the certificates that the user has installed manually or that have been installed by an Enterprise MDM server, using SCEP protocol.
Developers who try to use a client certificate from the shared user certificate store may run into the problem that the selected client certificate does not get sent to the server in response to the server challenge. Normally you specify the client certificate to use by setting the ClientCertificate property of the HttpBaseProtocolFilter a certificate obtained using CertificateStores.FindAllAsync, for example:
public static async Task<HttpClient> CreateHttpClientWithCertificate(string Thumbprint)
{
var thumbprintBytes = Thumbprint.StringToByteArray();
// get a reference to the certificate
var certQuery = new CertificateQuery()
{
Thumbprint = thumbprintBytes
};
var cert = (await CertificateStores.FindAllAsync(certQuery)).FirstOrDefault();
var filter = new HttpBaseProtocolFilter();
if (cert != null) // this should always be present, if not we'll get an auth error
{
filter.ClientCertificate = cert;
}
var client = new HttpClient(filter);
return client;
}
This will work if the certificate is stored in the applications local certificate store.
However, the security subsystem requires user confirmation before allowing access to a certificates private key of a certificate stored in the shared user certificates store. To complicate matters, if a client certificate is specified in code then the lower level network functions assume the application has already taken care of this and will not prompt the user for confirmation.
If you look at the Windows Runtime classes related to certificates you won’t find any method to explicitly request access to the certificate private key, so what is the app developer to do?
The solution is to use the selected certificate to 'Sign' some small bit of data. When an application calls CryptographicEngine.SignAsync, the underlying code requests access to the private key to do the signing at which point the user is asked if they want to allow the application to access the certificate private key. Note that you must call 'Async' version of this function because the synchronous version of the function: Sign, uses an option that blocks the display of the confirmation dialog.
For example:
public static async Task<bool> VerifyCertificateKeyAccess(Certificate selectedCertificate)
{
bool VerifyResult = false; // default to access failure
CryptographicKey keyPair = await PersistedKeyProvider.OpenKeyPairFromCertificateAsync(
selectedCertificate, HashAlgorithmNames.Sha1,
CryptographicPadding.RsaPkcs1V15);
String buffer = "Data to sign";
IBuffer Data = CryptographicBuffer.ConvertStringToBinary(buffer, BinaryStringEncoding.Utf16BE);
try
{
//sign the data by using the key
IBuffer Signed = await CryptographicEngine.SignAsync(keyPair, Data);
VerifyResult = CryptographicEngine.VerifySignature(keyPair, Data, Signed);
}
catch (Exception exp)
{
System.Diagnostics.Debug.WriteLine("Verification Failed. Exception Occurred : {0}", exp.Message);
// default result is false so drop through to exit.
}
return VerifyResult;
}
You can then modify the earlier code example to call this function prior to using the client certificate in order to ensure the application has access to the certificate private key, like so:
public static async Task<HttpClient> CreateHttpClientWithCertificate(string Thumbprint)
{
var thumbprintBytes = Thumbprint.StringToByteArray();
// get a reference to the certificate
var certQuery = new CertificateQuery()
{
Thumbprint = thumbprintBytes
};
var cert = (await CertificateStores.FindAllAsync(certQuery)).FirstOrDefault();
var filter = new HttpBaseProtocolFilter();
if (cert != null) // this should always be present, if not we'll get an auth error
{
// verify access to the certificate private key,
// this will prompt the user if needed.
var verifyResult = await VerifyCertificateKeyAccess(cert);
filter.ClientCertificate = cert;
}
var client = new HttpClient(filter);
return client;
}
One final note regarding the behavior on Windows vs Windows Phone. On Windows a dialog will be shown to the user when calling SignAsync and the user has the option to accept or reject along with a check box option to remember the user selection in future, after the application terminates and restarts. On Windows Phone there is no actual user dialog shown, the application is automatically granted access but only for the current application session. If the application terminates and is restarted then it must again go through this SignAsync operation to get access to the private key.
Don’t forget to Follow the Windows Store Developer Solutions team on Twitter @wsdevsol. Comments are welcome, both below and on twitter.