Certificate error when running in Docker container

We are trying out your SAML component for .Net Core and we get this exception when loading the certificate.

It works fine when running the code in Windows developer machine, but in a Docker container (running Linux) it throws this exception.

Certificate is read from database as byte array and converted into Base64 string.

Do you know a solution to this problem?

Is it possible to pass a byte array certificate or X509Certificate2 instance into IdP configuration?

Exception:

The X.509 certificate could not be loaded from the string. - error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error

at ComponentSpace.Saml2.Certificates.CertificateLoader.LoadCertificateFromStringAsync(String certificateString, String certificatePassword)
at ComponentSpace.Saml2.Certificates.AbstractCachedCertificateLoader.LoadCertificateFromStringAsync(String certificateString, String certificatePassword)
at ComponentSpace.Saml2.Certificates.CertificateManager.LoadCertificatesAsync(IList certificates, CertificateUse certificateUse)
at ComponentSpace.Saml2.Certificates.CertificateManager.GetPartnerIdentityProviderSignatureCertificatesAsync(String configurationID, String partnerIdentityProviderName)
at ComponentSpace.Saml2.SamlServiceProvider.GetPartnerProviderSignatureCertificatesAsync(Boolean precondition)
at ComponentSpace.Saml2.SamlServiceProvider.VerifySamlAssertionSignatureAsync(AssertionListItem assertionListItem)
at ComponentSpace.Saml2.SamlServiceProvider.GetSamlAssertionAsync(SamlResponse samlResponse)
at ComponentSpace.Saml2.SamlServiceProvider.ProcessSamlResponseAsync(XmlElement samlResponseElement)
at ComponentSpace.Saml2.SamlServiceProvider.ReceiveSsoAsync()

