Exception Using String Certificate

Hello,

I am loading the .pfx file content from a database and converting it into a string to be added to SAML IDP configuration programmatically. I am using code similar to the following where certificateContent is a byte array and password is a string:

var cert =
new X509Certificate2(certificateContent, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
var exported = cert.Export(X509ContentType.Pfx, password);
certAsString = Convert.ToBase64String(exported);

This works fine in my local PC dev environment, but throws an “Access Denied” cryptographic exception on a web server. We are loading certificates in other areas of the app using X509KeyStorageFlags.MachineKeySet flag and that works fine, but we are not exporting and converting to a base-64 string in those instances. The exception occurs when we try to create the X509Certificate2 object in the code above. I have tried it with and without the X509KeyStorageFlags.PersistKeySet flag included but does not change the result. We are using a Windows Server 2012 r2 server with IIS 8.5 as the web server.

Is there a better way that I should be converting this to a string for use in a SAML configuration? Any help is greatly appreciated.

Thanks!

Just to confirm, the above code is purely for converting the PFX file into a certificate string. Is that correct?
As an FYI, the following PowerShell command may be used to do the same thing.


$bytes = [System.IO.File]::ReadAllBytes(“idp.pfx”)
[System.Convert]::ToBase64String($bytes)


Does your SAML configuration include the password to access the private key?


“LocalCertificates”: [
{
“String”: “MIIC/jCCAeagAwIBAgIQ…”,
“Password”: “password”
}
]


If there’s still an issue, please enable SAML trace and send the log file as an email attachment to support@componentspace.com.
https://www.componentspace.com/Forums/7936/Enabling-SAML-Trace


That is correct, this code is strictly used to get the certificate data as a base-64 encoded string. It starts out as a byte array that is pulled from a database. I am then adding it to my IDP configuration like so:

LocalIdentityProviderConfiguration idpConfiguration = new LocalIdentityProviderConfiguration()
{
Description = “My IDP Configuration”,
Name = "<a href=“https://myidpconfig.name.com/SAML2” “>https://myidpconfig.name.com/SAML2
};

// Do some other stuff here…

idpConfiguration.LocalCertificates = new List()
{
new Certificate()
{
String = certificate,
Password = “my cert password”
}
};


I am able to work with the certificate using Powershell, etc. and I know that the password that I am providing in the config is correct.

I have enabled SAML Trace as described in the link that you provided and it does create the log file, but there is not much in it so I don’t think that it is getting past the conversion of the Certificate to a base-64 string to do anything with the ComponentSpace.Saml2 component where it would log things. Here is what is in the log file after three attempts to perform an SSO to the SP:
2018-04-25 10:14:43.289 -05:00 [Information] ComponentSpace.Saml2, Version=2.0.4.0, Culture=neutral, PublicKeyToken=null, .NET Core build, Licensed.
2018-04-25 10:14:43.539 -05:00 [Information] CLR: .NET Framework 4.7.2563.0, OS: Microsoft Windows 6.3.9600 , Culture: English (United States)

Thanks

There should be more trace entries in the log file.
Please double check the file permissions to ensure your application’s process can write to the file.
Often when only the first two entries appear it’s a file permissions issue.
You could give the “everyone” group write permission to the file as a temporary measure.
What’s the exception and stack trace you’re receiving ie. Exception.ToString()?
In your original post you indicated your code to create the base-64 encoded certificate string is throwing an access denied exception.
Just to confirm, is it your code to create the certificate string or the SAML code that attempts to load the certificate string from the configuration which is throwing the exception?
If it’s your code throwing the exception, you may not have permission to access the private key.
Please refer to the Certificate File Permissions section of our Certificate Guide.
https://www.componentspace.com/Forums/8238/Certificate-Guide

This is how I have configured SAML TRACE in my application:
The Logging section in appSettings.json…

“Logging”: {
“IncludeScopes”: false,
“LogLevel”: {
“Default”: “Verbose”
}
}


The logging configuration in BuildWebHost…

.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection(“Logging”));
logging.AddConsole();
logging.AddDebug();
// Enable SAML TRACE for ComponentSpace:
logging.AddSerilog(new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.RollingFile(samlLogPath + “/saml-{Date}.log”)
.Filter.ByIncludingOnly(Matching.FromSource(“ComponentSpace.Saml2”))
.CreateLogger());
})

I am not sure if this is the correct way to get “everything” logged (verbose). Please advise on that…
I did notice that on my local PC running in debug from Visual Studio that I get just the two entries in the saml log even when the whole SSO process to our service providers works. I am an administrator there and have other logs that get written in the same location, so not sure what is happening there.
The following is the stack trace that I get when the issue occurs on the web server:
System.Security.Cryptography.CryptographicException: Access denied.

at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr)
at System.Security.Cryptography.X509Certificates.X509Utils._LoadCertFromBlob(Byte[] rawData, IntPtr password, UInt32 dwFlags, Boolean persistKeySet, SafeCertContextHandle& pCertCtx)
at System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[] rawData, Object password, X509KeyStorageFlags keyStorageFlags)
at System.Security.Cryptography.X509Certificates.X509Certificate2…ctor(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)
at ABC.Web.Controllers.SsoController.d__26.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at ABC.Web.Controllers.SsoController.d__25.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at ABC.Web.Controllers.SsoController.d__17.MoveNext()

It is happening in the X509Certificate ctor call, so it is in my code, not the code to add the cert string to the SAML configuration. That code works once the cert string is created on my local PC. I’ll take a look at the certificate permissions in the guide.
Thanks for your help!

Your logging configuration and code looks fine so I’m not sure what the issue is.
The Debug logging level is sufficient but Verbose will work as well.
I suggest double checking the file permissions of the log file.
I think the “Access denied” issue you’re seeing can be resolved as described in the Certificate Guide.
Let me know how you go.


I have not been able to figure out the issue with the log file yet, but did resolve the issue with the “Access Denied” exception.
I was creating my certs on Windows 10 using New-SelfSignedCertificate in PowerShell. There is an issue with the default provider used for key storage by New-SelfSignedCertificate and the X509Certificate2 class. The class is not able to open the private key container if it is using the default key storage provider… There is more info in the links presented in this StackOverflow post.

The solution to this is to use a different -Provider in New-SelfSignedCertificate as mentioned in the Certificates Guide:
“Note that to generate SHA-256, SHA-384 and SHA-512 XML signatures, the Microsoft Enhanced RSA and AES Cryptographic Provider must be specified.”

Once I used -Provider “Microsoft Enhanced RSA and AES Cryptographic Provider” in the New-SelfSignedCertificate command and exported that cert and loaded it in the database for my site, I was able to get it open using code like this:

using (X509Certificate2 cert = new X509Certificate2(certificateContent, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable))
{
var exported = cert.Export(X509ContentType.Pfx, password);
certAsString = Convert.ToBase64String(exported);
}


(Essentially the same as my original code with a using wrap to handle the dispose.)

Thanks for the guidance!

Thank you for the update. I’m glad you got it working.