public void AddDomainName(string domainName) { if (DomainNames == null) { DomainNames = new List <string>(); } if (!string.IsNullOrWhiteSpace(domainName)) { if (!DomainNames.Exists(d => domainName.Equals(d, System.StringComparison.OrdinalIgnoreCase))) { DomainNames.Add(domainName); DomainNames = DomainNames.OrderBy(name => name).ToList(); } } }
/// <summary> /// Parse certificate for alternate name extension. /// </summary> private void Parse(byte[] data) { if (Oid.Value != Oids.SubjectAltName && Oid.Value != Oids.SubjectAltName2) { throw new FormatException("Extension has unknown oid."); } Uris.Clear(); DomainNames.Clear(); IPAddresses.Clear(); var altNames = new DerOctetString(data); var altNamesObjects = X509ExtensionUtilities.FromExtensionValue(altNames); var generalNames = Org.BouncyCastle.Asn1.X509.GeneralNames.GetInstance(altNamesObjects); foreach (var generalName in generalNames.GetNames()) { switch (generalName.TagNo) { case Org.BouncyCastle.Asn1.X509.GeneralName.UniformResourceIdentifier: Uris.Add(generalName.Name.ToString()); break; case Org.BouncyCastle.Asn1.X509.GeneralName.DnsName: DomainNames.Add(generalName.Name.ToString()); break; case Org.BouncyCastle.Asn1.X509.GeneralName.IPAddress: try { var addr = Asn1OctetString .GetInstance(generalName.Name) .GetOctets(); IPAddresses.Add(new IPAddress(addr).ToString()); } catch { throw new FormatException( "Certificate contains invalid IP address."); } break; default: break; } } }
static int Main(string[] args) { Uri Directory = null; List <string> ContactURLs = null; List <string> DomainNames = null; DateTime? NotBefore = null; DateTime? NotAfter = null; string HttpRootFolder = null; string EMail = null; string Country = null; string Locality = null; string StateOrProvince = null; string Organization = null; string OrganizationalUnit = null; string FileName = null; string Password = string.Empty; string s; int? PollingIngerval = null; int? KeySize = null; int i = 0; int c = args.Length; bool Help = false; bool Verbose = false; bool TermsOfServiceAgreed = false; bool NewKey = false; try { while (i < c) { s = args[i++].ToLower(); switch (s) { case "-dir": if (i >= c) { throw new Exception("Missing directory URI."); } if (Directory == null) { Directory = new Uri(args[i++]); } else { throw new Exception("Only one directory URI allowed."); } break; case "-le": if (Directory == null) { Directory = new Uri("https://acme-v02.api.letsencrypt.org/directory"); } else { throw new Exception("Only one directory URI allowed."); } break; case "-let": if (Directory == null) { Directory = new Uri("https://acme-staging-v02.api.letsencrypt.org/directory"); } else { throw new Exception("Only one directory URI allowed."); } break; case "-ce": if (i >= c) { throw new Exception("Missing contact e-mail."); } if (ContactURLs == null) { ContactURLs = new List <string>(); } if (EMail == null) { EMail = args[i]; } ContactURLs.Add("mailto:" + args[i++]); break; case "-cu": if (i >= c) { throw new Exception("Missing contact URI."); } if (ContactURLs == null) { ContactURLs = new List <string>(); } ContactURLs.Add(args[i++]); break; case "-dns": if (i >= c) { throw new Exception("Missing domain name."); } if (DomainNames == null) { DomainNames = new List <string>(); } DomainNames.Add(args[i++]); break; case "-na": if (i >= c) { throw new Exception("Missing timestamp."); } if (DateTime.TryParse(args[i++], out DateTime TP)) { NotAfter = TP; } else { throw new Exception("Invalid timestamp: " + args[i - 1]); } break; case "-nb": if (i >= c) { throw new Exception("Missing timestamp."); } if (DateTime.TryParse(args[i++], out TP)) { NotBefore = TP; } else { throw new Exception("Invalid timestamp: " + args[i - 1]); } break; case "-http": if (i >= c) { throw new Exception("Missing HTTP root folder."); } if (HttpRootFolder == null) { HttpRootFolder = args[i++]; } else { throw new Exception("Only one HTTP Root Folder allowed."); } break; case "-pi": if (i >= c) { throw new Exception("Missing polling interval."); } if (!int.TryParse(args[i++], out int j) || j <= 0) { throw new Exception("Invalid polling interval."); } if (PollingIngerval.HasValue) { throw new Exception("Only one polling interval allowed."); } else { PollingIngerval = j; } break; case "-ks": if (i >= c) { throw new Exception("Missing key size."); } if (!int.TryParse(args[i++], out j) || j <= 0) { throw new Exception("Invalid key size."); } if (KeySize.HasValue) { throw new Exception("Only one key size allowed."); } else { KeySize = j; } break; case "-c": if (i >= c) { throw new Exception("Missing country name."); } if (Country == null) { Country = args[i++]; } else { throw new Exception("Only one country name allowed."); } break; case "-l": if (i >= c) { throw new Exception("Missing locality name."); } if (Locality == null) { Locality = args[i++]; } else { throw new Exception("Only one locality name allowed."); } break; case "-st": if (i >= c) { throw new Exception("Missing state or province name."); } if (StateOrProvince == null) { StateOrProvince = args[i++]; } else { throw new Exception("Only one state or province name allowed."); } break; case "-o": if (i >= c) { throw new Exception("Missing organization name."); } if (Organization == null) { Organization = args[i++]; } else { throw new Exception("Only one organization name allowed."); } break; case "-ou": if (i >= c) { throw new Exception("Missing organizational unit name."); } if (OrganizationalUnit == null) { OrganizationalUnit = args[i++]; } else { throw new Exception("Only one organizational unit name allowed."); } break; case "-f": if (i >= c) { throw new Exception("Missing file name."); } if (FileName == null) { FileName = args[i++]; } else { throw new Exception("Only one file name allowed."); } break; case "-pwd": if (i >= c) { throw new Exception("Missing password."); } if (string.IsNullOrEmpty(Password)) { Password = args[i++]; } else { throw new Exception("Only one password allowed."); } break; case "-?": Help = true; break; case "-v": Verbose = true; break; case "-a": TermsOfServiceAgreed = true; break; case "-nk": NewKey = true; break; default: throw new Exception("Unrecognized switch: " + s); } } if (Help || c == 0) { Console.Out.WriteLine("Helps you create certificates using the Automatic Certificate"); Console.Out.WriteLine("Management Environment (ACME) v2 protocol."); Console.Out.WriteLine(); Console.Out.WriteLine("Command line switches:"); Console.Out.WriteLine(); Console.Out.WriteLine("-dir URI URI to the ACME directory resource to use."); Console.Out.WriteLine(" If not provided, the default Let's Encrypt"); Console.Out.WriteLine(" ACME v2 directory will be used:"); Console.Out.WriteLine(" https://acme-v02.api.letsencrypt.org/directory"); Console.Out.WriteLine("-le Uses the Let's Encrypt ACME v2 directory:"); Console.Out.WriteLine(" https://acme-v02.api.letsencrypt.org/directory"); Console.Out.WriteLine("-let Uses the Let's Encrypt ACME v2 staging directory:"); Console.Out.WriteLine(" https://acme-staging-v02.api.letsencrypt.org/directory"); Console.Out.WriteLine("-ce EMAIL Adds EMAIL to the list of contact e-mail addresses"); Console.Out.WriteLine(" when creating an account. Can be used multiple"); Console.Out.WriteLine(" times. The first e-mail address will also be"); Console.Out.WriteLine(" encoded into the certificate request."); Console.Out.WriteLine("-cu URI Adds URI to the list of contact URIs when creating"); Console.Out.WriteLine(" an account. Can be used multiple times."); Console.Out.WriteLine("-a You agree to the terms of service agreement. This"); Console.Out.WriteLine(" might be required if you want to be able to create"); Console.Out.WriteLine(" an account."); Console.Out.WriteLine("-nk Generates a new account key."); Console.Out.WriteLine("-dns DOMAIN Adds DOMAIN to the list of domain names when creating"); Console.Out.WriteLine(" an order for a new certificate. Can be used multiple"); Console.Out.WriteLine(" times. The first DOMAIN will be used as the common name"); Console.Out.WriteLine(" for the certificate request. The following domain names"); Console.Out.WriteLine(" will be used as altenative names."); Console.Out.WriteLine("-nb TIMESTAMP Generated certificate will not be valid before"); Console.Out.WriteLine(" TIMESTAMP."); Console.Out.WriteLine("-na TIMESTAMP Generated certificate will not be valid after"); Console.Out.WriteLine(" TIMESTAMP."); Console.Out.WriteLine("-http ROOTFOLDER Allows the application to respond to HTTP challenges"); Console.Out.WriteLine(" by storing temporary files under the corresponding ACME"); Console.Out.WriteLine(" challenge response folder /.well-known/acme-challenge"); Console.Out.WriteLine("-pi MS Polling Interval, in milliseconds. Default value is"); Console.Out.WriteLine(" 5000."); Console.Out.WriteLine("-ks BITS Certificate key size, in bits. Default is 4096."); Console.Out.WriteLine("-c COUNTRY Country name (C) in the certificate request."); Console.Out.WriteLine("-l LOCALITY Locality name (L) in the certificate request."); Console.Out.WriteLine("-st STATEORPROVINCE State or Province name (ST) in the certificate request."); Console.Out.WriteLine("-o ORGANIZATION Organization name (O) in the certificate request."); Console.Out.WriteLine("-ou ORGUNIT Organizational unit name (OU) in the certificate request."); Console.Out.WriteLine("-f FILENAME Output filename of the certificate, without file"); Console.Out.WriteLine(" extension."); Console.Out.WriteLine("-pwd PASSWORD Password to protect the private key in the generated"); Console.Out.WriteLine(" certificate."); Console.Out.WriteLine("-v Verbose mode."); Console.Out.WriteLine("-? Help."); return(0); } if (Verbose) { Log.Register(new ConsoleEventSink(false)); } if (Directory == null) { Directory = new Uri("https://acme-v02.api.letsencrypt.org/directory"); } if (!PollingIngerval.HasValue) { PollingIngerval = 5000; } if (!KeySize.HasValue) { KeySize = 4096; } if (FileName == null) { throw new Exception("File name not provided."); } if (string.IsNullOrEmpty(Password)) { Log.Warning("No password provided to protect the private key."); } if (string.IsNullOrEmpty(HttpRootFolder)) { Log.Warning("No HTTP root folder provided. Challenge responses must be manually configured."); } Types.Initialize( typeof(InternetContent).Assembly, typeof(AcmeClient).Assembly); Process(Verbose, Directory, ContactURLs?.ToArray(), TermsOfServiceAgreed, NewKey, DomainNames?.ToArray(), NotBefore, NotAfter, HttpRootFolder, PollingIngerval.Value, KeySize.Value, EMail, Country, Locality, StateOrProvince, Organization, OrganizationalUnit, FileName, Password).Wait(); Console.Out.WriteLine("Press ENTER to continue."); // TODO: Remove Console.In.ReadLine(); // TODO: Remove return(0); } catch (Exception ex) { ex = Log.UnnestException(ex); if (Verbose) { Log.Error(ex.Message); } else { Console.Out.WriteLine(ex.Message); } Console.Out.WriteLine("Press ENTER to continue."); // TODO: Remove Console.In.ReadLine(); // TODO: Remove return(-1); } }