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.