public async Task <DnsChallengeHelperResult> CompleteDNSChallenge(ILog log, ManagedCertificate managedcertificate, string domain, string txtRecordName, string txtRecordValue, bool isTestMode) { // for a given managed site configuration, attempt to complete the required challenge by // creating the required TXT record var credentials = new Dictionary <string, string>(); IDnsProvider dnsAPIProvider = null; var challengeConfig = managedcertificate.GetChallengeConfig(domain); /*if (String.IsNullOrEmpty(challengeConfig.ZoneId)) * { * return new ActionResult { IsSuccess = false, Message = "DNS Challenge Zone Id not set. Set the Zone Id to proceed." }; * }*/ if (!string.IsNullOrEmpty(challengeConfig.ChallengeCredentialKey)) { // decode credentials string array try { credentials = await _credentialsManager.GetUnlockedCredentialsDictionary(challengeConfig.ChallengeCredentialKey); } catch (Exception) { return(new DnsChallengeHelperResult { Result = new ActionResult { IsSuccess = false, Message = "DNS Challenge API Credentials could not be decrypted. The original user must be used for decryption." }, PropagationSeconds = 0, IsAwaitingUser = false }); } } var parameters = new Dictionary <string, string>(); if (challengeConfig.Parameters != null) { foreach (var p in challengeConfig.Parameters) { parameters.Add(p.Key, p.Value); } } try { dnsAPIProvider = await ChallengeProviders.GetDnsProvider(challengeConfig.ChallengeProvider, credentials, parameters, log); } catch (ChallengeProviders.CredentialsRequiredException) { return(new DnsChallengeHelperResult { Result = new ActionResult { IsSuccess = false, Message = "This DNS Challenge API requires one or more credentials to be specified." }, PropagationSeconds = 0, IsAwaitingUser = false }); } catch (Exception exp) { return(new DnsChallengeHelperResult { Result = new ActionResult { IsSuccess = false, Message = $"DNS Challenge API Provider could not be created. Check all required credentials are set. {exp.ToString()}" }, PropagationSeconds = 0, IsAwaitingUser = false }); } if (dnsAPIProvider == null) { return(new DnsChallengeHelperResult { Result = new ActionResult { IsSuccess = false, Message = "DNS Challenge API Provider not set or not recognised. Select an API to proceed." }, PropagationSeconds = 0, IsAwaitingUser = false }); } if (isTestMode && !dnsAPIProvider.IsTestModeSupported) { return(new DnsChallengeHelperResult { Result = new ActionResult { IsSuccess = true, Message = dnsAPIProvider.ProviderTitle + " does not perform any tests." }, PropagationSeconds = 0, IsAwaitingUser = false }); } string zoneId = null; if (parameters != null && parameters.ContainsKey("zoneid")) { zoneId = parameters["zoneid"]?.Trim(); } else { zoneId = challengeConfig.ZoneId?.Trim(); } if (dnsAPIProvider != null) { //most DNS providers require domains to by ASCII txtRecordName = _idnMapping.GetAscii(txtRecordName).ToLower(); log.Information($"DNS: Creating TXT Record '{txtRecordName}' with value '{txtRecordValue}', in Zone Id '{zoneId}' using API provider '{dnsAPIProvider.ProviderTitle}'"); try { var result = await dnsAPIProvider.CreateRecord(new DnsRecord { RecordType = "TXT", TargetDomainName = domain, RecordName = txtRecordName, RecordValue = txtRecordValue, ZoneId = zoneId }); result.Message = $"{dnsAPIProvider.ProviderTitle} :: {result.Message}"; bool isAwaitingUser = false; if (challengeConfig.ChallengeProvider.Contains(".Manual") || result.Message.Contains("[Action Required]")) { isAwaitingUser = true; } return(new DnsChallengeHelperResult { Result = result, PropagationSeconds = dnsAPIProvider.PropagationDelaySeconds, IsAwaitingUser = isAwaitingUser }); } catch (Exception exp) { return(new DnsChallengeHelperResult { Result = new ActionResult { IsSuccess = false, Message = $"Failed [{dnsAPIProvider.ProviderTitle}]: " + exp.Message }, PropagationSeconds = 0, IsAwaitingUser = false }); } //TODO: DNS query to check for new record /* * if (result.IsSuccess) * { * // do our own txt record query before proceeding with challenge completion * * int attempts = 3; * bool recordCheckedOK = false; * var networkUtil = new NetworkUtils(false); * * while (attempts > 0 && !recordCheckedOK) * { * recordCheckedOK = networkUtil.CheckDNSRecordTXT(domain, txtRecordName, txtRecordValue); * attempts--; * if (!recordCheckedOK) * { * await Task.Delay(1000); // hold on a sec * } * } * * // wait for provider specific propogation delay * * // FIXME: perform validation check in DNS nameservers await * // Task.Delay(dnsAPIProvider.PropagationDelaySeconds * 1000); * * return result; * } * else * { * return result; * } */ } else { return(new DnsChallengeHelperResult { Result = new ActionResult { IsSuccess = false, Message = "Error: Could not determine DNS API Provider." }, PropagationSeconds = 0, IsAwaitingUser = false }); } }
/// <summary> /// /// </summary> /// <param name="package"></param> /// <param name="isPreviewMode"></param> /// <returns></returns> public async Task <List <ActionStep> > PerformImport(ImportExportPackage package, ImportSettings settings, bool isPreviewMode) { // apply import var steps = new List <ActionStep>(); // import managed certs, certificate files, stored credentials, CAs var currentAppVersion = Certify.Management.Util.GetAppVersion(); if (currentAppVersion != package.SystemVersion) { if (package.SystemVersion == null || AppVersion.IsOtherVersionNewer(AppVersion.FromVersion(package.SystemVersion), AppVersion.FromVersion(currentAppVersion))) { steps.Add(new ActionStep { Title = "Version Check", Category = "Import", Key = "Version", HasWarning = true, Description = "Migration to an older app version is not supported. Results may be unreliable." }); } } else { steps.Add(new ActionStep { Title = "Version Check", Category = "Import", Key = "Version", Description = "Source is from the same version or a supported app version." }); } // check encryption var decryptionFailed = false; try { var decryptionCheckBytes = DecryptBytes(package.EncryptionValidation.Content, settings.EncryptionSecret, package.EncryptionSalt); var decryptionCheckString = Encoding.ASCII.GetString(decryptionCheckBytes).Trim('\0'); if (decryptionCheckString != "Secret") { // failed decryption decryptionFailed = true; } } catch (Exception) { decryptionFailed = true; } if (decryptionFailed) { steps.Add(new ActionStep { HasError = true, Title = "Decryption Check", Category = "Import", Key = "Decrypt", Description = "Secrets cannot be decrypted using the provided password." }); return(steps); } else { steps.Add(new ActionStep { Title = "Decryption Check", Category = "Import", Key = "Decrypt", Description = "Secrets can be decrypted OK using the provided password." }); } // stored credentials var credentialImportSteps = new List <ActionStep>(); foreach (var c in package.Content.StoredCredentials) { var decodedBytes = Convert.FromBase64String(c.Secret); var decryptedBytes = DecryptBytes(decodedBytes, settings.EncryptionSecret, package.EncryptionSalt); // convert decrypted bytes to UTF8 string and trim NUL c.Secret = UTF8Encoding.UTF8.GetString(decryptedBytes).Trim('\0'); var existing = await _credentialsManager.GetCredential(c.StorageKey); if (existing == null) { if (!isPreviewMode) { // perform import var result = await _credentialsManager.Update(c); if (result != null) { credentialImportSteps.Add(new ActionStep { Title = c.Title, Key = c.StorageKey }); } else { credentialImportSteps.Add(new ActionStep { Title = c.Title, Key = c.StorageKey, HasWarning = true, Description = $"Failed to store this credential. Items which depend on it may not function." }); } } else { // preview only credentialImportSteps.Add(new ActionStep { Title = c.Title, Key = c.StorageKey }); } } else { // credential already exists credentialImportSteps.Add(new ActionStep { Title = c.Title, Key = c.StorageKey, HasWarning = true, Description = $"Credential already exists, it will not be re-imported." }); } } steps.Add(new ActionStep { Title = "Import Stored Credentials", Category = "Import", Substeps = credentialImportSteps, Key = "StoredCredentials" }); var targetSiteBindings = new List <BindingInfo>(); if (await _targetServer?.IsAvailable() == true) { targetSiteBindings = await _targetServer.GetSiteBindingList(false); } // managed certs var managedCertImportSteps = new List <ActionStep>(); foreach (var c in package.Content.ManagedCertificates) { var existing = await _itemManager.GetById(c.Id); if (existing == null) { // check if item is auto deployment or single site, if single site warn if we don't have an exact match (convert to Auto) DeploymentOption deploymentMode = c.RequestConfig.DeploymentSiteOption; bool hasUnmatchedTargets = false; bool siteIdChanged = false; var warningMsg = ""; if (deploymentMode == DeploymentOption.SingleSite) { var targets = targetSiteBindings.Where(t => t.SiteId == c.ServerSiteId); if (targets.Any()) { //exact match on site id, check domains var unmatchedDomains = new List <string>(); foreach (var d in c.GetCertificateDomains()) { var t = targets.FirstOrDefault(ta => ta.Host == d); if (t == null) { unmatchedDomains.Add(d); hasUnmatchedTargets = true; warningMsg += " " + d; } } } else { // no exact site id match, check if a different site is an exact match, if so migrate site id // if no exact match, change to auto } } else { // auto deploy, site id only used for IIS site selection in UI } if (!isPreviewMode) { // perform actual import try { // TODO : re-map certificate pfx path, could be a different location on this instance // warn if deployment task script paths don't match an existing file? // TODO : warn if Certificate Authority ID does not match one we have (cert renewal will fail) var result = await _itemManager.Update(c); if (result != null) { managedCertImportSteps.Add(new ActionStep { Title = c.Name, Key = c.Id, HasWarning = (hasUnmatchedTargets || siteIdChanged) }); } else { managedCertImportSteps.Add(new ActionStep { Title = c.Name, Key = c.Id, HasError = true, Description = $"Failed to import item." }); } } catch (Exception exp) { managedCertImportSteps.Add(new ActionStep { Title = c.Name, Key = c.Id, HasError = true, Description = $"Failed to import item: {exp.Message}" }); } } else { // preview only managedCertImportSteps.Add(new ActionStep { Title = c.Name, Key = c.Id }); } } else { managedCertImportSteps.Add(new ActionStep { Title = c.Name, Key = c.Id, HasWarning = true, Description = "Item already exists, it will not be re-imported." }); } } steps.Add(new ActionStep { Title = "Import Managed Certificates", Category = "Import", Substeps = managedCertImportSteps, Key = "ManagedCerts" }); // certificate files var certFileImportSteps = new List <ActionStep>(); foreach (var c in package.Content.CertificateFiles) { var pfxBytes = DecryptBytes(c.Content, settings.EncryptionSecret, package.EncryptionSalt); X509Certificate2 cert = null; try { cert = new X509Certificate2(pfxBytes); } catch (Exception) { // maybe we need a password var managedCert = package.Content.ManagedCertificates.FirstOrDefault(m => m.CertificatePath == c.Filename && m.CertificatePasswordCredentialId != null); if (managedCert != null) { //get stored cred var cred = await _credentialsManager.GetUnlockedCredentialsDictionary(managedCert.CertificatePasswordCredentialId); if (cred != null) { var pfxPwd = cred["password"]; cert = new X509Certificate2(pfxBytes, pfxPwd); } } } if (cert != null) { bool isVerified = cert.Verify(); if (!System.IO.File.Exists(c.Filename)) { if (!isPreviewMode) { // perform actual import, TODO: re-map cert PFX storage location try { System.IO.File.WriteAllBytes(c.Filename, c.Content); certFileImportSteps.Add(new ActionStep { Title = $"Importing PFX {cert.Subject}, expiring {cert.NotAfter}", Key = c.Filename, HasWarning = !isVerified, Description = isVerified ? null : "Certificate did not pass verify check." }); } catch (Exception exp) { certFileImportSteps.Add(new ActionStep { Title = $"Importing PFX {cert.Subject}, expiring {cert.NotAfter}", Key = c.Filename, HasError = true, Description = $"Failed to write certificate to destination: {c.Filename} [{exp.Message}]" }); } } else { // preview only certFileImportSteps.Add(new ActionStep { Title = $"Importing PFX {cert.Subject}, expiring {cert.NotAfter}", Key = c.Filename, HasWarning = !isVerified, Description = isVerified ? "Would import to " + c.Filename : "Certificate did not pass verify check." }); } } else { certFileImportSteps.Add(new ActionStep { Title = $"Importing PFX {cert.Subject}, expiring {cert.NotAfter}", Key = c.Filename, HasWarning = true, Description = "Output file already exists, it will not be re-imported" }); } } else { certFileImportSteps.Add(new ActionStep { Title = $"Importing PFX Failed", Key = c.Filename, HasWarning = true, Description = "Could not create PFX from bytes. Password may be incorrect." }); } } steps.Add(new ActionStep { Title = "Import Certificate Files", Category = "Import", Substeps = certFileImportSteps, Key = "CertFiles" }); return(steps); }
public async Task <DnsChallengeHelperResult> GetDnsProvider(string providerTypeId, string credentialsId, Dictionary <string, string> parameters, ICredentialsManager credentialsManager) { var credentials = new Dictionary <string, string>(); IDnsProvider dnsAPIProvider = null; if (!string.IsNullOrEmpty(credentialsId)) { // decode credentials string array try { credentials = await credentialsManager.GetUnlockedCredentialsDictionary(credentialsId); } catch (Exception) { return(new DnsChallengeHelperResult { Result = new ActionResult { IsSuccess = false, Message = "DNS Challenge API Credentials could not be decrypted. The original user must be used for decryption." }, PropagationSeconds = 0, IsAwaitingUser = false }); } } try { dnsAPIProvider = await ChallengeProviders.GetDnsProvider(providerTypeId, credentials, parameters); } catch (ChallengeProviders.CredentialsRequiredException) { return(new DnsChallengeHelperResult { Result = new ActionResult { IsSuccess = false, Message = "This DNS Challenge API requires one or more credentials to be specified." }, PropagationSeconds = 0, IsAwaitingUser = false }); } catch (Exception exp) { return(new DnsChallengeHelperResult { Result = new ActionResult { IsSuccess = false, Message = $"DNS Challenge API Provider could not be created. Check all required credentials are set and software dependencies installed. {exp.ToString()}" }, PropagationSeconds = 0, IsAwaitingUser = false }); } if (dnsAPIProvider == null) { return(new DnsChallengeHelperResult { Result = new ActionResult { IsSuccess = false, Message = "DNS Challenge API Provider not set or could not load." }, PropagationSeconds = 0, IsAwaitingUser = false }); } return(new DnsChallengeHelperResult { Result = new ActionResult { IsSuccess = true, Message = "Create Provider Instance" }, Provider = dnsAPIProvider }); }