/// <summary>
        /// Extracts PFX from a local cert present in LocalMachine store under My.
        /// </summary>
        /// <param name="certificateName">Name of the certificate.</param>
        /// <param name="certificateIdentitier">The certificate identifier.</param>
        /// <param name="fullDirectoryPath">The full directory path to drop PFX at.</param>
        /// <returns>Exit code for the operation.</returns>
        private static Task <ExitCode> LocalMachineCertHandlerAsync(string certificateName, string certificateIdentitier, string fullDirectoryPath)
        {
            // Split the Certificate identifier on :. If we don't find ':' then we assume the identifier is a thumbprint,
            // otherwise we try to figure what identifier the user gave. We support X509FindType values as string.
            string[] certIdentifierSplit = certificateIdentitier.Split(':');

            X509FindType x509FindType = X509FindType.FindByThumbprint;

            if (certIdentifierSplit.Length > 0)
            {
                if (!Enum.TryParse <X509FindType>(certIdentifierSplit[1], out x509FindType))
                {
                    Logger.LogError(CallInfo.Site(), "Invalid Find type value used '{0}' in Ceritificate identifier '{1}'", certIdentifierSplit[1], certificateIdentitier);
                    return(Task.FromResult(ExitCode.InvalidCertConfiguration));
                }
            }

            X509Certificate2Collection certificateCollection = CertHelpers.FindCertificates(
                certIdentifierSplit[0],
                x509FindType,
                StoreName.My,
                StoreLocation.LocalMachine);

            if (certificateCollection.Count == 0)
            {
                Logger.LogError(CallInfo.Site(), "Failed to find certificate with name '{0}'", certificateName);
                return(Task.FromResult(ExitCode.CertificateMissingFromSource));
            }

            X509Certificate2 selectedCertificate = certificateCollection[0];

            // If more than 1 cert is found, choose the one with latest expiry.
            if (certificateCollection.Count > 1)
            {
                Logger.LogInfo(CallInfo.Site(), "Found multiple certificates which match the search identifier, selecting the one with latest expiry");

                foreach (X509Certificate2 x509Certificate2 in certificateCollection)
                {
                    // If the first selected certificate did not have a private key on it and
                    // we found another cert with private key pick that up.
                    if (!selectedCertificate.HasPrivateKey && x509Certificate2.HasPrivateKey)
                    {
                        selectedCertificate = x509Certificate2;
                    }

                    if (x509Certificate2.NotAfter > selectedCertificate.NotAfter && x509Certificate2.HasPrivateKey)
                    {
                        selectedCertificate = x509Certificate2;
                    }
                }
            }

            if (!selectedCertificate.HasPrivateKey)
            {
                Logger.LogError(CallInfo.Site(), "Certificate with name '{0}' and thumbprint '{1}' has missing Private Key", certificateName, selectedCertificate.Thumbprint);
                return(Task.FromResult(ExitCode.PrivateKeyMissingOnCertificate));
            }

            byte[] rawCertData = selectedCertificate.Export(X509ContentType.Pfx, DefaultPfxPassword);

            return(Task.FromResult(SaveCertificatePrivateKeyToDisk(rawCertData, certificateName, fullDirectoryPath)));
        }
        /// <summary>
        /// Processes the certificate management.
        /// </summary>
        /// <param name="directoryPath">Directory to put the certificatex in.</param>
        /// <param name="certConfiguration">Certificate configuration. This is a combination of comma separated values in following format
        /// *certFileName*;*SourceOfCert*;*CertIdentifierInSource*.</param>
        /// <param name="keyVaultUri">KeyVault uri if key vault is to be used.</param>
        /// <param name="keyVaultClientId">Application client Id to access keyvault.</param>
        /// <param name="keyVaultClientSecret">Application client secret to access keyvault.</param>
        /// <param name="keyVaultClientCert">Application client certificate thumbprint if the keyvault app has certificate credentials.</param>
        /// <returns>Exit code for the operation.</returns>
        internal static async Task <ExitCode> ProcessAsync(string directoryPath, string certConfiguration, string keyVaultUri, string keyVaultClientId, string keyVaultClientSecret, string keyVaultClientCert)
        {
            if (string.IsNullOrEmpty(directoryPath))
            {
                Logger.LogError(CallInfo.Site(), "Directory path missing for the Certificate directory.");
                return(ExitCode.DirectoryPathMissing);
            }

            if (string.IsNullOrEmpty(certConfiguration))
            {
                Logger.LogError(CallInfo.Site(), "Cert configuration missing. Please specify CertsToConfigure option");
                return(ExitCode.InvalidCertConfiguration);
            }

            // 1. Initialize KeyVault Client if params were passed.
            KeyVaultClient keyVaultClient = null;

            if (!string.IsNullOrEmpty(keyVaultUri))
            {
                if (string.IsNullOrEmpty(keyVaultClientId))
                {
                    Logger.LogError(CallInfo.Site(), "If KeyVaultUri is specified, KeyVault ClientId must be specified");
                    return(ExitCode.KeyVaultConfigurationIncomplete);
                }

                if (string.IsNullOrEmpty(keyVaultClientSecret) && string.IsNullOrEmpty(keyVaultClientCert))
                {
                    Logger.LogError(CallInfo.Site(), "If KeyVaultUri is specified, KeyVault ClientSecret or KeyVault ClientCert must be specified");
                    return(ExitCode.KeyVaultConfigurationIncomplete);
                }

                if (!string.IsNullOrEmpty(keyVaultClientSecret))
                {
                    KeyVaultClient.AuthenticationCallback callback =
                        (authority, resource, scope) => GetTokenFromClientSecretAsync(authority, resource, keyVaultClientId, keyVaultClientSecret);
                    keyVaultClient = new KeyVaultClient(callback);
                }
                else
                {
                    X509Certificate2Collection keyVaultCerts = CertHelpers.FindCertificates(keyVaultClientCert, X509FindType.FindByThumbprint);

                    if (keyVaultCerts.Count == 0)
                    {
                        Logger.LogError(CallInfo.Site(), "Failed to find Client cert with thumbprint '{0}'", keyVaultClientCert);
                        return(ExitCode.KeyVaultConfigurationIncomplete);
                    }

                    KeyVaultClient.AuthenticationCallback callback =
                        (authority, resource, scope) => GetTokenFromClientCertificateAsync(authority, resource, keyVaultClientId, keyVaultCerts[0]);
                    keyVaultClient = new KeyVaultClient(callback);
                }
            }

            // 2. Figure all the certs which need processing.
            string[] certsToConfigure          = certConfiguration.Split(',');
            string   currentExeDirectory       = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            string   fullDirectoryPathForCerts = Path.Combine(currentExeDirectory, directoryPath);

            // 3. Process specified certs one by one.
            foreach (string certToConfigure in certsToConfigure)
            {
                // 3a. Split the cert configuration data to get actual details. This data is informat
                // <CertNameOnDisk>;CertSource(LocalMachine or KeyVault);<CertIdentifier(SecretName or Thumbprint)>
                string[] certConfigurationParams = certToConfigure.Split(';');

                if (certConfigurationParams.Length != 3)
                {
                    Logger.LogError(CallInfo.Site(), "Invalid certificate configuration '{0}'. Cert configuration must be in format <CertFileName>;<CertSource>;<CertIdentifier>", certToConfigure);
                    return(ExitCode.InvalidCertConfiguration);
                }

                var certConfig =
                    new { CertName = certConfigurationParams[0], CertSource = certConfigurationParams[1], CertIdentifier = certConfigurationParams[2] };

                string pfxPassword = null;

                // 3b. Depending on the source of Cert get the PFX for the certs dropped into the directory.
                if (certConfig.CertSource.Equals("MyLocalMachine", StringComparison.OrdinalIgnoreCase))
                {
                    ExitCode localMachineCertHandler = await LocalMachineCertHandlerAsync(
                        certConfig.CertName,
                        certConfig.CertIdentifier,
                        fullDirectoryPathForCerts).ConfigureAwait(false);

                    if (localMachineCertHandler != ExitCode.Success)
                    {
                        return(localMachineCertHandler);
                    }

                    pfxPassword = DefaultPfxPassword;
                }
                else if (certConfig.CertSource.Equals("KeyVault", StringComparison.OrdinalIgnoreCase))
                {
                    ExitCode keyVaultCertHandlerExitCode = await KeyVaultCertHandlerAsync(
                        certConfig.CertName,
                        certConfig.CertIdentifier,
                        fullDirectoryPathForCerts,
                        keyVaultClient,
                        keyVaultUri).ConfigureAwait(false);

                    if (keyVaultCertHandlerExitCode != ExitCode.Success)
                    {
                        return(keyVaultCertHandlerExitCode);
                    }

                    pfxPassword = string.Empty;
                }
                else
                {
                    Logger.LogError(CallInfo.Site(), "Unsupported Certificate source '{0}' for cert '{1}'", certConfig.CertSource, certConfig.CertName);
                    return(ExitCode.UnsupportedCertSource);
                }

                // 3c. Convert PFX into .Key and .Crt. We are placing openssl next to this exe hence using current directory.
                ExitCode conversionExitCode = ConvertPfxIntoPemFormat(certConfig.CertName, fullDirectoryPathForCerts, currentExeDirectory, pfxPassword);

                if (conversionExitCode != ExitCode.Success)
                {
                    return(conversionExitCode);
                }

                // 3d. Delete the PFX as it is no longer needed.
                File.Delete(Path.Combine(fullDirectoryPathForCerts, certConfig.CertName + ".pfx"));
            }

            return(ExitCode.Success);
        }
