How can we improve Microsoft Azure Functions?

Add "clientCertEnabled" support for local development of Azure Functions

If I set the following flag on my azure-published azure-function "clientCertEnabled": true, my sample code below is able to use/consume the client certificate I send.

I am unable to get local debugging to accept client-certificates.

This makes unit-testing my azure function very difficult.

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using System.Security.Cryptography.X509Certificates;
using System.Collections.Generic;
using System.Linq;

namespace MyNamespace
{
public static class ClientCertificateTest
{
[FunctionName("ClientCertificateTestFunctionName")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
try
{
System.Security.Cryptography.X509Certificates.X509Certificate2 cert = req.GetClientCertificate();

X509Certificate2 headerCert = null;
System.Net.Http.Headers.HttpRequestHeaders headers = req.Headers;
if (headers.Any(x => x.Key.Equals("X-ARR-ClientCert", StringComparison.OrdinalIgnoreCase)))
{
IEnumerable<string> headerValues = headers.GetValues("X-ARR-ClientCert");
if (null != headerValues)
{
var certHeader = headerValues.FirstOrDefault();
byte[] clientCertBytes = Convert.FromBase64String(certHeader);
headerCert = new X509Certificate2(clientCertBytes);
}
}

string msg = (null == cert ? "NO CERT :(" : string.Format("We got a cert! '{0}'", cert.Subject)) + " " + (null == headerCert ? "NO headerCert :(" : string.Format("We got a headerCert! '{0}'", headerCert.Subject));

HttpContent content = req.Content;
string contentString = content.ReadAsStringAsync().Result;

msg += String.IsNullOrEmpty(contentString) ? ", no content" : ", " + contentString;

return req.CreateResponse(HttpStatusCode.OK, msg);
}
catch (Exception ex)
{
string errorMsg = ex.Message;
log.Error(errorMsg);
return req.CreateResponse(HttpStatusCode.BadRequest, errorMsg);
}
}
}
}

Microsoft Visual Studio Enterprise 2017 Preview (2)
Version 15.4.0 Preview 1.0
VisualStudio.15.Preview/15.4.0-pre.1.0+26823.1

Below is my "client" code for completeness.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace MyNameSpace.ConsoleOne
{
class Program
{
static void Main(string[] args)
{
try
{
////ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

/////////* Use the below to debug failed verification. */
////////ServicePointManager.ServerCertificateValidationCallback =
//////// new System.Net.Security.RemoteCertificateValidationCallback((
//////// sender,
//////// cert,
//////// chain,
//////// ssl) =>
////////{
//////// Console.WriteLine("ServerCertificateValidationCallback for Cert.Subject : '{0}'", cert.Subject);
//////// return true;
////////});

Console.WriteLine("START : {0}", System.Diagnostics.Process.GetCurrentProcess().ProcessName);

/* the kind of cert matters this one works, this kind of certificate worked
X509Extension.X509KeyUsageExtension.KeyUsages = 'CrlSign, KeyCertSign' */
string thumbPrint = "0123456789012345678901234567890123456789";

/* certicate that would not work */
/*X509Extension.X509KeyUsageExtension.KeyUsages = 'KeyEncipherment, DigitalSignature' */
//thumbPrint = "1234567890123456789012345678901234567890";

X509Certificate2 clientCert = GetClientCertificate(thumbPrint);
WebRequestHandler requestHandler = new WebRequestHandler();
requestHandler.ClientCertificates.Add(clientCert);

string suffixUrl = "api/ClientCertificateTestFunctionName";


/* local not working, local cannot see client certificate */
string baseUrl = "http://localhost:7071/";

/* remote (azure published, if clientCertEnabled is set to true, it will work */
baseUrl = "https://yourFunctionNameHere.azurewebsites.net/";

/* some vodoo to create the queryString, the "code" value is gotten via the azure-portal "Get function URL" while on the properties page of the azure function... NOTE, I had to get this value before I change clientCertEnabled to true (aka, when it was clientCertEnabled was false) after I changed clientCertEnabled to true, the "extra" querystring of the azure function was not showing */
var builder = new UriBuilder("http://www.wontactuallybeused.com");
builder.Port = -1;
var query = System.Web.HttpUtility.ParseQueryString(builder.Query);
query["code"] = "NotForYouToSee0123456789012345678901234567890123456789==";
builder.Query = query.ToString();
string url = builder.ToString();
suffixUrl += builder.Query;

HttpClient client = new HttpClient(requestHandler)
{
BaseAddress = new Uri(baseUrl)
};

var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("login", "abc")
};

var postContent = new FormUrlEncodedContent(pairs);

HttpResponseMessage hrm = client.PostAsync(suffixUrl, postContent).Result;
//hrm.EnsureSuccessStatusCode();
if (hrm.IsSuccessStatusCode)
{
}

HttpContent content = hrm.Content;
string contentString = content.ReadAsStringAsync().Result;

Console.WriteLine(hrm);
Console.WriteLine(contentString);
}
catch(Exception ex)
{
string errorMsg = GenerateFullFlatMessage(ex, true);
Console.WriteLine(errorMsg);
}

Console.WriteLine("END : {0}", System.Diagnostics.Process.GetCurrentProcess().ProcessName);
Console.WriteLine(string.Empty);

Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
}

private static X509Certificate2 GetClientCertificate(string thumbprintValue)
{
thumbprintValue = System.Text.RegularExpressions.Regex.Replace(thumbprintValue, @"[^\da-zA-z]", string.Empty).ToUpper();
X509Store userCaStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
try
{
userCaStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificatesInStore = userCaStore.Certificates;
X509Certificate2Collection findResult = certificatesInStore.Find(X509FindType.FindByThumbprint, thumbprintValue, true);
X509Certificate2 clientCertificate = null;
if (findResult.Count == 1)
{
clientCertificate = findResult[0];
}
else
{
throw new Exception("Unable to locate the correct client certificate.");
}
return clientCertificate;
}
catch
{
throw;
}
finally
{
userCaStore.Close();
}
}

private static string GenerateFullFlatMessage(Exception ex, bool showStackTrace)
{
string returnValue;

StringBuilder sb = new StringBuilder();
Exception nestedEx = ex;

while (nestedEx != null)
{
if (!string.IsNullOrEmpty(nestedEx.Message))
{
sb.Append(nestedEx.Message + System.Environment.NewLine);
}

if (showStackTrace && !string.IsNullOrEmpty(nestedEx.StackTrace))
{
sb.Append(nestedEx.StackTrace + System.Environment.NewLine);
}

nestedEx = nestedEx.InnerException;
}

returnValue = sb.ToString();

return returnValue;
}
}
}

8 votes
Sign in
(thinking…)
Sign in with: Microsoft
Signed in as (Sign out)

We’ll send you updates on this idea

Anonymous shared this idea  ·   ·  Flag idea as inappropriate…  ·  Admin →

0 comments

Sign in
(thinking…)
Sign in with: Microsoft
Signed in as (Sign out)
Submitting...

Feedback and Knowledge Base