— Inner exception stack trace —
at Internal.Cryptography.Pal.CertificatePal.FromBlob(Byte[] rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
at System.Security.Cryptography.X509Certificates.X509Certificate…ctor(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)
at System.Security.Cryptography.X509Certificates.X509Certificate2…ctor(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)
at ComponentSpace.Saml2.Certificates.CertificateLoader.LoadCertificateFromStringAsync(String certificateString, String certificatePassword)

We use the following code to load the certificate string.


new X509Certificate2(
Convert.FromBase64String(certificateString),
certificatePassword,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);


Please try running this code in a small test app to confirm you get the same issue outside of our library.
I also suggest comparing the base-64 certificate string on the Windows and Linux systems just in case something odd is happening when you convert from the byte array to the base-64 string.
You could implement the ICertificateManager interface and you would be free to construct certificates from byte arrays etc but I think it would be better to resolve this issue as implementing ICertificateManager would be more work.
Let us know what you find.

Have you find a fix for this? I’m having the same problem. Running the project on a docker an fails however on Windows this was working fine.

We never heard back from the original poster.

Can you confirm that the base-64 encoded certificate string is correct?

Can you try the above code (ie X509Certificate2 constructor etc) in your app to confirm you see the same issue and it isn’t specific to the SAML library?

Found the solution for my issue.
Posting this in case anyone makes the same foolish mistake. In our workflow certs are uploaded via rest api, so they need to be encoded in base64. Those values are stored in a DB to then be used in the saml assertion. The X509Certificate2 in Windows seems to support encoded certs, but not in linux. The direct migration to Docker did not work.

//this used to work in Windows but not in Linux
var x509Certificate2 = new X509Certificate2(Convert.FromBase64String(CertificateBase64), password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);[

//this is the workaround
byte[] data = Convert.FromBase64String(CertificateBase64);
string decodedString = Encoding.UTF8.GetString(data);
var x509Certificate2 = new X509Certificate2(Convert.FromBase64String(decodedString), password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);

I’m not sure I understand your code. The Encoding.UTF8.GetString(data) isn’t going to be a base-64 string. If I run this code on Windows, Convert.FromBase64String(decodedString) throws a FormatException.

Let’s say we have the following cert 1234/+abc== and we need to send it via rest api. You have to encode it => MTIzNC8rYWJjPT0= before posting the data.
In windows this used to work
var x509Certificate2 = new X509Certificate2(“MTIzNC8rYWJjPT0=”, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);

I’m not sure what the conversion is that you’re doing.

Also, the X509Certificate2 constructor whose first parameter is a string specifies the certificate file name. I’m not sure how this code used to work.

https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.-ctor?view=net-5.0#System_Security_Cryptography_X509Certificates_X509Certificate2__ctor_System_String_System_String_System_Security_Cryptography_X509Certificates_X509KeyStorageFlags_

[quote]
andreasn - 4/2/2019
We are trying out your SAML component for .Net Core and we get this exception when loading the certificate.

It works fine when running the code in Windows developer machine, but in a Docker container (running Linux) it throws this exception.

Certificate is read from database as byte array and converted into Base64 string.

Do you know a solution to this problem?

Is it possible to pass a byte array certificate or X509Certificate2 instance into IdP configuration?

Exception:

The X.509 certificate could not be loaded from the string. - error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error

at ComponentSpace.Saml2.Certificates.CertificateLoader.LoadCertificateFromStringAsync(String certificateString, String certificatePassword)
at ComponentSpace.Saml2.Certificates.AbstractCachedCertificateLoader.LoadCertificateFromStringAsync(String certificateString, String certificatePassword)
at ComponentSpace.Saml2.Certificates.CertificateManager.LoadCertificatesAsync(IList certificates, CertificateUse certificateUse)
at ComponentSpace.Saml2.Certificates.CertificateManager.GetPartnerIdentityProviderSignatureCertificatesAsync(String configurationID, String partnerIdentityProviderName)
at ComponentSpace.Saml2.SamlServiceProvider.GetPartnerProviderSignatureCertificatesAsync(Boolean precondition)
at ComponentSpace.Saml2.SamlServiceProvider.VerifySamlAssertionSignatureAsync(AssertionListItem assertionListItem)
at ComponentSpace.Saml2.SamlServiceProvider.GetSamlAssertionAsync(SamlResponse samlResponse)
at ComponentSpace.Saml2.SamlServiceProvider.ProcessSamlResponseAsync(XmlElement samlResponseElement)
at ComponentSpace.Saml2.SamlServiceProvider.ReceiveSsoAsync()

--- Inner exception stack trace ---
at Internal.Cryptography.Pal.CertificatePal.FromBlob(Byte[] rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)
at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)
at ComponentSpace.Saml2.Certificates.CertificateLoader.LoadCertificateFromStringAsync(String certificateString, String certificatePassword)
[/quote]

@andreasn : How do you store certificate in Database? Do you use nvarchar column type? Also do insert cert with -----BEGIN CERTIFICATE----- ....-----END CERTIFICATE----- ?
If we insert cert with -----END CERTIFICATE----- this lines, it will fail here byte[] bytes = Convert.FromBase64String(item.Certificate);

Just store the base-64 string. This can be stored as an nvarchar.

Another option is to store the base-64 string directly in the SAML configuration (eg appsettings.json). For more information, refer to our Configuration Guide and Certificate Guide.

https://www.componentspace.com/Forums/8234/Configuration-Guide

https://www.componentspace.com/Forums/8238/Certificate-Guide

[quote]
ComponentSpace - 4/2/2019
We use the following code to load the certificate string.


new X509Certificate2(
Convert.FromBase64String(certificateString),
certificatePassword,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);


Please try running this code in a small test app to confirm you get the same issue outside of our library.
I also suggest comparing the base-64 certificate string on the Windows and Linux systems just in case something odd is happening when you convert from the byte array to the base-64 string.
You could implement the ICertificateManager interface and you would be free to construct certificates from byte arrays etc but I think it would be better to resolve this issue as implementing ICertificateManager would be more work.
Let us know what you find.
[/quote]

I use ICertificateLoader to convert the public key certificate from byte[] to Base64, but its not working on the docker container. Please find the error below,

[quote]request.ComponentSpace.Saml2.Exceptions.SamlCertificateException: The X.509 certificate could not be loaded from the byte array. ---> Interop+Crypto+OpenSslCryptographicException: error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error
at ComponentSpace.Saml2.Certificates.CertificateLoader.LoadCertificateComponentSpace.Saml2.Certificates.CertificateLoader.LoadCertificate
at ComponentSpace.Saml2.Certificates.CertificateLoader.LoadCertificateFromBytesAsyncComponentSpace.Saml2.Certificates.CertificateLoader.LoadCertificateFromBytesAsync
at ComponentSpace.Saml2.Certificates.CertificateLoader.LoadCertificateFromBytesAsyncComponentSpace.Saml2.Certificates.CertificateLoader.LoadCertificateFromBytesAsync[/quote]
Please find the code below,

public class CertificateHelper : ICertificateHelper
{
private readonly ICertificateLoader certificateLoader;

public CertificateHelper(ICertificateLoader certificateLoader)
{
this.certificateLoader = certificateLoader;
}

public async Task GetCertificateBase64StringAsync(IFormFile signingCertificate, string certificatePassword)
{
X509Certificate2 x509Certificate;
X509ContentType certType = Path.GetExtension(signingCertificate.FileName).Equals(".pfx", StringComparison.OrdinalIgnoreCase) ? X509ContentType.Pfx : X509ContentType.Cert;

using (MemoryStream memoryStream = new MemoryStream())
{
signingCertificate.CopyTo(memoryStream);
x509Certificate = await certificateLoader.LoadCertificateFromBytesAsync(memoryStream.ToArray(), certificatePassword);
}
return Convert.ToBase64String(x509Certificate.Export(certType, certificatePassword), Base64FormattingOptions.None);
}
}


The alternate working solution is to use BouncyCastle.X509
var bouncy = new X509CertificateParser();
var bcert = bouncy.ReadCertificate(fileBytes);

x509Certificate = new X509Certificate2(bcert.GetEncoded(), certificatePassword ?? string.Empty,
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);


Can you suggest?

Thanks in advance.
Jagath

Hi Jagath,

The ICertificateLoader.LoadCertificateFromBytesAsync calls:

new X509Certificate2(certificateBytes, certificatePassword, x509KeyStorageFlags);

Do you get the same issue if you use the X509Certificate2 constructor directly rather than calling through our interface?

This is the same as the BouncyCastle code (ie same X509Certificate2 constructor being called) except for the the way the bytes are being loaded from the file.

I suggest saving the signingCertificate.CopyTo bytes to a file and compare this with the contents of the original uploaded file to see whether there are any differences.