I read some posts (that don't exist anymore) and came up with the following code that generates a PFX certificate. It works fine to the part of creating this self-signed certificate.
I'm trying to expand this to crate a self-signed certificate and from that one, create it's "childs". I tryed many things but none of then actually export the certificate with it's chain as result.
The current code get's to a point of exporting a PFX with a containing CA and importing it would include both certificates, but not associate then with each other.
It's kind of a long code, but the action should work on the last "Create" funcion of it.
using Sys = global::System;
using SysInterop = global::System.Runtime.InteropServices;
using SysCry509 = global::System.Security.Cryptography.X509Certificates;
using MdPFX = global::PFX;
public static class PFX
{
#region NativeCode
private const string DLL_Kernel = "kernel32.dll";
private const string DLL_AdvApi = "AdvApi32.dll";
private const string DLL_Crypt = "Crypt32.dll";
[SysInterop.StructLayout(SysInterop.LayoutKind.Sequential)] private struct SystemTime
{
public short Year;
public short Month;
public short DayOfWeek;
public short Day;
public short Hour;
public short Minute;
public short Second;
public short Milliseconds;
}
[SysInterop.StructLayout(SysInterop.LayoutKind.Sequential)] private struct CryptoApiBlob
{
public int DataLength;
public Sys.IntPtr Data;
public CryptoApiBlob(int dataLength, Sys.IntPtr data)
{
this.DataLength = dataLength;
this.Data = data;
}
}
[SysInterop.StructLayout(SysInterop.LayoutKind.Sequential)] private struct CryptKeyProviderInformation
{
[SysInterop.MarshalAs(SysInterop.UnmanagedType.LPWStr)] public string ContainerName;
[SysInterop.MarshalAs(SysInterop.UnmanagedType.LPWStr)] public string ProviderName;
public int ProviderType;
public int Flags;
public int ProviderParameterCount;
public Sys.IntPtr ProviderParameters;
public int KeySpec;
}
[SysInterop.DllImport(MdPFX.DLL_Kernel, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool FileTimeToSystemTime([SysInterop.In] ref long fileTime, out MdPFX.SystemTime systemTime);
[SysInterop.DllImport(MdPFX.DLL_AdvApi, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CryptAcquireContextW(out Sys.IntPtr providerContext, [SysInterop.MarshalAs(SysInterop.UnmanagedType.LPWStr)] string container, [SysInterop.MarshalAs(SysInterop.UnmanagedType.LPWStr)] string provider, int providerType, int flags);
[SysInterop.DllImport(MdPFX.DLL_AdvApi, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CryptReleaseContext(Sys.IntPtr providerContext, int flags);
[SysInterop.DllImport(MdPFX.DLL_AdvApi, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CryptGenKey(Sys.IntPtr providerContext, int algorithmId, int flags, out Sys.IntPtr cryptKeyHandle);
[SysInterop.DllImport(MdPFX.DLL_AdvApi, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CryptDestroyKey(Sys.IntPtr cryptKeyHandle);
[SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CertStrToNameW(int certificateEncodingType, Sys.IntPtr x500, int strType, Sys.IntPtr reserved, [SysInterop.MarshalAs(SysInterop.UnmanagedType.LPArray)] [SysInterop.Out] byte[] encoded, ref int encodedLength, out Sys.IntPtr errorString);
[SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)] private static extern Sys.IntPtr CertCreateSelfSignCertificate(Sys.IntPtr providerHandle, [SysInterop.In] ref MdPFX.CryptoApiBlob subjectIssuerBlob, int flags, [SysInterop.In] ref MdPFX.CryptKeyProviderInformation keyProviderInformation, Sys.IntPtr signatureAlgorithm, [SysInterop.In] ref MdPFX.SystemTime startTime, [SysInterop.In] ref MdPFX.SystemTime endTime, Sys.IntPtr extensions);
[SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CertFreeCertificateContext(Sys.IntPtr certificateContext);
[SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)] private static extern Sys.IntPtr CertOpenStore([SysInterop.MarshalAs(SysInterop.UnmanagedType.LPStr)] string storeProvider, int messageAndCertificateEncodingType, Sys.IntPtr cryptProvHandle, int flags, Sys.IntPtr parameters);
[SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CertCloseStore(Sys.IntPtr certificateStoreHandle, int flags);
[SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CertAddCertificateContextToStore(Sys.IntPtr certificateStoreHandle, Sys.IntPtr certificateContext, int addDisposition, out Sys.IntPtr storeContextPtr);
[SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool CertSetCertificateContextProperty(Sys.IntPtr certificateContext, int propertyId, int flags, [SysInterop.In] ref MdPFX.CryptKeyProviderInformation data);
[SysInterop.DllImport(MdPFX.DLL_Crypt, SetLastError = true, ExactSpelling = true)][return: SysInterop.MarshalAs(SysInterop.UnmanagedType.Bool)] private static extern bool PFXExportCertStoreEx(Sys.IntPtr certificateStoreHandle, ref MdPFX.CryptoApiBlob pfxBlob, Sys.IntPtr password, Sys.IntPtr reserved, int flags);
private static void Check(bool nativeCallSucceeded) { if (!nativeCallSucceeded) { SysInterop.Marshal.ThrowExceptionForHR(SysInterop.Marshal.GetHRForLastWin32Error()); } }
private static MdPFX.SystemTime ToSystemTime(Sys.DateTime dateTime)
{
long fileTime = dateTime.ToFileTime();
MdPFX.SystemTime systemTime = default(MdPFX.SystemTime);
MdPFX.Check(MdPFX.FileTimeToSystemTime(ref fileTime, out systemTime));
return systemTime;
}
#endregion
public static byte[] Create(string commonName, Sys.DateTime startTime, Sys.DateTime endTime, Sys.Security.SecureString password)
{
byte[] pfxData;
if (commonName == null) { commonName = string.Empty; }
MdPFX.SystemTime startSystemTime = MdPFX.ToSystemTime(startTime);
MdPFX.SystemTime endSystemTime = MdPFX.ToSystemTime(endTime);
string containerName = Sys.Guid.NewGuid().ToString();
SysInterop.GCHandle dataHandle = default(SysInterop.GCHandle);
Sys.IntPtr providerContext = Sys.IntPtr.Zero;
Sys.IntPtr cryptKey = Sys.IntPtr.Zero;
Sys.IntPtr certContext = Sys.IntPtr.Zero;
Sys.IntPtr certStore = Sys.IntPtr.Zero;
Sys.IntPtr storeCertContext = Sys.IntPtr.Zero;
Sys.IntPtr passwordPtr = Sys.IntPtr.Zero;
Sys.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
try
{
MdPFX.Check(MdPFX.CryptAcquireContextW(out providerContext, containerName, null, 1, 8));
MdPFX.Check(MdPFX.CryptGenKey(providerContext, 1, 1, out cryptKey));
Sys.IntPtr errorStringPtr = Sys.IntPtr.Zero;
int nameDataLength = 0;
byte[] nameData = null;
dataHandle = SysInterop.GCHandle.Alloc(commonName, SysInterop.GCHandleType.Pinned);
if (!MdPFX.CertStrToNameW(0x00010001, dataHandle.AddrOfPinnedObject(), 3, Sys.IntPtr.Zero, null, ref nameDataLength, out errorStringPtr)) { throw new Sys.ArgumentException(SysInterop.Marshal.PtrToStringUni(errorStringPtr)); }
nameData = new byte[nameDataLength];
if (!MdPFX.CertStrToNameW(0x00010001, dataHandle.AddrOfPinnedObject(), 3, Sys.IntPtr.Zero, nameData, ref nameDataLength, out errorStringPtr)) { throw new Sys.ArgumentException(SysInterop.Marshal.PtrToStringUni(errorStringPtr)); }
dataHandle.Free();
dataHandle = SysInterop.GCHandle.Alloc(nameData, SysInterop.GCHandleType.Pinned);
MdPFX.CryptoApiBlob nameBlob = new MdPFX.CryptoApiBlob(nameData.Length, dataHandle.AddrOfPinnedObject());
MdPFX.CryptKeyProviderInformation kpi = new MdPFX.CryptKeyProviderInformation();
kpi.ContainerName = containerName;
kpi.ProviderType = 1;
kpi.KeySpec = 1;
certContext = MdPFX.CertCreateSelfSignCertificate(providerContext, ref nameBlob, 0, ref kpi, Sys.IntPtr.Zero, ref startSystemTime, ref endSystemTime, Sys.IntPtr.Zero);
MdPFX.Check(certContext != Sys.IntPtr.Zero);
dataHandle.Free();
certStore = MdPFX.CertOpenStore("Memory", 0, Sys.IntPtr.Zero, 0x2000, Sys.IntPtr.Zero);
MdPFX.Check(certStore != Sys.IntPtr.Zero);
MdPFX.Check(MdPFX.CertAddCertificateContextToStore(certStore, certContext, 1, out storeCertContext));
MdPFX.CertSetCertificateContextProperty(storeCertContext, 2, 0, ref kpi);
if (password != null) { passwordPtr = SysInterop.Marshal.SecureStringToCoTaskMemUnicode(password); }
MdPFX.CryptoApiBlob pfxBlob = new MdPFX.CryptoApiBlob();
MdPFX.Check(MdPFX.PFXExportCertStoreEx(certStore, ref pfxBlob, passwordPtr, Sys.IntPtr.Zero, 7));
pfxData = new byte[pfxBlob.DataLength];
dataHandle = SysInterop.GCHandle.Alloc(pfxData, SysInterop.GCHandleType.Pinned);
pfxBlob.Data = dataHandle.AddrOfPinnedObject();
MdPFX.Check(MdPFX.PFXExportCertStoreEx(certStore, ref pfxBlob, passwordPtr, Sys.IntPtr.Zero, 7));
dataHandle.Free();
}
finally
{
if (passwordPtr != Sys.IntPtr.Zero) { SysInterop.Marshal.ZeroFreeCoTaskMemUnicode(passwordPtr); }
if (dataHandle.IsAllocated) { dataHandle.Free(); }
if (certContext != Sys.IntPtr.Zero) { MdPFX.CertFreeCertificateContext(certContext); }
if (storeCertContext != Sys.IntPtr.Zero) { MdPFX.CertFreeCertificateContext(storeCertContext); }
if (certStore != Sys.IntPtr.Zero) { MdPFX.CertCloseStore(certStore, 0); }
if (cryptKey != Sys.IntPtr.Zero) { MdPFX.CryptDestroyKey(cryptKey); }
if (providerContext != Sys.IntPtr.Zero)
{
MdPFX.CryptReleaseContext(providerContext, 0);
MdPFX.CryptAcquireContextW(out providerContext, containerName, null, 1, 0x10);
}
}
return pfxData;
}
public static Sys.Security.SecureString CreateSecurePassword(string insecurePassword)
{
if (!string.IsNullOrEmpty(insecurePassword))
{
Sys.Security.SecureString password = new Sys.Security.SecureString();
foreach (char ch in insecurePassword) { password.AppendChar(ch); }
password.MakeReadOnly();
return password;
} else { return null; }
}
public static byte[] Create(string commonName, Sys.DateTime startTime, Sys.DateTime endTime, string insecurePassword)
{
byte[] pfxData;
Sys.Security.SecureString password = null;
try
{
password = MdPFX.CreateSecurePassword(insecurePassword);
pfxData = MdPFX.Create(commonName, startTime, endTime, password);
} finally { if (password != null) { password.Dispose(); } }
return pfxData;
}
public static byte[] Create(string commonName, int YearsValid, Sys.Security.SecureString password)
{
if (!commonName.StartsWith("CN=", Sys.StringComparison.OrdinalIgnoreCase)) { commonName = "CN=" + commonName; }
return MdPFX.Create(commonName, Sys.DateTime.Now, Sys.DateTime.Now.AddYears(YearsValid), password);
}
public static void Create(Sys.IO.Stream save, string commonName, int YearsValid, Sys.Security.SecureString password)
{
byte[] certificateData = MdPFX.Create(commonName, 5, password);
using (Sys.IO.BinaryWriter binWriter = new Sys.IO.BinaryWriter(save))
{
binWriter.Write(certificateData);
binWriter.Flush();
}
}
public static byte[] Create(string commonName, Sys.DateTime startTime, Sys.DateTime endTime) { return MdPFX.Create(commonName, startTime, endTime, (Sys.Security.SecureString)null); }
public static byte[] Create(string commonName, int YearsValid, string insecurePassword) { using (Sys.Security.SecureString password = MdPFX.CreateSecurePassword(insecurePassword)) { return MdPFX.Create(commonName, YearsValid, password); } }
public static void Create(Sys.IO.Stream save, string commonName, int YearsValid, string insecurePassword) { using (Sys.Security.SecureString password = MdPFX.CreateSecurePassword(insecurePassword)) { MdPFX.Create(save, commonName, YearsValid, password); } }
public static void Create(string savePath, string commonName, int YearsValid, Sys.Security.SecureString password) { using (Sys.IO.FileStream fStream = Sys.IO.File.Open(savePath, Sys.IO.FileMode.Create)) { MdPFX.Create(fStream, commonName, YearsValid, password); } }
public static void Create(string savePath, string commonName, int YearsValid, string insecurePassword) { using (Sys.Security.SecureString password = MdPFX.CreateSecurePassword(insecurePassword)) { using (Sys.IO.FileStream fStream = Sys.IO.File.Open(savePath, Sys.IO.FileMode.Create)) { MdPFX.Create(fStream, commonName, YearsValid, password); } } }
public static byte[] Create(SysCry509.X509Certificate2 certificate, string insecurePassword, SysCry509.X509Certificate2 signingCert, SysCry509.X509Certificate2Collection chain = null)
{
SysCry509.X509Certificate2Collection col = new SysCry509.X509Certificate2Collection(certificate);
if (chain != null) { col.AddRange(chain); }
if (signingCert != null)
{
SysCry509.X509Certificate2 sigCertNoPK = new SysCry509.X509Certificate2(signingCert.Export(SysCry509.X509ContentType.Cert));
col.Add(sigCertNoPK);
}
return col.Export(SysCry509.X509ContentType.Pfx, insecurePassword);
}
public static byte[] Create(string commonName, string insecurePassword, int YearsValid, SysCry509.X509Certificate2 signingCert, SysCry509.X509Certificate2Collection chain = null)
{
SysCry509.X509Certificate2 certificate = new SysCry509.X509Certificate2();
certificate.Import(MdPFX.Create(commonName, YearsValid, insecurePassword), insecurePassword, (SysCry509.X509KeyStorageFlags.PersistKeySet | SysCry509.X509KeyStorageFlags.Exportable));
return MdPFX.Create(certificate, insecurePassword, signingCert, chain: chain);
}
public static SysCry509.X509Certificate2 Load(byte[] rawData, string insecurePassword)
{
try
{
SysCry509.X509Certificate2 rCert = new SysCry509.X509Certificate2();
rCert.Import(rawData, insecurePassword, (SysCry509.X509KeyStorageFlags.PersistKeySet | SysCry509.X509KeyStorageFlags.Exportable));
return rCert;
} catch { return null; }
}
public static byte[] Create(string commonName, string insecurePassword, int YearsValid, bool Signed, SysCry509.X509Certificate2Collection chain = null)
{
if (Signed)
{
SysCry509.X509Store store = new SysCry509.X509Store(typeof(MdPFX).FullName, SysCry509.StoreLocation.LocalMachine);
store.Open(SysCry509.OpenFlags.ReadWrite);
const string rCertN = "A.Root.Cert.Name";
SysCry509.X509Certificate2 rCert = null;
if (store.Certificates.Count > 0) { foreach (SysCry509.X509Certificate2 c in store.Certificates) { if (c.SubjectName.Name == rCertN) { rCert = c; break; } } }
if (rCert == null)
{
rCert = MdPFX.Load(MdPFX.Create(rCertN, 10, "A.Root.Cert.Pass"), "A.Root.Cert.Pass");
store.Add(rCert);
}
store.Close();
return MdPFX.Create(commonName, insecurePassword, YearsValid, rCert, chain: chain);
} else { return MdPFX.Create(commonName, YearsValid, insecurePassword); }
}
}
Then, if i run like this, it's not giving the certificate with chain to the "CA" created.
internal static class Program
{
internal static void Main()
{
Sys.IO.File.WriteAllBytes("C:\\Users\\User\\Desktop\\cert.pfx", MdPFX.Create("My.Name", "Pass.123", 10, true, chain: null));
}
}
UPDATED QUESTION: 2022-01-10
So one thing, @bartonjs recomended me to check this link: https://stackoverflow.com/a/48210587/6535399 I beleave i've seen this before, but still went on it and copied the code (as is) on the soution to a new blank project and ran (adding the "Export" to PFX with password).
File.WriteAllBytes("C:\\signed.pfx", cert.Export(X509ContentType.Pfx, "pwdpwdpwd"));
Then i imported the certificate, and still isn't exact: i'll post the generated certificate (image) and one that is "right" down below - the windows screen is in portuguese (since it's my OS language), but is the "Certificate Path" tab of each certiciate.
The certificate created from the sample code outed this:
But should be like this:
PFX files do not contain certificate chains, they just contain certificates (which could happen to form a chain).
cert.Export(X509ContentType.Pfx, "pwdpwdpwd")
exports one certificate as a PFX. It's shorthand for
X509Certificate2Collection coll = new X509Certificate2Collection();
coll.Add(cert);
coll.Export(X509ContentType.Pfx, "pwdpwdpwd");
Given that, the way to export two certificates into one PFX should be apparent:
X509Certificate2Collection coll = new X509Certificate2Collection();
coll.Add(issuer);
coll.Add(cert);
coll.Export(X509ContentType.Pfx, "pwdpwdpwd");
If you're exporting it as "a chain", then you probably do not want the issuer's private key to be present, so if you don't already have a public-only version of that cert, use new X509Certificate2(issuer.RawData)
to make one.