/// <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); }
/// <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); }