I recently rewrote one of our function apps from .NET Core 3.1 to .NET 6 and implementing dependency injection. Our function app calls a WCF web service endpoint for several bits of shared functionality that we have in our platforms.
Our old .NET Core 3.1 function app used to "register" the web service as follows:
public static IWCFWebService GetWebService()
{
string endpointUrl = Environment.GetEnvironmentVariable("WebServiceEndpoint");
string certString = Environment.GetEnvironmentVariable("WebServiceCert");
return new WCFWebServiceClient(endpointUrl, certString);
}
whereas the WebServiceCert variable was actually just the hashed cert string in our environmental config. It worked fine.
The WCFWebServiceClientconstructor was as follows:
public WCFWebServiceClient(string endpointUrl, string certString) :
base(WCFWebServiceClient.GetDefaultBinding(),
WCFWebServiceClient.GetDefaultEndpointAddress())
{
EndpointUrl = endpointUrl;
this.Endpoint.Address = new System.ServiceModel.EndpointAddress(endpointUrl);
this.Endpoint.Name = EndpointConfiguration.BasicHttpBinding_IWCFWebService.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
this.ClientCredentials.ClientCertificate.Certificate = GetCertificate(certString);
}
In rewriting the app to .NET 6, I wanted to register the WCF service in a way that we were actually consuming the raw certificate and not the hashed string. I registered it as follows:
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureAppConfiguration(config => config
.SetBasePath(Directory.GetCurrentDirectory())
.AddEnvironmentVariables())
.ConfigureServices(services =>
{
services.AddTransient<IWCFWebService>(provider =>
{
var endpoint = Environment.GetEnvironmentVariable("WebServiceEndpoint");
var certificate = new
X509Certificate2(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) +
@"\Certificates\Webservice_Client.pfx", "password");
return new WCFWebServiceClient(endpoint, certificate);
});
}
with the actual raw .pfx certificate being in the project and being built and copied to the output folder.
I wrote an overload for the WCFWebServiceClient constructor to take the X509Certificate instead of the hash string. It is as follows:
public WCFWebServiceClient(string endpointUrl, X509Certificate2 certificate) :
base(WCFWebServiceClient.GetDefaultBinding(),
WCFWebServiceClient.GetDefaultEndpointAddress())
{
EndpointUrl = endpointUrl;
this.Endpoint.Address = new System.ServiceModel.EndpointAddress(endpointUrl);
this.Endpoint.Name = EndpointConfiguration.BasicHttpBinding_IWCFWebService.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
this.ClientCredentials.ClientCertificate.Certificate = certificate;
}
Ever since this implementation, we've had very mixed results. We're often seeing the following error in our Application Insights Logs:
Exception: Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Keyset does not exist
which obviously points to some sort of error with the certificate. It doesn't seem to be related to any user permissions or anything because those have not changed since we had it working on .NET Core 3.1. The certificate hasn't changed or moved. We can get it to work fine wehn debugging locally, which indicates the certificate is still valid and the user permissions are correct. The certificate is installed on the proper servers because we have other applications and code calling the same web service (albeit on a older version of Framework and not .NET Core). However, we get this error in our logs on the deployed function app, but not consistently. Sometimes the function will work, sometimes it will throw this error.
Since it wasn't consistent, my first thought is that maybe the lifetime scope I was registering it under was incorrect, so I changed it from Transient to Scoped and still appear to get the error. I could still try Singleton, but I'm wary if that's the correct solution and I've read online that a Singleton call to a WCF isn't the correct way. (I'm still a little uncertain as to when to use which scopes).
Since it's not a certificate issue and not a credentials issue, and we can get it to work sometimes but not all the time on the deployed function app, I'm out of ideas as to what could cause this outside of TRYING to change the scope to Singleton?
Can anyone help?