/// <summary> /// Extracts the public Key of an RSA key or Certificate /// </summary> /// <param name="Content">RSA or Certificate content</param> /// <param name="IsCert">True for Certificate, false for RSA private key</param> /// <returns>Public key</returns> public static string GetPubKey(string Content, bool IsCert) { Logger.Debug("Extracting Public key. IsCert={0}", IsCert); using (var IN = new KillHandle()) { IN.WriteAllText(Content); if (IsCert) { return(Run("x509", "-in", IN.FileName, "-pubkey", "-noout")); } else { return(Run("rsa", "-in", IN.FileName, "-pubout")); } } }
/// <summary> /// Creates a PFX file /// </summary> /// <param name="Certificate">Main Certificate</param> /// <param name="PrivateKey">Private Key of Certificate</param> /// <param name="Parents">Parent certificates</param> /// <param name="Password">Password</param> /// <returns>PFX binary</returns> public static byte[] CreatePfx(string Certificate, string PrivateKey, string[] Parents, string Password) { Logger.Log("Creating PFX file."); using (var PfxFile = new KillHandle()) { using (var CertFile = new KillHandle()) { if (Parents == null) { CertFile.WriteAllText(Certificate); } else { CertFile.WriteAllLines(Parents.Concat(new string[] { Certificate }).ToArray()); } using (var KeyFile = new KillHandle()) { KeyFile.WriteAllText(PrivateKey); var args = new string[] { "pkcs12", "-export", "-in", CertFile.FileName, "-inkey", KeyFile.FileName, "-out", PfxFile.FileName, "-passout", $"pass:{Password}" }; if (Parents != null && Parents.Length > 0) { using (var ParentFile = new KillHandle()) { ParentFile.WriteAllLines(Parents); Run(args); } } else { Run(args); } } } return(PfxFile.ReadAllBytes()); } }
/// <summary> /// Generates a new Certificate /// </summary> /// <param name="RootKey">Root CA RSA Key</param> /// <param name="CaCert">Root CA Content</param> /// <param name="PrivateKey">Private RSA Key</param> /// <param name="HostName">HostName (often known as Common Name)</param> /// <param name="SAN">Alternative Host names</param> /// <param name="ExpirationDays">Expiration in days</param> /// <param name="UseSha256">Use SHA256 instead of SHA1</param> /// <param name="CountryCode">Two-letter ISO Country code</param> /// <param name="State">State</param> /// <param name="Town">Town</param> /// <param name="Organization">Company Name</param> /// <param name="OrganizationUnit">Department</param> /// <param name="EmailAddress">E-Mail address for Certificate</param> /// <returns>Certificate</returns> public static string GenerateCertificate(string RootKey, string CaCert, string PrivateKey, string HostName, string[] SAN = null, int ExpirationDays = 3650, bool UseSha256 = true, string CountryCode = "XX", string State = "Local", string Town = "Local", string Organization = "ACME", string OrganizationUnit = "ACME", string EmailAddress = "*****@*****.**") { Logger.Log("Generating Certificate"); if (string.IsNullOrEmpty(HostName)) { Logger.Error("HostName not specified"); throw new ArgumentNullException(nameof(HostName)); } if (string.IsNullOrEmpty(RootKey)) { Logger.Error("RootKey not specified"); throw new ArgumentNullException(nameof(RootKey)); } if (string.IsNullOrEmpty(PrivateKey)) { Logger.Error("PrivateKey not specified"); throw new ArgumentNullException(nameof(PrivateKey)); } if (ExpirationDays < 1) { Logger.Error("ExpirationDays too small"); throw new ArgumentOutOfRangeException("ExpirationDays"); } if (!Tools.IsValidIp(HostName) && !Tools.IsValidDomainName(HostName)) { Logger.Error("HostName is invalid domain name or IP"); throw new FormatException("HostName is invalid domain name or IP"); } if (SAN != null && !SAN.All(m => Tools.IsValidDomainName(m) || Tools.IsValidIp(m))) { throw new FormatException("SAN contains invalid domain name or IP"); } if (SAN == null || SAN.Length == 0) { SAN = new string[] { HostName }; } var Params = new Dictionary <string, string>(); Params[nameof(CountryCode)] = CountryCode; Params[nameof(State)] = State; Params[nameof(Town)] = Town; Params[nameof(Organization)] = Organization; Params[nameof(OrganizationUnit)] = OrganizationUnit; Params[nameof(HostName)] = HostName; Params[nameof(EmailAddress)] = EmailAddress; foreach (var Entry in Params) { if (string.IsNullOrEmpty(Entry.Value)) { Logger.Error("{0} not specified", Entry.Key); throw new ArgumentNullException(Entry.Key); } if (Entry.Value.Contains("/") || Entry.Value.Contains("\"")) { Logger.Error("{0} has invalid characters", Entry.Key); throw new FormatException($"{Entry.Key} can't have slash or quote in it."); } } if (CountryCode.Length != 2) { Logger.Error("CountryCode not two chars in length"); throw new FormatException("Country code must be 2 letter code"); } Logger.Debug("Arguments OK"); string Subject = $"/C={CountryCode}/ST={State}/L={Town}/O={Organization}/OU={OrganizationUnit}/CN={HostName}/emailAddress={EmailAddress}"; using (var KeyPropsFile = new KillHandle()) { Logger.Debug("Writing Props to {0}", KeyPropsFile.FileName); KeyPropsFile.WriteAllLines(new string[] { "[req]", "req_extensions = v3_req", "[v3_req]", SAN != null && SAN.Length > 0 ? "subjectAltName=" + SanLine(SAN) : "" }); using (var PrivateKeyFile = new KillHandle()) { Logger.Debug("Using {0} as Private Key File", PrivateKeyFile.FileName); PrivateKeyFile.WriteAllText(PrivateKey); using (var CaKeyFile = new KillHandle()) { Logger.Debug("Using {0} as CA Key File", CaKeyFile.FileName); CaKeyFile.WriteAllText(RootKey); using (var CaCertFile = new KillHandle()) { Logger.Debug("Using {0} as CA Cert File", CaCertFile.FileName); CaCertFile.WriteAllText(CaCert); //Make a proper Certificate Request Logger.Debug("Making Cert Request"); var ExeParams = new string[] { "req", "-new", "-key", PrivateKeyFile.FileName, UseSha256 ? "-sha256" : "-sha1", //"-nodes", "-extensions", "v3_req", "-subj", Subject }; using (var ReqFile = new KillHandle()) { var Req = Run(ExeParams); Logger.Debug("Request:\n{0}", Req); ReqFile.WriteAllText(Req); //Sign the Request with the CA key ExeParams = new string[] { "x509", "-req", UseSha256 ? "-sha256" : "-sha1", "-extensions", "v3_req", "-extfile", KeyPropsFile.FileName, "-days", ExpirationDays.ToString(), "-in", ReqFile.FileName, "-CA", CaCertFile.FileName, "-CAkey", CaKeyFile.FileName, "-CAcreateserial" }; var Cert = Run(ExeParams); Logger.Debug("Certificate:\n{0}", Cert); return(Cert); } } } } } }
/// <summary> /// Generates a new Root Certificate /// </summary> /// <param name="PrivateKey">Private key content</param> /// <param name="ExpirationDays">Expiration in days</param> /// <param name="UseSha256">Use SHA256 instead of SHA1</param> /// <param name="CountryCode">Two-letter ISO Country code</param> /// <param name="State">State</param> /// <param name="Town">Town</param> /// <param name="Organization">Company Name</param> /// <param name="OrganizationUnit">Department</param> /// <param name="CommonName">Display Name for Certificate</param> /// <param name="EmailAddress">E-Mail address for Certificate</param> /// <returns>Certificate</returns> public static string GenerateRootCert(string PrivateKey, int ExpirationDays = 3650, bool UseSha256 = true, string CountryCode = "XX", string State = "Local", string Town = "Local", string Organization = "ACME", string OrganizationUnit = "ACME", string CommonName = "ACME Root CA", string EmailAddress = "*****@*****.**") { Logger.Log("Generating Root CA"); if (string.IsNullOrEmpty(PrivateKey)) { Logger.Error("PrivateKey not specified"); throw new ArgumentNullException(nameof(PrivateKey)); } if (ExpirationDays < 1) { Logger.Error("ExpirationDays too small"); throw new ArgumentOutOfRangeException("ExpirationDays"); } var Params = new Dictionary <string, string>(); Params[nameof(CountryCode)] = CountryCode; Params[nameof(State)] = State; Params[nameof(Town)] = Town; Params[nameof(Organization)] = Organization; Params[nameof(OrganizationUnit)] = OrganizationUnit; Params[nameof(CommonName)] = CommonName; Params[nameof(EmailAddress)] = EmailAddress; foreach (var Entry in Params) { if (string.IsNullOrEmpty(Entry.Value)) { Logger.Error("{0} not specified", Entry.Key); throw new ArgumentNullException(Entry.Key); } if (Entry.Value.Contains("/") || Entry.Value.Contains("\"")) { Logger.Error("{0} has invalid characters", Entry.Key); throw new FormatException($"{Entry.Key} can't have slash or quote in it."); } } if (CountryCode.Length != 2) { Logger.Error("CountryCode not two chars in length"); throw new FormatException("Country code must be 2 letter code"); } Logger.Debug("Arguments OK"); string Subject = $"/C={CountryCode}/ST={State}/L={Town}/O={Organization}/OU={OrganizationUnit}/CN={CommonName}/emailAddress={EmailAddress}"; using (var TempKeyFile = new KillHandle()) { Logger.Debug("Using {0} as temporary CA file", TempKeyFile.FileName); TempKeyFile.WriteAllText(PrivateKey); var RunParams = new string[] { "req", "-new", "-x509", //"-nodes", UseSha256 ? "-sha256" : "-sha1", "-key", TempKeyFile.FileName, "-days", ExpirationDays.ToString(), "-subj", Subject, "-extensions", "v3_ca" }; var CACert = Run(RunParams); Logger.Debug("CA Cert:\n{0},", CACert); return(CACert); } }