Exemple #3
0
        /// <summary>
        /// Processes the certificate management.
        /// </summary>
        /// <param name="directoryPath">Directory to put the certificatex in.</param>
        /// <param name="certConfiguration">Certificate configuration. This is a combination of comma separated values in following format
        /// *certFileName*;*SourceOfCert*;*CertIdentifierInSource*.</param>
        /// <param name="keyVaultUris">KeyVault uris if key vault is to be used.</param>
        /// <param name="keyVaultClientId">Application client Id to access keyvault.</param>
        /// <param name="keyVaultClientSecret">Application client secret to access keyvault.</param>
        /// <param name="keyVaultClientCert">Application client certificate thumbprint if the keyvault app has certificate credentials.</param>
        /// <param name="useManagedIdentity">Use managed identity.</param>
        /// <returns>Exit code for the operation.</returns>
        internal static async Task <ExitCode> ProcessAsync(
            string directoryPath,
            string certConfiguration,
            List <string> keyVaultUris,
            string keyVaultClientId,
            string keyVaultClientSecret,
            string keyVaultClientCert,
            bool useManagedIdentity)
        {
            if (string.IsNullOrEmpty(directoryPath))
            {
                Logger.LogError(CallInfo.Site(), "Directory path missing for the Certificate directory.");
                return(ExitCode.DirectoryPathMissing);
            }

            if (string.IsNullOrEmpty(certConfiguration))
            {
                Logger.LogError(CallInfo.Site(), "Cert configuration missing. Please specify CertsToConfigure option");
                return(ExitCode.InvalidCertConfiguration);
            }

            // 1. Initialize KeyVault Client if params were passed.
            KeyVaultClient keyVaultClient = null;
            Dictionary <string, string> keyVaultSecretMap = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);

            if (keyVaultUris.Any())
            {
                KeyVaultClient.AuthenticationCallback callback = null;

                if (useManagedIdentity)
                {
                    string connectionString = "RunAs=App";

                    if (!string.IsNullOrEmpty(keyVaultClientId))
                    {
                        connectionString = connectionString + ";AppId=" + keyVaultClientId;
                    }

                    AzureServiceTokenProvider tokenProvider = new AzureServiceTokenProvider(connectionString);
                    callback = new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback);
                }
                else
                {
                    if (string.IsNullOrEmpty(keyVaultClientId))
                    {
                        Logger.LogError(CallInfo.Site(), "If KeyVaultUri is specified and managed identity is not used, KeyVault ClientId must be specified");
                        return(ExitCode.KeyVaultConfigurationIncomplete);
                    }

                    if (string.IsNullOrEmpty(keyVaultClientSecret) && string.IsNullOrEmpty(keyVaultClientCert))
                    {
                        Logger.LogError(CallInfo.Site(), "If KeyVaultUri is specified and managed identity is not used, KeyVault ClientSecret or KeyVault ClientCert must be specified");
                        return(ExitCode.KeyVaultConfigurationIncomplete);
                    }

                    if (!string.IsNullOrEmpty(keyVaultClientSecret))
                    {
                        callback =
                            (authority, resource, scope) => GetTokenFromClientSecretAsync(authority, resource, keyVaultClientId, keyVaultClientSecret);
                    }
                    else
                    {
                        X509Certificate2Collection keyVaultCerts = CertHelpers.FindCertificates(keyVaultClientCert, X509FindType.FindByThumbprint);

                        if (keyVaultCerts.Count == 0)
                        {
                            Logger.LogError(CallInfo.Site(), "Failed to find Client cert with thumbprint '{0}'", keyVaultClientCert);
                            return(ExitCode.KeyVaultConfigurationIncomplete);
                        }

                        callback =
                            (authority, resource, scope) => GetTokenFromClientCertificateAsync(authority, resource, keyVaultClientId, keyVaultCerts[0]);
                    }
                }

                keyVaultClient = new KeyVaultClient(callback);

                foreach (string keyVaultUri in keyVaultUris)
                {
                    IPage <SecretItem> secrets = await keyVaultClient.GetSecretsAsync(keyVaultUri).ConfigureAwait(false);

                    foreach (SecretItem secret in secrets)
                    {
                        keyVaultSecretMap[secret.Identifier.Name] = keyVaultUri;
                    }

                    while (!string.IsNullOrEmpty(secrets.NextPageLink))
                    {
                        secrets = await keyVaultClient.GetSecretsNextAsync(secrets.NextPageLink).ConfigureAwait(false);

                        foreach (SecretItem secret in secrets)
                        {
                            keyVaultSecretMap[secret.Identifier.Name] = keyVaultUri;
                        }
                    }
                }
            }

            // 2. Figure all the certs which need processing.
            string[] certsToConfigure          = certConfiguration.Split(',');
            string   currentExeDirectory       = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            string   fullDirectoryPathForCerts = Path.Combine(currentExeDirectory, directoryPath);

            // 3. Process specified certs one by one.
            foreach (string certToConfigure in certsToConfigure)
            {
                // 3a. Split the cert configuration data to get actual details. This data is informat
                // <CertNameOnDisk>;CertSource(LocalMachine or KeyVault);<CertIdentifier(SecretName or Thumbprint)>
                string[] certConfigurationParams = certToConfigure.Split(';');

                if (certConfigurationParams.Length != 3)
                {
                    Logger.LogError(CallInfo.Site(), "Invalid certificate configuration '{0}'. Cert configuration must be in format <CertFileName>;<CertSource>;<CertIdentifier>", certToConfigure);
                    return(ExitCode.InvalidCertConfiguration);
                }

                var certConfig =
                    new { CertName = certConfigurationParams[0], CertSource = certConfigurationParams[1], CertIdentifier = certConfigurationParams[2] };

                // 3b. Depending on the source of Cert get the PFX for the certs dropped into the directory.
                if (certConfig.CertSource.Equals("MyLocalMachine", StringComparison.OrdinalIgnoreCase))
                {
                    ExitCode localMachineCertHandler = await LocalMachineCertHandlerAsync(
                        certConfig.CertName,
                        certConfig.CertIdentifier,
                        fullDirectoryPathForCerts).ConfigureAwait(false);

                    if (localMachineCertHandler != ExitCode.Success)
                    {
                        return(localMachineCertHandler);
                    }
                }
                else if (certConfig.CertSource.Equals("KeyVault", StringComparison.OrdinalIgnoreCase))
                {
                    if (!keyVaultSecretMap.TryGetValue(certConfig.CertIdentifier, out string keyVaultUri))
                    {
                        Logger.LogError(CallInfo.Site(), "Certificate with name '{0}' missing from all specified KeyVaults", certConfig.CertIdentifier);
                        return(ExitCode.CertificateMissingFromSource);
                    }

                    ExitCode keyVaultCertHandlerExitCode = await KeyVaultCertHandlerAsync(
                        certConfig.CertName,
                        certConfig.CertIdentifier,
                        fullDirectoryPathForCerts,
                        keyVaultClient,
                        keyVaultUri).ConfigureAwait(false);

                    if (keyVaultCertHandlerExitCode != ExitCode.Success)
                    {
                        return(keyVaultCertHandlerExitCode);
                    }
                }
                else
                {
                    Logger.LogError(CallInfo.Site(), "Unsupported Certificate source '{0}' for cert '{1}'", certConfig.CertSource, certConfig.CertName);
                    return(ExitCode.UnsupportedCertSource);
                }

                // 3c. Convert PFX into .Key and .Crt. We are placing openssl next to this exe hence using current directory.
                ExitCode conversionExitCode = ConvertPfxIntoPemFormat(certConfig.CertName, fullDirectoryPathForCerts, currentExeDirectory, password: string.Empty);

                if (conversionExitCode != ExitCode.Success)
                {
                    return(conversionExitCode);
                }

                // 3d. Delete the PFX as it is no longer needed.
                File.Delete(Path.Combine(fullDirectoryPathForCerts, certConfig.CertName + ".pfx"));
            }

            return(ExitCode.Success);
        }