As Benny mentioned, this is thread quite old, but I was in the same situation, that I could not find a really satisfying solution to first, convert a .cer file to a .pem file, and secondly, extract (in my case) the old subject hash, which is necessary for android. Finally the best solution I could find was by using the BouncyCastle nuget package. Hence the following code is based on that library.
using Org.BouncyCastle.X509;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
namespace CertificateTest
{
public class CertificateService
{
private readonly X509Certificate certificate;
public string AsPem
{
get
{
var base64Pem = Convert.ToBase64String(certificate.GetEncoded());
var wrappedPem = string.Join("\n", SplitByLength(base64Pem, 64));
return $"-----BEGIN CERTIFICATE-----\n{wrappedPem}\n-----END CERTIFICATE-----";
}
}
public string SubjectHash => CreateHash(certificate.SubjectDN.GetDerEncoded(), SHA1.Create());
public string SubjectHashOld => CreateHash(certificate.SubjectDN.GetDerEncoded(), MD5.Create());
public CertificateService(string certificateFilePath)
{
certificate = new X509CertificateParser().ReadCertificate(File.ReadAllBytes(certificateFilePath));
}
// hashing based on https://github.com/openssl/openssl/blob/master/crypto/x509/x509_cmp.c
private string CreateHash(byte[] subjectName, HashAlgorithm hashAlgorithm)
{
var hashBytes = hashAlgorithm.ComputeHash(subjectName);
ulong ret = (
((ulong)hashBytes[0]) |
((ulong)hashBytes[1] << 8) |
((ulong)hashBytes[2] << 16) |
((ulong)hashBytes[3] << 24)
) & 0xffffffff;
return ret.ToString("X2").PadLeft(8, '0').ToLower();
}
// Taken from: https://stackoverflow.com/a/3008775/773339
private IEnumerable<string> SplitByLength(string str, int maxLength)
{
for (var index = 0; index < str.Length; index += maxLength)
{
yield return str.Substring(index, Math.Min(maxLength, str.Length - index));
}
}
}
}
Usage is quite simple:
private static void Main(string[] args)
{
var service = new CertificateService("my-certificate.pem"); // new CertificateService("my-certificate.cer");
Debug.WriteLine(service.AsPem);
Debug.WriteLine(service.SubjectHash);
Debug.WriteLine(service.SubjectHashOld);
}
As you can see, some of the code (e.g. the hashing) I could already find in threads like this. For me the real break through was using the BouncyCastle library instead of the built-in classes, and completely loading the certificate with it and extracting all information from there. Manual parsing and otherwise encoding the SubjectDN lead me nowhere.
Since Convert.ToBase64String
cannot split lines after 64 characters itself, this is done manually here by using the referenced code from stack overflow.