/// <summary> /// Extracts PFX from a certificate uploaded or generated from KeyVault. This does not support certs uploaded into KeyVault using secret. /// </summary> /// <param name="certificateName">Name of the certificate.</param> /// <param name="certificateSecretName">Secret name of the certificate. This is usually certificate name.</param> /// <param name="fullDirectoryPath">The full directory path to drop PFX at.</param> /// <param name="keyVaultClient">The key vault client.</param> /// <param name="keyVaultUrl">The key vault URL.</param> /// <returns>Exit code for the operation.</returns> private static async Task <ExitCode> KeyVaultCertHandlerAsync( string certificateSource, string certificateName, string certificateSecretName, string fullDirectoryPath, KeyVaultClient keyVaultClient, string keyVaultUrl) { if (keyVaultClient == null) { Logger.LogError(CallInfo.Site(), "KeyVaultClient was not initialized. Make sure required params for KeyVault connection were passed"); return(ExitCode.KeyVaultConfigurationIncomplete); } if (string.IsNullOrEmpty(keyVaultUrl)) { Logger.LogError(CallInfo.Site(), "Invalid KeyVault uri."); return(ExitCode.KeyVaultConfigurationIncomplete); } SecretBundle certificateSecret; try { if (string.Equals(certificateSource, "KeyVaultSecret", StringComparison.OrdinalIgnoreCase) || string.Equals(certificateSource, "KeyVault", StringComparison.OrdinalIgnoreCase)) { certificateSecret = await keyVaultClient.GetSecretAsync(keyVaultUrl, certificateSecretName).ConfigureAwait(false); } else if (string.Equals(certificateSource, "KeyVaultCertificate", StringComparison.OrdinalIgnoreCase)) { var cert = await keyVaultClient.GetCertificateAsync(keyVaultUrl, certificateSecretName).ConfigureAwait(false); certificateSecret = await keyVaultClient.GetSecretAsync(cert.SecretIdentifier.ToString()).ConfigureAwait(false); } else { Logger.LogError(CallInfo.Site(), $"Specified KeyVault source is not supported: {certificateSource}"); return(ExitCode.KeyVaultConfigurationIncomplete); } } catch (Exception ex) { Logger.LogError(CallInfo.Site(), ex, "Failed to get certificate with secret name '{0}' from key vault '{1}'", certificateSecretName, keyVaultUrl); return(ExitCode.KeyVaultOperationFailed); } // Only supporting managed certs for now. if (certificateSecret.Managed != true) { Logger.LogError(CallInfo.Site(), "Failed to decrypt certificate. Only managed certificates are supported. Download the unmanaged cert from secret and reupload it to certificates."); return(ExitCode.FailedToDecodeCertFromKeyVault); } return(SaveCertificatePrivateKeyToDisk(Convert.FromBase64String(certificateSecret.Value), certificateName, fullDirectoryPath)); }
/// <summary> /// Converts the PFX into pem format and extracts the Private key into .key and public in .crt format. /// </summary> /// <param name="certificateName">Name of the certificate.</param> /// <param name="certDirectoryPath">The full directory path for the PFX file. This is also the same path where the PEM and CRT files will be placed.</param> /// <param name="opensslExeDirectory">The openssl executable directory.</param> /// <param name="password">PFX password.</param> /// <returns>Exit code for the operation.</returns> private static ExitCode ConvertPfxIntoPemFormat(string certificateName, string certDirectoryPath, string opensslExeDirectory, string password) { string opensslPath = Path.Combine(opensslExeDirectory, "openssl.exe"); string pathToPfx = Path.Combine(certDirectoryPath, certificateName + ".pfx"); string keyExtractionProcessArgs = string.Format( PrivateKeyExportArguments, opensslPath, pathToPfx, Path.Combine(certDirectoryPath, certificateName + ".key"), password); // We have to start cmd.exe as openssl.exe exit is not read by Process class. Logger.LogVerbose(CallInfo.Site(), "Starting extraction of Private key for '{0}' using '{0}'", certificateName, opensslPath); Process exportPrivateKeyProcess = Process.Start("cmd", keyExtractionProcessArgs); exportPrivateKeyProcess.WaitForExit(); Logger.LogVerbose(CallInfo.Site(), "Private key extraction for certificate '{0}' process completed with exit code '{1}'", certificateName, exportPrivateKeyProcess.ExitCode); if (exportPrivateKeyProcess.ExitCode != 0) { Logger.LogError(CallInfo.Site(), "Private key extraction failed for certificate name '{0}'", certificateName); return(ExitCode.PrivateKeyExtractionFailed); } string crtExtractionProcessArgs = string.Format( PublicKeyExportArguments, opensslPath, pathToPfx, Path.Combine(certDirectoryPath, certificateName + ".crt"), password); // We have to start cmd.exe as openssl.exe exit is not read by Process class. Logger.LogVerbose(CallInfo.Site(), "Starting extraction of Public key from PFX using '{0}'", opensslPath); Process exportPublicKeyProcess = Process.Start("cmd", crtExtractionProcessArgs); exportPublicKeyProcess.WaitForExit(); Logger.LogVerbose(CallInfo.Site(), "Public key extraction for certificate '{0}' process completed with exit code '{1}'", certificateName, exportPublicKeyProcess.ExitCode); if (exportPublicKeyProcess.ExitCode != 0) { Logger.LogError(CallInfo.Site(), "Public key extraction failed for certificate name '{0}'", certificateName); return(ExitCode.PublicKeyExtractionFailed); } return(ExitCode.Success); }
/// <summary> /// Converts the PFX into pem format and extracts the Private key into .key and public in .crt format. /// </summary> /// <param name="certificateName">Name of the certificate.</param> /// <param name="certDirectoryPath">The full directory path for the PFX file. This is also the same path where the PEM and CRT files will be placed.</param> /// <param name="opensslExeDirectory">The openssl executable directory.</param> /// <param name="password">PFX password.</param> /// <returns>Exit code for the operation.</returns> private static ExitCode ConvertPfxIntoPemFormat(string certificateName, string certDirectoryPath, string opensslExeDirectory, string password) { string opensslPath = Path.Combine(opensslExeDirectory, "OpenSSL", "openssl.exe"); string pathToPfx = Path.Combine(certDirectoryPath, certificateName + ".pfx"); string keyExtractionProcessArgs = string.Format( CultureInfo.InvariantCulture, PrivateKeyExportArguments, pathToPfx, Path.Combine(certDirectoryPath, certificateName + ".key"), password); Logger.LogVerbose(CallInfo.Site(), "Starting extraction of Private key for '{0}' using '{0}'", certificateName, opensslPath); int exportPrivateKeyExitCode = ExecuteOpensslProcess(opensslPath, keyExtractionProcessArgs); Logger.LogVerbose(CallInfo.Site(), "Private key extraction for certificate '{0}' process completed with exit code '{1}'", certificateName, exportPrivateKeyExitCode); if (exportPrivateKeyExitCode != 0) { Logger.LogError(CallInfo.Site(), "Private key extraction failed for certificate name '{0}'", certificateName); return(ExitCode.PrivateKeyExtractionFailed); } string crtExtractionProcessArgs = string.Format( CultureInfo.InvariantCulture, PublicKeyExportArguments, pathToPfx, Path.Combine(certDirectoryPath, certificateName + ".crt"), password); Logger.LogVerbose(CallInfo.Site(), "Starting extraction of Public key from PFX using '{0}'", opensslPath); int exportPublicKeyExitCode = ExecuteOpensslProcess(opensslPath, crtExtractionProcessArgs); Logger.LogVerbose(CallInfo.Site(), "Public key extraction for certificate '{0}' process completed with exit code '{1}'", certificateName, exportPublicKeyExitCode); if (exportPublicKeyExitCode != 0) { Logger.LogError(CallInfo.Site(), "Public key extraction failed for certificate name '{0}'", certificateName); return(ExitCode.PublicKeyExtractionFailed); } return(ExitCode.Success); }
/// <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); }
/// <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> /// Defines the entry point of the application. /// </summary> /// <param name="args">The arguments.</param> /// <remarks> /// Possible Arguments /// 1) If you want to dump the certs from local machine only and make it work for one environment config. /// --ConfigureCerts "Certs" --ApplicationInsightsKey "AIKeyHere" --CertsToConfigure "ClusterCert;MyLocalMachine;ClusterCertThumbprint,SSLCert;MyLocalMachine;SSLCertThumbprint" /// 2) If you want to dump the certs from local machine and KeyVault and make it work for one environment config. /// --ConfigureCerts "Certs" --ApplicationInsightsKey "AIKeyHere" --CertsToConfigure "ClusterCert;MyLocalMachine;ClusterCertThumbprint,SSLCert;KeyVault;SSLSecretName" --KeyVaultUri "https://dummyvault.vault.azure.net/" --KeyVaultClientId "1dc8b8b3-be3e-482a-b56b-9092c91aa4b2" -KeyVaultClientSecret "keyvaultappsecret" /// 3) If you want to dump the certs from local machine and make it work for different environments having different configs. /// a) Set the arguments to --UseEnvironmentVariables (or -UseEnv) /// b) And set the Environment variables /// i) ConfigureCerts to Certs /// ii) ApplicationInsightsKey to AiKeyHere /// iii) CertsToConfigure to ClusterCert;MyLocalMachine;ClusterCertThumbprint,SSLCert;MyLocalMachine;SSLCertThumbprint /// Similarily other options can be set in environment variables to enable rest of the options like KeyVault. /// </remarks> private static void Main(string[] args) { CommandLineApplication commandLineApplication = new CommandLineApplication(false); CommandOption useEnvironmentVariablesOption = commandLineApplication.Option( "-UseEnv | --UseEnvironmentVariables", #pragma warning disable SA1118 // Parameter should not span multiple lines "Instead of using specified options, use Environment varibles with the same name (except the -- at start). This is to enable different integrations for different environments." + " If you use this, command line values are ignored.", #pragma warning restore SA1118 // Parameter should not span multiple lines CommandOptionType.NoValue); CommandOption configureCertsOption = commandLineApplication.Option( "--ConfigureCerts <DirectoryRelativePath>", "Configures certs for Traefik by dropping them into the specifiec directory (relative to executing assembly). Certs are dropped in .key and .crt format.", CommandOptionType.SingleValue); CommandOption certsToConfigureOption = commandLineApplication.Option( "--CertsToConfigure <FormattedCertsToConfigure>", #pragma warning disable SA1118 // Parameter should not span multiple lines "The value looks something like SSLCert;MyLocalMachine;7ce597cba5ae055fa37f222aaffc1007c3d61277,ClusterCert;KeyVault;ClusterCertSecretName. The format is" + "NameOfTheCert1;Source1;<Cert identifier1>,NameOfCert2;Source2;<Cert identifier2>." + "Possible Source values are 'MyLocalMachine' which fetches certs from Personal under LocalMachine Store and " + "'KeyVault' which fetches the cert from KeyVault. If KeyVault is specified, Specify ClientId and secret using --KeyVaultUri, --KeyVaultClientId, --KeyVaultClientSecret or --KeyVaultClientCert", #pragma warning restore SA1118 // Parameter should not span multiple lines CommandOptionType.SingleValue); CommandOption keyVaultUriOption = commandLineApplication.Option( "--KeyVaultUri <KeyVaultUri>", "Uri to use for KeyVault connection. Use --KeyVaultClientId to specify ClientId of the app to use to access Key Vault.", CommandOptionType.MultipleValue); CommandOption keyVaultClientIdOption = commandLineApplication.Option( "--KeyVaultClientId <ClientId>", "Client Id to use for KeyVault connection. Specify the secret by using --KeyVaultClientSecret or --KeyVaultClientCert.", CommandOptionType.SingleValue); CommandOption keyVaultClientSecretOption = commandLineApplication.Option( "--KeyVaultClientSecret <ClientSecret>", "Client secret to use for KeyVault connection. Specify the ClientId using --KeyVaultClientId.", CommandOptionType.SingleValue); CommandOption useManagedIdentity = commandLineApplication.Option( "--UseManagedIdentity", "Uses Managed Identity for authenticating with KeyVault. You can specify the client Id if you want to use one in --KeyVaultClientId option.", CommandOptionType.NoValue); CommandOption keyVaultClientCertThumbprintOption = commandLineApplication.Option( "--KeyVaultClientCert <ClientCertThumbprint>", "Cert thumbprint to be used to contact key vault. The cert needs to be present on the machine. Specify the ClientId using --KeyVaultClientId.", CommandOptionType.SingleValue); CommandOption applicationInsightsInstrumentationKeyOption = commandLineApplication.Option( "--ApplicationInsightsKey <InstrumentationKey>", "Instrumentation key to push traces for PreConfiguration into Application Insights.", CommandOptionType.SingleValue); commandLineApplication.HelpOption("-h|--help|-?"); commandLineApplication.OnExecute(async() => { try { bool useEnvironmentVariables = useEnvironmentVariablesOption.HasValue(); if (applicationInsightsInstrumentationKeyOption.HasValueExtended(useEnvironmentVariables)) { var instrumentationKey = applicationInsightsInstrumentationKeyOption.GetValueExtended(useEnvironmentVariables); Logger.ConfigureLogger(instrumentationKey); } if (configureCertsOption.HasValueExtended(useEnvironmentVariables)) { ExitCode certHandlerExitCode = await CertificateHandler.ProcessAsync( configureCertsOption.GetValueExtended(useEnvironmentVariables), certsToConfigureOption.GetValueExtended(useEnvironmentVariables), keyVaultUriOption.GetValuesExtended(useEnvironmentVariables), keyVaultClientIdOption.GetValueExtended(useEnvironmentVariables), keyVaultClientSecretOption.GetValueExtended(useEnvironmentVariables), keyVaultClientCertThumbprintOption.GetValueExtended(useEnvironmentVariables), useManagedIdentity.IsSwitchSpecified(useEnvironmentVariables)).ConfigureAwait(false); if (certHandlerExitCode != ExitCode.Success) { return((int)certHandlerExitCode); } } return((int)ExitCode.Success); } catch (AggregateException aggrEx) { foreach (Exception innerException in aggrEx.InnerExceptions) { Logger.LogError(CallInfo.Site(), innerException); } return((int)ExitCode.UnknownFailure); } catch (Exception ex) { Logger.LogError(CallInfo.Site(), ex); return((int)ExitCode.UnknownFailure); } }); commandLineApplication.Execute(args); Logger.Flush(); }
/// <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); }