Esempio n. 1
0
        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
                });
            }
        }
Esempio n. 2
0
        /// <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);
        }
Esempio n. 3
0
        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
            });
        }