Example #1
0
        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);
            }
        }
Example #2
0
        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;
                }
            }
        }