I'm having an issue with regards to a combination of Xamarin and X509 certificates.
We have an app that needs to generate a .PFX file using the BouncyCastle library. The file is generated successfully and decoded in a simple .NET console app without error, to communicate with an MQTT message broker on AWS IoT. Then we moved the code over to Xamarin. After some code changes, we managed to get the Android version working and communicating with the MQTT message broker. However, it fails when running on iOS, specifically when a new X509Certificate2 object is created using the path to the .PFX file and the appropriate password, using this code:
X509Certificate2 clientCertPfx = new X509Certificate2(Path.Combine(folderPath, certificatePathPfx), password);
It fails with the error message: CryptographicException: Unable to decode certificate
This error only happens with a Xamarin.iOS build; the Xamarin.Android build runs as expected. We followed the stack trace to the following code sample that match up with the error message. It would seem like the .PFX file is not being parsed / read properly. I'm wondering if there's anyone out there who knows whether or not there's a way around this error. Is this a shortcoming in the Mono implementation of the X509Certificate2 methods? Is there a chance that the BouncyCastle Pcks12Builder exported the .PFX file incorrectly or something?
Any help would be appreciated, and I'm happy to offer up any additional code as needed.
EDIT 1 Here's the code I use to generate the .PFX file.
var pfxStore = new Pkcs12StoreBuilder()
.SetUseDerEncoding(true)
.Build();
StreamReader reader = File.OpenText(cert_file_pem); // signed PEM certificate file
var pemReader = new PemReader(reader);
List<X509CertificateEntry> chain = new List<X509CertificateEntry>();
AsymmetricCipherKeyPair privKey = null;
object o;
while ((o = pemReader.ReadObject()) != null)
{
if (o is Org.BouncyCastle.X509.X509Certificate)
{
chain.Add(new X509CertificateEntry((Org.BouncyCastle.X509.X509Certificate)o));
}
else if (o is AsymmetricCipherKeyPair)
{
privKey = (AsymmetricCipherKeyPair)o;
}
}
if (privKey == null)
{
using (StreamReader privKeyReader = File.OpenText(key_file)) // private key file
{
privKey = (AsymmetricCipherKeyPair)new PemReader(privKeyReader).ReadObject();
}
}
pfxStore.SetKeyEntry("convert", new AsymmetricKeyEntry(privKey.Private), chain.ToArray());
MemoryStream pfxMemoryStream = new MemoryStream();
pfxStore.Save(pfxMemoryStream, thingName.ToCharArray(), new SecureRandom());
var result = Pkcs12Utilities.ConvertToDefiniteLength(pfxMemoryStream.ToArray(), password.ToCharArray());
FileStream pfxFileStream = File.Create(certificatePathPfx);
pfxFileStream.Write(result);
pfxFileStream.Close();
pfxMemoryStream.Close();
EDIT 2 I've also noticed some strange behaviour with regards to the following code:
X509Certificate2 clientCertPfx = new X509Certificate2(certificatePathPfx, password);
Console.WriteLine("Cert type for PFX doc (data): " + X509Certificate2.GetCertContentType(clientCertPfx.RawData));
Console.WriteLine("Cert type for PFX doc (path): " + X509Certificate2.GetCertContentType(certificatePathPfx));
When running this piece of code in a .NET console app, I get the following output:
Cert type for PFX doc (data): Cert
Cert type for PFX doc (path): Pfx
It seems like the X509Certificate2.GetCertContentType()
function picks up different content types based on whether the function is called with the path of the file / file name, or the raw file data.