I'm trying to connect to a server that uses TLS with client certificate authentication. Below is a code snippet:
async Task TestClientCertAuth()
{
int iWinInetError = 0;
Uri theUri = new Uri("http://xxx-xxx");
try
{
using (HttpBaseProtocolFilter baseProtocolFilter = new HttpBaseProtocolFilter())
{
// Task<Certificate> GetClientCertificate() displays a UI with all available
// certificates with and returns the user selecter certificate. An
// oversimplified implementation is included for completeness.
baseProtocolFilter.ClientCertificate = await GetClientCertificate();
baseProtocolFilter.AllowAutoRedirect = false;
baseProtocolFilter.AllowUI = false;
using (HttpClient httpClient = new HttpClient(baseProtocolFilter))
using (HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Get, theUri))
using (HttpResponseMessage httpResponse = await httpClient.SendRequestAsync(httpRequest))
{
httpResponse.EnsureSuccessStatusCode();
// Further HTTP calls using httpClient based on app logic.
}
}
}
catch (Exception ex)
{
iWinInetError = ex.HResult & 0xFFFF;
LogMessage(ex.ToString() + " Error code: " + iWinInetError);
throw;
}
}
// Task<Certificate> GetClientCertificate() displays a UI with all available
// certificates with and returns the user selecter certificate. An
// oversimplified implementation is included for completeness.
private async Task<Certificate> GetClientCertificate()
{
IReadOnlyList<Certificate> certList = await CertificateStores.FindAllAsync();
Certificate clientCert = null;
// Always choose first enumerated certificate. Works so long as there is only one cert
// installed and it's the right one.
if ((null != certList) && (certList.Count > 0))
{
clientCert = certList.First();
}
return clientCert;
}
The SendRequestAsync call throws an exception with HRESULT 0x80072F7D - I believe that means ERROR_INTERNET_SECURITY_CHANNEL_ERROR. There are no problems with the server certificate trust. The client certificate is installed in the app local store and I am abe to retrieve it using CertificateStores.FindAllAsync. Looking at the SSL traces and I can see the client certificate is not being sent.
The the above issue does not occur if HttpBaseProtocolFilter.AllowUI is set to true. In this case, the SendRequestAsync call causes a UI to be displayed asking for consent to use the client certificate. Once 'Allow' is selected on this dialog, I can see the client cert and cert verify messages being sent in the traces and the connection is established successfully.
Question: The app code already handles certificate selection by the user. I would like to know whether there is any way to specify consent to use the client certificate programmatically. Because enabling AllowUI causes other side effects - say for example if the server retruns a 401 HTTP code with a WWW-Authenticate: Basic header, the base protoctol filter pops up it's own UI to accept the user credentials without giving a chance for the caller to handle it. Would like to avoid both of the above UIs since I have already selected the client certificate and obtained credentials from the user with own UIs. Thanks