static int Main(string[] args) { if (args.Length < 1) { Help(); return(0); } var command = args[0]; if (command.Equals("-h", StringComparison.OrdinalIgnoreCase) || command.Equals("/h", StringComparison.OrdinalIgnoreCase) || command.Equals("-?", StringComparison.OrdinalIgnoreCase) || command.Equals("/?", StringComparison.OrdinalIgnoreCase) || command.Equals("?", StringComparison.OrdinalIgnoreCase)) { // Display the help screen Help(); return(0); } else if (command.Equals("c", StringComparison.OrdinalIgnoreCase)) { // Generate a certificate if (args.Length < 2) { Error("Too few arguments were specified."); return(ExitCode_CommandLineError); } #region Arguments var processCommandLinePath = true; Arguments: var argIndex = 1; string outputFile = null; string outputCertFile = null; string commonOutputName = null; string outputPassword = null; int keySize = DefaultKeySize; string subjectName = null; IList <string> subjectAlternativeNameList = null; var basicKeyUsages = BasicKeyUsages.None; bool basicKeyUsagesCritical = false; var extendedUsages = new List <string>(); bool extendedUsagesCritical = false; DateTime fromDate = DateTime.UtcNow.Date; // The date only DateTime toDate = fromDate.AddYears(1); bool toDateExplicitlySet = false; int years = 0; bool isCA = false; int caLength = -1; byte[] serialNumber = null; string issuerPath = null; string issuerPassword = null; string commandLinePath = null; #region Collect while (argIndex < args.Length) { var argument = args[argIndex++].ToLower(); if (argument.Length > 0 && argument[0] == '/') { argument = '-' + argument.Substring(1); } try { switch (argument) { #region Output file case "-o": outputFile = args[argIndex++]; break; case "-oc": outputCertFile = args[argIndex++]; break; case "-on": commonOutputName = args[argIndex++]; break; case "-op": outputPassword = args[argIndex++]; break; #endregion #region Basic case "-k": if (int.TryParse(args[argIndex++], out keySize) && keySize > 0) { break; } else { Error("Invalid key size."); return(ExitCode_CommandLineError); } case "-s": subjectName = args[argIndex++]; try { X501DistinguishedName.Parse(subjectName); } catch (FormatException exc) { Error("Invalid subject distinguished name: {0}", exc.Message); return(ExitCode_CommandLineError); } break; case "-sa": subjectAlternativeNameList = args[argIndex++].Split(',').Select(x => x.Trim()).Where(x => x.Length > 0).ToList(); break; case "-sn": var serialNumberText = args[argIndex++]; if (serialNumberText.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase) || serialNumberText.StartsWith("x", StringComparison.InvariantCultureIgnoreCase)) { // A hexadecimal string try { serialNumber = ToBytes(serialNumberText.Substring(1)); break; } catch (FormatException) { Error("Invalid serial number: invalid hexadecimal string."); return(ExitCode_CommandLineError); } } else { // An integer number long serialNumberLong; if (long.TryParse(serialNumberText, out serialNumberLong)) { serialNumber = BitConverter.GetBytes(serialNumberLong); // Remove trailing zero bytes serialNumber = serialNumber.Reverse().SkipWhile(x => x == 0).Reverse().ToArray(); if (serialNumber.Length == 0) { serialNumber = new byte[] { 0 }; } break; } else { Error("Invalid serial number: the specified value is not an integer or is too large."); return(ExitCode_CommandLineError); } } #endregion #region Key usage case "-bu": case "-buc": basicKeyUsagesCritical = argument == "-buc"; try { basicKeyUsages = (BasicKeyUsages)Enum.Parse(typeof(BasicKeyUsages), args[argIndex++], true); } catch (ArgumentException exc) { Error("Invalid certificate usages: {0}", exc.Message); return(ExitCode_CommandLineError); } break; case "-eu": case "-euc": extendedUsagesCritical = argument == "-euc"; foreach (var item in args[argIndex++].Split(',')) { var usage = item.Trim(); if (item.Length == 0) { continue; } string result; switch (item.ToLower()) { case "serverauthentication": result = ExtendedKeyUsages.ServerAuthentication; break; case "clientauthentication": result = ExtendedKeyUsages.ClientAuthentication; break; case "codesigning": result = ExtendedKeyUsages.CodeSigning; break; case "emailprotection": result = ExtendedKeyUsages.EmailProtection; break; case "timestamping": result = ExtendedKeyUsages.TimeStamping; break; case "ocspsigning": result = ExtendedKeyUsages.OCSPSigning; break; default: if (item.StartsWith("oid:")) { result = item.Substring(4); break; } else { Error("Invalid extended usage: '{0}'.", item); return(ExitCode_CommandLineError); } } extendedUsages.Add(result); } break; #endregion #region Validity period case "-f": if (DateTime.TryParse(args[argIndex++], out fromDate)) { break; } else { Error("Invalid validity period starting date."); return(ExitCode_CommandLineError); } case "-t": if (DateTime.TryParse(args[argIndex++], out toDate)) { toDateExplicitlySet = true; break; } else { Error("Invalid validity period ending date."); return(ExitCode_CommandLineError); } case "-y": if (int.TryParse(args[argIndex++], out years) && years > 0) { break; } else { Error("Invalid number of years for which the certificate is valid."); return(ExitCode_CommandLineError); } #endregion #region CA case "-ca": isCA = true; break; case "-calen": if (int.TryParse(args[argIndex++], out caLength) && caLength >= 0) { break; } else { Error("Invalid 'calen' value."); return(ExitCode_CommandLineError); } #endregion #region Issuer case "-i": issuerPath = args[argIndex++]; break; case "-ip": issuerPassword = args[argIndex++]; break; #endregion #region Other case "-r": commandLinePath = args[argIndex++]; break; #endregion #region Basic case "?": case "-?": case "-h": Help(); return(ExitCode_CommandLineError); default: Error("Invalid argument: '{0}'.", argument); return(ExitCode_CommandLineError); #endregion } } catch (IndexOutOfRangeException) { Error("'{0}' must be followed by an additional parameter.", argument); return(ExitCode_CommandLineError); } } #endregion #region Process if (processCommandLinePath && false == string.IsNullOrEmpty(commandLinePath)) { if (false == File.Exists(commandLinePath)) { Error("The file containing command line arguments was not found."); return(ExitCode_CommandLineError); } string[] readCommandLineArguments; try { readCommandLineArguments = File.ReadAllLines(commandLinePath); } catch (Exception exc) { Error("Could not read the file containing command line arguments: {0}", exc.Message); return(ExitCode_CommandLineError); } try { var readCommandLine = string.Join(" ", readCommandLineArguments); readCommandLineArguments = ParseCommandLine(readCommandLine); } catch (Win32Exception exc) { Error("Could not parse the arguments read from file: {0}", exc.Message); return(ExitCode_CommandLineError); } // Indicate that the command line arguments has been already processed processCommandLinePath = false; // The arguments specified on the command line MUST override the arguments read from the file. // Therefore, they must follow the read arguments in the array. // Move the command argument at the front args = new string[] { args[0] }.Concat(readCommandLineArguments).Concat(args.Skip(1)).ToArray(); Console.WriteLine(); // Reprocess the command-line arguments goto Arguments; } if (false == string.IsNullOrEmpty(commonOutputName)) { // Apply the common name to the other parameters if (string.IsNullOrEmpty(outputFile)) { outputFile = Path.ChangeExtension(commonOutputName, ".pfx"); } if (string.IsNullOrEmpty(outputCertFile)) { outputCertFile = Path.ChangeExtension(commonOutputName, ".cer"); } } if (string.IsNullOrEmpty(outputFile)) { Error("An output file must be specified."); return(ExitCode_CommandLineError); } if (string.IsNullOrEmpty(subjectName)) { Error("A subject distinguished name (DN) must be specified."); return(ExitCode_CommandLineError); } if (outputPassword == null) { Console.Write("Enter OUTPUT file password: "******"Enter ISSUER certificate password: "******"Cannot load the certificate of the issuer: {0}", exc.Message); return(ExitCode_CommandLineError); } if (false == issuerCertificate.Extensions.OfType <X509BasicConstraintsExtension>().Any(x => x.CertificateAuthority)) { Error("The certificate of the issuer must be a CA."); return(ExitCode_CommandLineError); } if (false == issuerCertificate.HasPrivateKey) { Error("The certificate of the issuer must has an associated private key."); return(ExitCode_CommandLineError); } } else { issuerCertificate = null; } if (serialNumber == null) { serialNumber = Guid.NewGuid().ToByteArray(); } if (false == toDateExplicitlySet && years > 0) { toDate = fromDate.AddYears(years); } if (fromDate >= toDate) { Error("The ending of the validity period must follow its ending."); return(ExitCode_CommandLineError); } #endregion #endregion #region Work try { var builder = new X509CertificateBuilder(); Console.Write("Generating RSA key..."); // If the default key container is not used accessing the key will throw a "Key not found" exception using (var rsa = new RSACryptoServiceProvider(keySize, new CspParameters() { Flags = CspProviderFlags.UseDefaultKeyContainer | CspProviderFlags.CreateEphemeralKey })) { try { #region Key Console.WriteLine(" Done"); builder.PublicKey = rsa; #endregion builder.SubjectName = subjectName; builder.SubjectAlternativeNames = subjectAlternativeNameList; builder.SerialNumber = serialNumber; builder.KeyUsages = basicKeyUsages; builder.KeyUsagesCritical = basicKeyUsagesCritical; builder.ExtendedKeyUsages = extendedUsages.ToArray(); builder.ExtendedKeyUsagesCritical = extendedUsagesCritical; builder.NotBefore = fromDate; builder.NotAfter = toDate; builder.IsCertificateAuthority = isCA; builder.CertificateAuthorityPathLength = caLength; if (issuerCertificate == null) { builder.SelfSign(rsa); } else { builder.Sign(issuerCertificate); } File.WriteAllBytes(outputFile, builder.ExportPkcs12(rsa, outputPassword, 1000)); var certData = builder.Export(); if (false == string.IsNullOrEmpty(outputCertFile)) { File.WriteAllBytes(outputCertFile, certData); } // Display the hash of the certificate Console.WriteLine("Certificate hash:"); foreach (var alg in HashAlgorithmList) { using (var hash = HashAlgorithm.Create(alg)) { var binaryHash = hash.ComputeHash(certData); Console.WriteLine("{0,-8}{1}", alg, BitConverter.ToString(binaryHash).Replace("-", "")); } } } finally { // Remove the key from the key container. Otherwise, the key will be kept on the file // system which is completely undesirable. rsa.PersistKeyInCsp = false; } } } catch (Exception exc) { Error("Unexpected error: {0}", exc.Message); return(ExitCode_ProcessingError); } #endregion Console.WriteLine(); Console.WriteLine("All done."); return(0); } else if (command.Equals("h", StringComparison.OrdinalIgnoreCase)) { // Output the hash of a certificate if (args.Length < 2) { Error("Too few arguments were specified."); return(ExitCode_CommandLineError); } #region Arguments var argIndex = 1; string fileName = args[argIndex++]; string password = null; string algorithm = null; while (argIndex < args.Length) { var argument = args[argIndex++].ToLower(); if (argument.Length > 0 && argument[0] == '/') { argument = '-' + argument.Substring(1); } try { switch (argument) { case "-p": password = args[argIndex++]; break; case "-a": algorithm = args[argIndex++]; if (false == ValidHashAlgorithmList.Contains(algorithm, StringComparer.OrdinalIgnoreCase)) { Error("Invalid hash algorithm: '{0}'.", algorithm); return(ExitCode_CommandLineError); } break; #region Basic case "?": case "-?": case "-h": Help(); return(ExitCode_CommandLineError); default: Error("Invalid argument: '{0}'.", argument); return(ExitCode_CommandLineError); #endregion } } catch (IndexOutOfRangeException) { Error("'{0}' must be followed by an additional parameter.", argument); return(ExitCode_CommandLineError); } } #endregion #region Processing try { #region Load file if (false == File.Exists(fileName)) { Error("The certificate file was not found."); return(ExitCode_CommandLineError); } X509Certificate2 certificate = null; if (false == fileName.EndsWith(".pfx", StringComparison.InvariantCultureIgnoreCase)) { // Assume that the file may be in DER format try { certificate = new X509Certificate2(fileName); } catch (CryptographicException) { // The file may be encrypted so ignore the error } } if (certificate == null) { if (password == null) { // Prompt the user for a password Console.Write("Enter PFX password: "******"Cannot load the certificate: {0}", exc.Message); return(ExitCode_CommandLineError); } } #endregion if (string.Equals(algorithm, "SHA1", StringComparison.OrdinalIgnoreCase)) { // Only the SHA1 hash must be displayed Console.WriteLine(certificate.GetCertHashString()); } else if (algorithm != null) { // Display the hash only for the specified algorithm using (var hash = HashAlgorithm.Create(algorithm)) { var binaryHash = hash.ComputeHash(certificate.GetRawCertData()); Console.WriteLine(BitConverter.ToString(binaryHash).Replace("-", "")); } } else { // Display the hash for all supported algorithms var certData = certificate.GetRawCertData(); foreach (var alg in HashAlgorithmList) { using (var hash = HashAlgorithm.Create(alg)) { var binaryHash = hash.ComputeHash(certData); Console.WriteLine("{0,-8}{1}", alg, BitConverter.ToString(binaryHash).Replace("-", "")); } } } } catch (Exception exc) { Error("Unexpected error: {0}", exc.Message); return(ExitCode_ProcessingError); } #endregion return(0); } else { Error("Unknown command '{0}'. Run the program without arguments to see the help screen.", command); return(ExitCode_CommandLineError); } }
byte[] GenerateCertificate(Options options) { var builder = new X509CertificateBuilder(); // If the default key container is not used accessing the key will throw a "Key not found" exception using (var rsa = new RSACryptoServiceProvider(options.keySize, new CspParameters() { Flags = CspProviderFlags.UseDefaultKeyContainer | CspProviderFlags.CreateEphemeralKey })) { try { #region Key builder.PublicKey = rsa; #endregion builder.SubjectName = options.subjectName; builder.SubjectAlternativeNames = options.subjectAlternativeNames; builder.SerialNumber = options.serialNumber; builder.KeyUsages = options.basicKeyUsages; builder.KeyUsagesCritical = options.basicKeyUsagesCritical; if (options.extendedUsages != null) { builder.ExtendedKeyUsages = options.extendedUsages.ToArray(); } builder.ExtendedKeyUsagesCritical = options.extendedUsagesCritical; builder.NotBefore = options.fromDate; builder.NotAfter = options.toDate; builder.IsCertificateAuthority = options.isCA; builder.CertificateAuthorityPathLength = options.caLength; if (options.issuerCertificate == null) { builder.SelfSign(rsa); } else { builder.Sign(options.issuerCertificate); } File.WriteAllBytes(options.outputFile, builder.ExportPkcs12(rsa, options.outputPassword, 1000)); var certData = builder.Export(); if (false == string.IsNullOrEmpty(options.outputCertFile)) { File.WriteAllBytes(options.outputCertFile, certData); } return(certData); } finally { // Remove the key from the key container. Otherwise, the key will be kept on the file // system which is completely undesirable. rsa.PersistKeyInCsp = false; } } }