1

I'd like to export PFX (public + private part) to separate files (*.cer, *.key). PFX may (or may not) be password protected.

I've tried to achieve my goal, which is fine with certificate (public), but causes a problem with private key.

My bootastrap class is as below:

public class CertBootstrap
    {
        private readonly FileInfo CertificateFile;
        private readonly SecureString CertificatePassword;
        public bool HasPasswordBeenSet { get; private set; } = false;
        public X509Certificate2 Certificate { get; private set; }

        public CertBootstrap(FileInfo certificationFile, string password)
        {
            if(!certificationFile.Exists)
            {
                throw new FileNotFoundException(certificationFile.FullName);
            }

            CertificateFile = certificationFile;
            HasPasswordBeenSet = true;
            CertificatePassword = ConvertPassword(password);
        }

        public CertBootstrap(FileInfo certificationFile)
        {
            if(!certificationFile.Exists)
            {
                throw new FileNotFoundException(certificationFile.FullName);
            }

            CertificateFile = certificationFile;
            HasPasswordBeenSet = false;
        }

        public CertBootstrap(string certificationFullFileName, string password)
        {
            var certificateFile = new FileInfo(certificationFullFileName);
            if(certificateFile == null || !certificateFile.Exists)
            {
                throw new FileNotFoundException(certificationFullFileName);
            }

            CertificateFile = certificateFile;
            HasPasswordBeenSet = true;
            CertificatePassword = ConvertPassword(password);
        }

        public CertBootstrap(string certificationFullFileName)
        {
            var certificateFile = new FileInfo(certificationFullFileName);
            if(certificateFile == null || !certificateFile.Exists)
            {
                throw new FileNotFoundException(certificationFullFileName);
            }

            CertificateFile = certificateFile;
            HasPasswordBeenSet = false;
        }

        public bool VerifyPassword(string password)
        {
            try
            {
                byte[] fileContent = File.ReadAllBytes(CertificateFile.FullName);

                var certificate = new X509Certificate2(fileContent, password);
            }
            catch(CryptographicException ex)
            {
                if((ex.HResult & 0xFFFF) == 0x56)
                {
                    return false;
                }

                ;

                throw;
            }

            return true;
        }

        private static SecureString ConvertPassword(string password)
        {
            var secure = new SecureString();
            foreach(char c in password)
            {
                secure.AppendChar(c);
            }

            return secure;
        }

        public void LoadBootstrap()
        {
            LoadBootstrap(X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
        }

        public void LoadBootstrap(X509KeyStorageFlags keyStorageFlags)
        {
            this.Certificate = this.HasPasswordBeenSet ? new X509Certificate2(this.CertificateFile.FullName, this.CertificatePassword, keyStorageFlags) : new X509Certificate2(this.CertificateFile.FullName);
        }

        private static RSA ExportPrivateKeyCoreNet5Net6(RSA privateKey)
        {
            var password = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString());

            PbeParameters pPar = new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 100_000);

            try
            {
                byte[] tempG = privateKey.ExportEncryptedPkcs8PrivateKey(password, pPar);

                using(RSA exportRewriter = RSA.Create())
                {
                    exportRewriter.ExportParameters(true);
                    exportRewriter.ImportEncryptedPkcs8PrivateKey(password, tempG, out _);

                    return exportRewriter;
                }
            }
            catch(Exception e)
            {
                //catch this error
                string em = e.Message;

                throw;
            }
        }

        private static byte[] ExportPrivateKey(RSA privateKey)
        {
            try
            {
                return privateKey.ExportPkcs8PrivateKey();
            }
            catch(CryptographicException)
            {
            }

            return ExportPrivateKeyCoreNet5Net6(privateKey).ExportRSAPublicKey();
        }

        public byte[] GeneratePrivateKeyPem()
        {
            byte[] privateCertKeyBytes = ExportPrivateKey(this.Certificate.GetRSAPrivateKey());

            char[] newPemData = PemEncoding.Write("PRIVATE KEY", privateCertKeyBytes);

            return newPemData.Select(c => (byte)c).ToArray();
        }

        public byte[] GenerateCertificatePem()
        {
            var certData = Certificate.RawData;
            var newPemData = PemEncoding.Write("CERTIFICATE", certData);

            return newPemData.Select(c => (byte)c).ToArray();
        }

        public FileInfo SaveCertificate()
        {
            var newData = GenerateCertificatePem();

            var oldFile = Path.GetFileNameWithoutExtension(CertificateFile.FullName);
            var newCertPemFile = new FileInfo($@"{CertificateFile.DirectoryName}\{oldFile}.cer");

            return SaveNewCertFile(newCertPemFile, newData);
        }

        public FileInfo SavePrivateKey()
        {
            var newData = GeneratePrivateKeyPem();

            var oldFile = Path.GetFileNameWithoutExtension(CertificateFile.FullName);
            var newPrivateKeyPemFile = new FileInfo($@"{CertificateFile.DirectoryName}\{oldFile}.key");

            return SaveNewCertFile(newPrivateKeyPemFile, newData);
        }

        public FileInfo SaveNewCertFile(FileInfo newFile, byte[] data)
        {
            File.WriteAllBytes(newFile.FullName, data);

            return newFile;
        }
    }

With it, I'm trying to save private part:

// (...)
CertBootstrap certBootstrap = new CertBootstrap("simple.pfx");
certBootstrap.LoadBootstrap();

// (...)
certBootstrap.SavePrivateKey();

and it all ends up with exception: "The requested operation it not supported".

I found similar thread: Cannot export RSA private key parameters, the requested operation is not supported and tried to do the same (Export -> Import with password). But did not help.

Can You point me propper direction? Most probably I'm missing something, but am out of ideas for now.

Thank You in advance.

Michael
  • 21
  • 2
  • There's a lot of code shown that isn't relevant to your problem. There are also a few ancillary problems in the code. 1) Don't use SecureString. 2) Don't use PersistKeySet unless you're adding the cert to a persisted store. 3) ExportPrivateKey's try and fallback use different file formats. 4) For real, don't use SecureString. 5) Race conditions between File(Info).Exists and actually reading the data later. -- Try to write the code that matters into a simple Main() method, and if you still have a problem, update the question to present it more simply. – bartonjs May 23 '22 at 17:08

0 Answers0