예제 #1
0
        public async Task TestChallengeRequestHttp01()
        {
            var site = await iisManager.GetIISSiteById(_siteId);

            Assert.AreEqual(site.Name, testSiteName);

            var dummyManagedCertificate = new ManagedCertificate
            {
                Id             = Guid.NewGuid().ToString(),
                Name           = testSiteName,
                GroupId        = site.Id.ToString(),
                UseStagingMode = true,
                RequestConfig  = new CertRequestConfig
                {
                    PrimaryDomain = testSiteDomain,
                    Challenges    = new ObservableCollection <CertRequestChallengeConfig>(
                        new List <CertRequestChallengeConfig>
                    {
                        new CertRequestChallengeConfig {
                            ChallengeType = "http-01"
                        }
                    }),
                    PerformAutoConfig                = true,
                    PerformAutomatedCertBinding      = true,
                    PerformChallengeFileCopy         = true,
                    PerformExtensionlessConfigChecks = true,
                    WebsiteRootPath = testSitePath
                },
                ItemType = ManagedCertificateType.SSL_ACME
            };

            var result = await certifyManager.PerformCertificateRequest(null, dummyManagedCertificate);

            //ensure cert request was successful
            Assert.IsTrue(result.IsSuccess, "Certificate Request Not Completed");

            //check details of cert, subject alternative name should include domain and expiry must be great than 89 days in the future
            var managedCertificates = await certifyManager.GetManagedCertificates();

            var managedCertificate = managedCertificates.FirstOrDefault(m => m.Id == dummyManagedCertificate.Id);

            //emsure we have a new managed site
            Assert.IsNotNull(managedCertificate);

            //have cert file details
            Assert.IsNotNull(managedCertificate.CertificatePath);

            var fileExists = System.IO.File.Exists(managedCertificate.CertificatePath);

            Assert.IsTrue(fileExists);

            //check cert is correct
            var certInfo = CertificateManager.LoadCertificate(managedCertificate.CertificatePath);

            Assert.IsNotNull(certInfo);

            var isRecentlyCreated = Math.Abs((DateTime.UtcNow - certInfo.NotBefore).TotalDays) < 2;

            Assert.IsTrue(isRecentlyCreated);

            var expiresInFuture = (certInfo.NotAfter - DateTime.UtcNow).TotalDays >= 89;

            Assert.IsTrue(expiresInFuture);

            // remove managed site
            await certifyManager.DeleteManagedCertificate(managedCertificate.Id);
        }
        private async Task <List <ActionStep> > DeployToAllTargetBindings(IBindingDeploymentTarget deploymentTarget,
                                                                          ManagedCertificate managedCertificate,
                                                                          CertRequestConfig requestConfig,
                                                                          string certStoreName,
                                                                          byte[] certHash,
                                                                          List <string> dnsHosts,
                                                                          bool isPreviewOnly = false
                                                                          )
        {
            var actions     = new List <ActionStep>();
            var targetSites = new List <IBindingDeploymentTargetItem>();

            // ensure defaults applied for deployment mode
            requestConfig.ApplyDeploymentOptionDefaults();

            // if single site, add that
            if (requestConfig.DeploymentSiteOption == DeploymentOption.SingleSite)
            {
                if (!string.IsNullOrEmpty(managedCertificate.ServerSiteId))
                {
                    var site = await deploymentTarget.GetTargetItem(managedCertificate.ServerSiteId);

                    if (site != null)
                    {
                        targetSites.Add(site);
                    }
                }
            }

            // or add all sites (if required)
            if (requestConfig.DeploymentSiteOption == DeploymentOption.AllSites || requestConfig.DeploymentSiteOption == DeploymentOption.Auto)
            {
                targetSites.AddRange(await deploymentTarget.GetAllTargetItems());
            }

            // for each sites we want to target, identify bindings to add/update as required
            foreach (var site in targetSites)
            {
                try
                {
                    var existingBindings = await deploymentTarget.GetBindings(site.Id);

                    var existingHttps = existingBindings.Where(e => e.Protocol == "https").ToList();

                    //remove https bindings which already have an https equivalent (specific hostname or blank)
                    existingBindings.RemoveAll(b => existingHttps.Any(e => e.Host == b.Host) && b.Protocol == "http");

                    existingBindings = existingBindings.OrderBy(b => b.Protocol).ThenBy(b => b.Host).ToList();

                    // for each binding create or update an https binding
                    foreach (var b in existingBindings)
                    {
                        var updateBinding = false;

                        //if binding is http and there is no https binding, create one
                        var hostname = b.Host;

                        // install the cert for this binding if the hostname matches, or we have a
                        // matching wildcard, or if there is no hostname specified in the binding

                        if (requestConfig.DeploymentBindingReplacePrevious || requestConfig.DeploymentSiteOption == DeploymentOption.Auto)
                        {
                            // if replacing previous, check if current binding cert hash matches
                            // previous cert hash
                            if (b.CertificateHash != null && (managedCertificate.CertificatePreviousThumbprintHash != null || managedCertificate.CertificateThumbprintHash != null))
                            {
                                if (string.Equals(b.CertificateHash, managedCertificate.CertificatePreviousThumbprintHash))
                                {
                                    updateBinding = true;
                                }
                                else if (string.Equals(b.CertificateHash, managedCertificate.CertificateThumbprintHash))
                                {
                                    updateBinding = true;
                                }
                            }
                        }

                        if (updateBinding == false)
                        {
                            // TODO: add wildcard match
                            if (string.IsNullOrEmpty(hostname) && requestConfig.DeploymentBindingBlankHostname)
                            {
                                updateBinding = true;
                            }
                            else
                            {
                                if (requestConfig.DeploymentBindingMatchHostname)
                                {
                                    updateBinding = ManagedCertificate.IsDomainOrWildcardMatch(dnsHosts, hostname);
                                }
                            }
                        }

                        if (requestConfig.DeploymentBindingOption == DeploymentBindingOption.UpdateOnly)
                        {
                            // update existing bindings only, so only update if this is already an
                            // https binding
                            if (b.Protocol != "https")
                            {
                                updateBinding = false;
                            }
                        }

                        if (b.Protocol != "http" && b.Protocol != "https")
                        {
                            // skip bindings for other service types
                            updateBinding = false;
                        }

                        if (updateBinding)
                        {
                            //SSL port defaults to 443 or the config default, unless we already have an https binding, in which case re-use same port
                            var sslPort         = 443;
                            var targetIPAddress = "*";

                            if (!string.IsNullOrWhiteSpace(requestConfig.BindingPort))
                            {
                                sslPort = int.Parse(requestConfig.BindingPort);
                            }

                            if (b.Protocol == "https")
                            {
                                if (b.Port > 0)
                                {
                                    sslPort = b.Port;
                                }
                                if (!unassignedIPs.Contains(b.IP))
                                {
                                    targetIPAddress = b.IP;
                                }
                            }
                            else
                            {
                                if (!unassignedIPs.Contains(requestConfig.BindingIPAddress))
                                {
                                    targetIPAddress = requestConfig.BindingIPAddress;
                                }
                            }

                            //create/update binding and associate new cert

                            //if any binding elements configured, use those, otherwise auto bind using defaults and SNI

                            var stepActions = await UpdateBinding(
                                deploymentTarget,
                                site,
                                existingBindings,
                                certStoreName,
                                certHash,
                                hostname,
                                sslPort : sslPort,
                                useSNI : (requestConfig.BindingUseSNI != null ? (bool)requestConfig.BindingUseSNI : true),
                                ipAddress : targetIPAddress,
                                alwaysRecreateBindings : requestConfig.AlwaysRecreateBindings,
                                isPreviewOnly : isPreviewOnly
                                );

                            actions.AddRange(stepActions);
                        }
                    }
                    //
                }
                catch (Exception exp)
                {
                    actions.Add(new ActionStep {
                        Title = site.Name, Category = "Deploy.AddOrUpdateBindings", HasError = true, Description = exp.ToString()
                    });
                }
            }

            return(actions);
        }
        /// <summary>
        /// Perform set of test challenges and configuration checks to determine if site appears
        /// valid for certificate requests
        /// </summary>
        /// <param name="managedCertificate"> managed site to check </param>
        /// <param name="isPreviewMode">
        /// If true, perform full set of checks (DNS etc), if false performs minimal/basic checks
        /// </param>
        /// <returns>  </returns>
        public async Task <List <StatusMessage> > TestChallenge(ILog log, ManagedCertificate managedCertificate,
                                                                bool isPreviewMode, IProgress <RequestProgressState> progress = null)
        {
            var results = new List <StatusMessage>();

            if (managedCertificate.RequestConfig.PerformAutoConfig && managedCertificate.GetChallengeConfig(null).ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP)
            {
                var serverCheck = await _serverProvider.RunConfigurationDiagnostics(managedCertificate.ServerSiteId);

                results.AddRange(serverCheck.ConvertAll(x => new StatusMessage {
                    IsOK = !x.HasError, HasWarning = x.HasWarning, Message = x.Description
                }));
            }

            if (managedCertificate.GetChallengeConfig(null).ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP)
            {
                if (CoreAppSettings.Current.EnableHttpChallengeServer)
                {
                    _httpChallengeServerAvailable = await StartHttpChallengeServer();

                    if (_httpChallengeServerAvailable)
                    {
                        results.Add(new StatusMessage {
                            IsOK = true, Message = "Http Challenge Server process available."
                        });
                    }
                    else
                    {
                        results.Add(new StatusMessage {
                            IsOK = true, HasWarning = true, Message = "Built-in Http Challenge Server process unavailable or could not start. Challenge responses will fall back to IIS."
                        });
                    }
                }
            }

            results.AddRange(
                await _challengeDiagnostics.TestChallengeResponse(
                    log,
                    _serverProvider,
                    managedCertificate,
                    isPreviewMode,
                    CoreAppSettings.Current.EnableDNSValidationChecks, progress
                    )
                );

            if (progress != null)
            {
                if (results.Any(r => r.IsOK == false))
                {
                    ReportProgress(progress,
                                   new RequestProgressState(RequestState.Error, "One or more tests failed", managedCertificate,
                                                            isPreviewMode));
                }
                else
                {
                    ReportProgress(progress,
                                   new RequestProgressState(RequestState.Success, "All Tests Completed OK", managedCertificate,
                                                            isPreviewMode));
                }
            }

            if (CoreAppSettings.Current.EnableHttpChallengeServer)
            {
                await StopHttpChallengeServer();
            }

            return(results);
        }
예제 #4
0
        /// <summary>
        /// Simulates responding to a challenge, performs a sample configuration and attempts to
        /// verify it.
        /// </summary>
        /// <param name="serverManager">  </param>
        /// <param name="managedCertificate">  </param>
        /// <returns> APIResult </returns>
        /// <remarks>
        /// The purpose of this method is to test the options (permissions, configuration) before
        /// submitting a request to the ACME server, to avoid creating failed requests and hitting
        /// usage limits.
        /// </remarks>
        public async Task <List <StatusMessage> > TestChallengeResponse(
            ILog log,
            ICertifiedServer serverManager,
            ManagedCertificate managedCertificate,
            bool isPreviewMode,
            bool enableDnsChecks,
            IProgress <RequestProgressState> progress = null
            )
        {
            var results = new List <StatusMessage>();

            var requestConfig = managedCertificate.RequestConfig;
            var result        = new StatusMessage {
                IsOK = true
            };
            var domains = new List <string> {
                requestConfig.PrimaryDomain
            };

            if (requestConfig.SubjectAlternativeNames != null)
            {
                domains.AddRange(requestConfig.SubjectAlternativeNames);
            }

            domains = domains.Distinct().ToList();

            // if wildcard domain included, check first level labels not also specified, i.e.
            // *.example.com & www.example.com cannot be mixed, but example.com, *.example.com &
            // test.wwww.example.com can
            var invalidLabels = new List <string>();

            if (domains.Any(d => d.StartsWith("*.")))
            {
                foreach (var wildcard in domains.Where(d => d.StartsWith("*.")))
                {
                    var rootDomain = wildcard.Replace("*.", "");
                    // add list of domains where label count exceeds root domain label count
                    invalidLabels.AddRange(domains.Where(domain => domain != wildcard && domain.EndsWith(rootDomain) && domain.Count(s => s == '.') == wildcard.Count(s => s == '.')));

                    if (invalidLabels.Any())
                    {
                        results.Add(new StatusMessage {
                            IsOK = false, Message = $"Wildcard domain certificate requests (e.g. {wildcard}) cannot be mixed with requests including immediate subdomains (e.g. {invalidLabels[0]})."
                        });
                        return(results);
                    }
                }
            }

            var generatedAuthorizations = new List <PendingAuthorization>();

            try
            {
                // if DNS checks enabled, attempt them here
                if (isPreviewMode && enableDnsChecks)
                {
                    var includeIPResolution = false;
                    if (managedCertificate.RequestConfig.Challenges.Any(c => c.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP))
                    {
                        includeIPResolution = true;
                    }

                    log.Information("Performing preview DNS tests. {managedItem}", managedCertificate);

                    var tasks = new List <Task <List <ActionResult> > >();

                    foreach (var d in domains)
                    {
                        tasks.Add(_netUtil.CheckDNS(log, d.Replace("*.", ""), includeIPResolution));
                    }

                    var allResults = await Task.WhenAll(tasks);

                    // add DNS check results. DNS check fails are considered a warning instead of an error.
                    foreach (var checkResults in allResults)
                    {
                        foreach (var c in checkResults)
                        {
                            results.Add(new StatusMessage
                            {
                                IsOK       = true,
                                HasWarning = !c.IsSuccess,
                                Message    = c.Message
                            });
                        }
                    }
                }

                foreach (var domain in domains)
                {
                    var challengeConfig = managedCertificate.GetChallengeConfig(domain);

                    var challengeType = challengeConfig.ChallengeType;
                    if (challengeType == SupportedChallengeTypes.CHALLENGE_TYPE_SNI)
                    {
                        log.Warning("tls-sni-01 challenge type is no longer supported by the Let's Encrypt service. Falling back to http-01");
                        challengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP;
                    }

                    if (challengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP)
                    {
                        // if dns validation not selected but one or more domains is a wildcard, reject
                        if (domain.StartsWith("*."))
                        {
                            results.Add(new StatusMessage {
                                IsOK = false, Message = $"http-01 authorization cannot be used for wildcard domains: {domain}. Use DNS (dns-01) validation instead."
                            });
                            return(results);
                        }

                        var challengeFileUrl = $"http://{domain}/.well-known/acme-challenge/configcheck";

                        var simulatedAuthorization = new PendingAuthorization
                        {
                            Challenges = new List <AuthorizationChallengeItem> {
                                new AuthorizationChallengeItem
                                {
                                    ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP,
                                    ResourcePath  = ".well-known\\acme-challenge\\configcheck",
                                    ResourceUri   = challengeFileUrl,
                                    Value         = "Extensionless File Config Test - OK"
                                }
                            }
                        };

                        generatedAuthorizations.Add(simulatedAuthorization);

                        var httpChallengeResult = await PerformChallengeResponse_Http01(
                            log, serverManager, domain, managedCertificate, simulatedAuthorization
                            );

                        if (!httpChallengeResult.IsSuccess)
                        {
                            result.IsOK = false;
                            result.FailedItemSummary.Add($"Config checks failed to verify http://{domain} is both publicly accessible and can serve extensionless files e.g. {challengeFileUrl}");
                            result.Message = httpChallengeResult.Message;
                            results.Add(result);

                            // don't check any more after first failure
                            break;
                        }
                        else
                        {
                            results.Add(new StatusMessage {
                                IsOK = true, Message = httpChallengeResult.Message, Result = httpChallengeResult
                            });
                        }
                    }
                    else if (challengeType == SupportedChallengeTypes.CHALLENGE_TYPE_SNI)
                    {
                        result.IsOK = false;
                        result.FailedItemSummary.Add($"The {SupportedChallengeTypes.CHALLENGE_TYPE_SNI} challenge type is no longer available.");
                        results.Add(result);

                        return(results);

                        /*
                         *
                         * var serverVersion = await serverManager.GetServerVersion();
                         *
                         * if (serverVersion.Major < 8)
                         * {
                         *  result.IsOK = false;
                         *  result.FailedItemSummary.Add($"The {SupportedChallengeTypes.CHALLENGE_TYPE_SNI} challenge is only available for IIS versions 8+.");
                         *  results.Add(result);
                         *
                         *  return results;
                         * }
                         *
                         * var simulatedAuthorization = new PendingAuthorization
                         * {
                         *  Challenges = new List<AuthorizationChallengeItem> {
                         *           new AuthorizationChallengeItem
                         *           {
                         *                ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_SNI,
                         *                HashIterationCount= 1,
                         *                Value = GenerateSimulatedKeyAuth()
                         *           }
                         *       }
                         * };
                         *
                         * generatedAuthorizations.Add(simulatedAuthorization);
                         *
                         * result.IsOK =
                         *   PrepareChallengeResponse_TlsSni01(
                         *      log, serverManager, domain, managedCertificate, simulatedAuthorization
                         *  )();
                         *
                         * results.Add(result);
                         */
                    }
                    else if (challengeType == SupportedChallengeTypes.CHALLENGE_TYPE_DNS)
                    {
                        var recordName = $"_acme-challenge-test.{domain}".Replace("*.", "");

                        if (challengeConfig.ChallengeProvider == Certify.Providers.DNS.AcmeDns.DnsProviderAcmeDns.Definition.Id)
                        {
                            // use real cname to avoid having to setup different records
                            recordName = $"_acme-challenge.{domain}".Replace("*.", "");
                        }

                        var simulatedAuthorization = new PendingAuthorization
                        {
                            Challenges = new List <AuthorizationChallengeItem> {
                                new AuthorizationChallengeItem
                                {
                                    ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_DNS,
                                    Key           = recordName,
                                    Value         = GenerateSimulatedDnsAuthValue()
                                }
                            }
                        };
                        generatedAuthorizations.Add(simulatedAuthorization);

                        var dnsResult =
                            await PerformChallengeResponse_Dns01(
                                log,
                                domain.Replace("*.", ""),
                                managedCertificate,
                                simulatedAuthorization
                                );

                        result.Message = dnsResult.Result.Message;
                        result.IsOK    = dnsResult.Result.IsSuccess;

                        results.Add(result);
                    }
                    else
                    {
                        throw new NotSupportedException($"ChallengeType not supported: {challengeConfig.ChallengeType}");
                    }
                }
            }
            finally
            {
                //FIXME: needs to be filtered by managed site: result.Message = String.Join("\r\n", GetActionLogSummary());
                generatedAuthorizations.ForEach(ga => ga.Cleanup());
            }

            return(results);
        }
예제 #5
0
        private async Task <DnsChallengeHelperResult> PerformChallengeResponse_Dns01(ILog log, string domain, ManagedCertificate managedCertificate, PendingAuthorization pendingAuth)
        {
            var dnsChallenge = pendingAuth.Challenges.FirstOrDefault(c => c.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_DNS);

            if (dnsChallenge == null)
            {
                var msg = $"No dns-01 challenge to complete for {managedCertificate.Name}. Request cannot continue.";

                log.Warning(msg);

                return(new DnsChallengeHelperResult
                {
                    Result = new ActionResult
                    {
                        IsSuccess = false,
                        Message = msg
                    },
                    IsAwaitingUser = false,
                    PropagationSeconds = 0
                });
            }

            // create DNS records (manually or via automation)
            var dnsHelper = new DnsChallengeHelper();

            var dnsResult = await dnsHelper.CompleteDNSChallenge(log, managedCertificate, domain, dnsChallenge.Key, dnsChallenge.Value);

            if (!dnsResult.Result.IsSuccess)
            {
                log.Error($"DNS update failed: {dnsResult.Result.Message}");
            }
            else
            {
                log.Information($"DNS: {dnsResult.Result.Message}");
            }

            var cleanupQueue = new List <Action> {
            };

            // configure cleanup actions for use after challenge completes
            pendingAuth.Cleanup = async() =>
            {
                var result = await dnsHelper.DeleteDNSChallenge(log, managedCertificate, domain, dnsChallenge.Key, dnsChallenge.Value);

                //log.Information(result.Result?.Message);
            };

            return(dnsResult);
        }
        /// <summary>
        /// Creates or updates the https bindings associated with the dns names in the current
        /// request config, using the requested port/ips or autobinding
        /// </summary>
        /// <param name="requestConfig">  </param>
        /// <param name="pfxPath">  </param>
        /// <param name="cleanupCertStore">  </param>
        /// <returns>  </returns>
        public async Task <List <ActionStep> > StoreAndDeploy(IBindingDeploymentTarget deploymentTarget, ManagedCertificate managedCertificate, string pfxPath, string pfxPwd, bool isPreviewOnly)
        {
            var actions = new List <ActionStep>();

            var requestConfig = managedCertificate.RequestConfig;

            if (!isPreviewOnly)
            {
                if (new System.IO.FileInfo(pfxPath).Length == 0)
                {
                    throw new ArgumentException("InstallCertForRequest: Invalid PFX File");
                }
            }

            //store cert in default store against primary domain
            var certStoreName           = CertificateManager.GetStore().Name;
            X509Certificate2 storedCert = null;

            byte[] certHash = null;

            // unless user has opted not to store cert, store it now
            if (requestConfig.DeploymentSiteOption != DeploymentOption.NoDeployment)
            {
                if (!isPreviewOnly)
                {
                    try
                    {
                        storedCert = await CertificateManager.StoreCertificate(requestConfig.PrimaryDomain, pfxPath, isRetry : false, enableRetryBehaviour : _enableCertDoubleImportBehaviour, pwd : pfxPwd);

                        if (storedCert != null)
                        {
                            certHash = storedCert.GetCertHash();


                            // TODO: move setting friendly name to cert request manager
                            managedCertificate.CertificateFriendlyName = storedCert.FriendlyName;

                            actions.Add(new ActionStep {
                                HasError = false, Title = "Certificate Stored", Category = "Certificate Storage", Description = "Certificate stored OK"
                            });
                        }
                    }
                    catch (Exception exp)
                    {
                        actions.Add(new ActionStep {
                            HasError = true, Title = "Certificate Storage Failed", Category = "Certificate Storage", Description = "Error storing certificate." + exp.Message
                        });
                        return(actions);
                    }
                }
                else
                {
                    //fake cert for preview only
                    storedCert = new X509Certificate2();
                    certHash   = new byte[] { 0x00, 0x01, 0x02 };
                }
            }

            if (storedCert != null)
            {
                //get list of domains we need to create/update https bindings for
                var dnsHosts = new List <string> {
                    requestConfig.PrimaryDomain
                };

                if (requestConfig.SubjectAlternativeNames != null)
                {
                    foreach (var san in requestConfig.SubjectAlternativeNames)
                    {
                        dnsHosts.Add(san);
                    }
                }

                dnsHosts = dnsHosts
                           .Distinct()
                           .Where(d => !string.IsNullOrEmpty(d))
                           .ToList();

                // depending on our deployment mode we decide which sites/bindings to update:
                var deployments = await DeployToAllTargetBindings(deploymentTarget, managedCertificate, requestConfig, certStoreName, certHash, dnsHosts, isPreviewOnly);

                actions.AddRange(deployments);
            }
            else
            {
                if (requestConfig.DeploymentSiteOption != DeploymentOption.NoDeployment)
                {
                    actions.Add(new ActionStep {
                        HasError = true, Title = "Certificate Storage", Description = "Certificate could not be stored."
                    });
                }
            }

            // deployment tasks completed
            return(actions);
        }
예제 #7
0
        public async Task <PendingAuthorization> PerformAutomatedChallengeResponse(ILog log, ICertifiedServer iisManager, ManagedCertificate managedCertificate, PendingAuthorization pendingAuth)
        {
            var requestConfig   = managedCertificate.RequestConfig;
            var domain          = pendingAuth.Identifier.Dns;
            var challengeConfig = managedCertificate.GetChallengeConfig(domain);

            if (pendingAuth.Challenges != null)
            {
                // from list of possible challenges, select the one we prefer to attempt
                var requiredChallenge = pendingAuth.Challenges.FirstOrDefault(c => c.ChallengeType == challengeConfig.ChallengeType);

                if (requiredChallenge != null)
                {
                    pendingAuth.AttemptedChallenge = requiredChallenge;
                    if (requiredChallenge.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP)
                    {
                        // perform http-01 challenge response
                        var check = await PerformChallengeResponse_Http01(log, iisManager, domain, managedCertificate, pendingAuth);

                        if (requestConfig.PerformExtensionlessConfigChecks)
                        {
                            pendingAuth.AttemptedChallenge.ConfigCheckedOK = check.IsSuccess;
                        }
                    }

                    if (requiredChallenge.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_SNI)
                    {
                        // perform tls-sni-01 challenge response
                        var check = PrepareChallengeResponse_TlsSni01(log, iisManager, domain, managedCertificate, pendingAuth);
                        if (requestConfig.PerformTlsSniBindingConfigChecks)
                        {
                            // set config check OK if all checks return true
                            pendingAuth.AttemptedChallenge.ConfigCheckedOK = check();
                        }
                    }

                    if (requiredChallenge.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_DNS)
                    {
                        // perform dns-01 challenge response
                        var check = await PerformChallengeResponse_Dns01(log, domain, managedCertificate, pendingAuth);

                        pendingAuth.AttemptedChallenge.ConfigCheckedOK    = check.Result.IsSuccess;
                        pendingAuth.AttemptedChallenge.ChallengeResultMsg = check.Result.Message;
                        pendingAuth.AttemptedChallenge.IsAwaitingUser     = check.IsAwaitingUser;
                        pendingAuth.AttemptedChallenge.PropagationSeconds = check.PropagationSeconds;
                    }
                }
            }
            return(pendingAuth);
        }
예제 #8
0
        /// <summary>
        /// Request creation of a new managed certificate. This only adds to the list of managed certificates and does not perform the certificate order or deployment etc.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public async Task <ActionResult <ManagedCertificate> > CreateManagedCertificate(ManagedCertificateCreateOptions item)
        {
            if (item.Domains == null || !item.Domains.Any())
            {
                return(new ActionResult <ManagedCertificate>("Managed Certificate must contain one or more domains", false));
            }

            item.Domains = item.Domains.Select(d => d.ToLowerInvariant().Trim())
                           .Distinct()
                           .ToList();

            var primaryDomain = item.Domains.First();
            var domainOptions = new ObservableCollection <DomainOption>(
                item.Domains
                .Select(d => new DomainOption {
                Domain = d, IsSelected = true
            })
                );

            domainOptions.First().IsPrimaryDomain = true;

            var managedCertificate = new ManagedCertificate
            {
                Id   = Guid.NewGuid().ToString(),
                Name = item.Title,
                IncludeInAutoRenew = true,
                ItemType           = ManagedCertificateType.SSL_ACME,
                UseStagingMode     = true,
                RequestConfig      = new CertRequestConfig
                {
                    PrimaryDomain           = item.Domains.First(),
                    SubjectAlternativeNames = item.Domains.ToArray(),
                    Challenges = new ObservableCollection <CertRequestChallengeConfig>(
                        new List <CertRequestChallengeConfig>
                    {
                        new CertRequestChallengeConfig {
                            ChallengeType = "http-01"
                        }
                    }),
                    PerformAutoConfig                = true,
                    PerformAutomatedCertBinding      = true,
                    PerformChallengeFileCopy         = true,
                    PerformExtensionlessConfigChecks = false
                },
                DomainOptions = domainOptions
            };

            try
            {
                var result = await _client.UpdateManagedCertificate(managedCertificate);

                if (result != null)
                {
                    return(new ActionResult <ManagedCertificate> {
                        IsSuccess = true, Message = "OK", Result = result
                    });
                }
                else
                {
                    return(new ActionResult <ManagedCertificate> {
                        IsSuccess = false, Message = "Failed to create managed certificate."
                    });
                }
            }
            catch (Exception exp)
            {
                return(new ActionResult <ManagedCertificate> {
                    IsSuccess = false, Message = "Failed to create managed certificate: " + exp.ToString()
                });
            }
        }
        public async Task<List<ActionResult>> Execute(DeploymentTaskExecutionParams execParams)
        {

            var definition = GetDefinition(execParams.Definition);

            var results = await Validate(execParams);
            if (results.Any())
            {
                return results;
            }

            var managedCert = ManagedCertificate.GetManagedCertificate(execParams.Subject);

            if (string.IsNullOrEmpty(managedCert.CertificatePath))
            {
                results.Add(new ActionResult("No certificate to deploy.", false));
                return results;

            }
            string vaultUri = execParams.Settings.Parameters.FirstOrDefault(c => c.Key == "vault_uri")?.Value;
            string vaultPath = execParams.Settings.Parameters.FirstOrDefault(c => c.Key == "vault_secret_path")?.Value;

            var httpClient = new HttpClient();
            httpClient.DefaultRequestHeaders.Add("X-Vault-Token", execParams.Credentials["api_token"]);
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            var vaultUrl = $"{vaultUri}{vaultPath}";

            byte[] pfxData = File.ReadAllBytes(managedCert.CertificatePath);

            var pfxPwd = "";

            var secret = new
            {
                data = new
                {
                    key = GetEncodedCertComponent("pemkey", pfxData, pfxPwd),
                    cert = GetEncodedCertComponent("pemcrt", pfxData, pfxPwd),
                    intermediates = GetEncodedCertComponent("pemchain", pfxData, pfxPwd),
                    pfx = GetEncodedCertComponent("pfxfull", pfxData, pfxPwd)
                }
            };

            /*
                {
                  "data": { },
                  "options": { },
                  "version": 0
                }";
            */

            var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(secret), System.Text.UnicodeEncoding.UTF8, "application/json");

            execParams.Log.Information($"Deploying to Vault: {vaultUrl}");

            var response = await httpClient.PostAsync(vaultUrl, content);

            if (response.IsSuccessStatusCode)
            {
                return results;
            }
            else
            {
                var error = await response.Content.ReadAsStringAsync();
                return new List<ActionResult> { new ActionResult("Vault storage failed: " + error, false) };
            }
        }
예제 #10
0
 public async Task <List <ActionStep> > GetPreviewActions(ManagedCertificate item)
 {
     return(await CertifyClient.PreviewActions(item));
 }
예제 #11
0
        /// <summary>
        /// Generate a list of actions which will be performed on the next renewal of this managed certificate, populating
        /// the description of each action with a Markdown format description
        /// </summary>
        /// <param name="item"></param>
        /// <param name="serverProvider"></param>
        /// <param name="certifyManager"></param>
        /// <returns></returns>
        public async Task <List <ActionStep> > GeneratePreview(
            ManagedCertificate item,
            ICertifiedServer serverProvider,
            ICertifyManager certifyManager,
            ICredentialsManager credentialsManager
            )
        {
            var newLine = "\r\n";
            var steps   = new List <ActionStep>();

            var stepIndex = 1;

            var hasDomains = true;

            var allTaskProviders = await certifyManager.GetDeploymentProviders();

            var certificateAuthorities = await certifyManager.GetCertificateAuthorities();

            // ensure defaults are applied for the deployment mode, overwriting any previous selections
            item.RequestConfig.ApplyDeploymentOptionDefaults();

            if (string.IsNullOrEmpty(item.RequestConfig.PrimaryDomain))
            {
                hasDomains = false;
            }

            if (hasDomains)
            {
                var allCredentials = await credentialsManager.GetStoredCredentials();

                var allDomains = new List <string> {
                    item.RequestConfig.PrimaryDomain
                };

                if (item.RequestConfig.SubjectAlternativeNames != null)
                {
                    allDomains.AddRange(item.RequestConfig.SubjectAlternativeNames);
                }
                allDomains = allDomains.Distinct().OrderBy(d => d).ToList();

                // certificate summary
                var certDescription = new StringBuilder();
                var ca = certificateAuthorities.FirstOrDefault(c => c.Id == item.CertificateAuthorityId);

                certDescription.AppendLine(
                    $"A new certificate will be requested from the *{ca?.Title ?? "Default"}* certificate authority for the following domains:"
                    );

                certDescription.AppendLine($"\n**{item.RequestConfig.PrimaryDomain}** (Primary Domain)");

                if (item.RequestConfig.SubjectAlternativeNames.Any(s => s != item.RequestConfig.PrimaryDomain))
                {
                    certDescription.AppendLine($" and will include the following *Subject Alternative Names*:");

                    foreach (var d in item.RequestConfig.SubjectAlternativeNames)
                    {
                        certDescription.AppendLine($"* {d} ");
                    }
                }

                steps.Add(new ActionStep
                {
                    Title       = "Summary",
                    Description = certDescription.ToString()
                });

                // validation steps :
                // TODO: preview description should come from the challenge type provider

                var challengeInfo = new StringBuilder();
                foreach (var challengeConfig in item.RequestConfig.Challenges)
                {
                    challengeInfo.AppendLine(
                        $"{newLine}Authorization will be attempted using the **{challengeConfig.ChallengeType}** challenge type." +
                        newLine
                        );

                    var matchingDomains = item.GetChallengeConfigDomainMatches(challengeConfig, allDomains);
                    if (matchingDomains.Any())
                    {
                        challengeInfo.AppendLine(
                            $"{newLine}The following matching domains will use this challenge: " + newLine
                            );

                        foreach (var d in matchingDomains)
                        {
                            challengeInfo.AppendLine($"{newLine} * {d}");
                        }

                        challengeInfo.AppendLine(
                            $"{newLine}**Please review the Deployment section below to ensure this certificate will be applied to the expected website bindings (if any).**" + newLine
                            );
                    }
                    else
                    {
                        challengeInfo.AppendLine(
                            $"{newLine}*No domains will match this challenge type.* Either the challenge is not required or domain matches are not fully configured."
                            );
                    }

                    challengeInfo.AppendLine(newLine);

                    if (challengeConfig.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP)
                    {
                        challengeInfo.AppendLine(
                            $"This will involve the creation of a randomly named (extensionless) text file for each domain (website) included in the certificate." +
                            newLine
                            );

                        if (CoreAppSettings.Current.EnableHttpChallengeServer)
                        {
                            challengeInfo.AppendLine(
                                $"The *Http Challenge Server* option is enabled. This will create a temporary web service on port 80 during validation. " +
                                $"This process co-exists with your main web server and listens for http challenge requests to /.well-known/acme-challenge/. " +
                                $"If you are using a web server on port 80 other than IIS (or other http.sys enabled server), that will be used instead." +
                                newLine
                                );
                        }

                        if (!string.IsNullOrEmpty(item.RequestConfig.WebsiteRootPath) && string.IsNullOrEmpty(challengeConfig.ChallengeRootPath))
                        {
                            challengeInfo.AppendLine(
                                $"The file will be created at the path `{item.RequestConfig.WebsiteRootPath}\\.well-known\\acme-challenge\\` " +
                                newLine
                                );
                        }

                        if (!string.IsNullOrEmpty(challengeConfig.ChallengeRootPath))
                        {
                            challengeInfo.AppendLine(
                                $"The file will be created at the path `{challengeConfig.ChallengeRootPath}\\.well-known\\acme-challenge\\` " +
                                newLine
                                );
                        }

                        challengeInfo.AppendLine(
                            $"The text file will need to be accessible from the URL `http://<yourdomain>/.well-known/acme-challenge/<randomfilename>` " +
                            newLine);

                        challengeInfo.AppendLine(
                            $"The issuing Certificate Authority will follow any redirection in place (such as rewriting the URL to *https*) but the initial request will be made via *http* on port 80. " +
                            newLine);
                    }

                    if (challengeConfig.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_DNS)
                    {
                        challengeInfo.AppendLine(
                            $"This will involve the creation of a DNS TXT record named `_acme-challenge.yourdomain.com` for each domain or subdomain included in the certificate. " +
                            newLine);

                        if (!string.IsNullOrEmpty(challengeConfig.ChallengeCredentialKey))
                        {
                            var creds = allCredentials.FirstOrDefault(c => c.StorageKey == challengeConfig.ChallengeCredentialKey);
                            if (creds != null)
                            {
                                challengeInfo.AppendLine(
                                    $"The following DNS API Credentials will be used:  **{creds.Title}** " + newLine);
                            }
                            else
                            {
                                challengeInfo.AppendLine(
                                    $"**Invalid credential settngs.**  The currently selected credential does not exist."
                                    );
                            }
                        }
                        else
                        {
                            challengeInfo.AppendLine(
                                $"No DNS API Credentials have been set.  API Credentials are normally required to make automatic updates to DNS records."
                                );
                        }

                        challengeInfo.AppendLine(
                            newLine + $"The issuing Certificate Authority will follow any redirection in place (such as a substitute CNAME pointing to another domain) but the initial request will be made against any of the domain's nameservers. "
                            );
                    }

                    if (!string.IsNullOrEmpty(challengeConfig.DomainMatch))
                    {
                        challengeInfo.AppendLine(
                            $"{newLine}This challenge type will be selected based on matching domain **{challengeConfig.DomainMatch}** ");
                    }
                    else
                    {
                        if (item.RequestConfig.Challenges.Count > 1)
                        {
                            challengeInfo.AppendLine(
                                $"{newLine}This challenge type will be selected for any domain not matched by another challenge. ");
                        }
                        else
                        {
                            challengeInfo.AppendLine(
                                $"{newLine}**This challenge type will be selected for all domains.**");
                        }
                    }

                    challengeInfo.AppendLine($"{newLine}---{newLine}");
                }

                steps.Add(new ActionStep
                {
                    Title       = $"{stepIndex}. Domain Validation",
                    Category    = "Validation",
                    Description = challengeInfo.ToString()
                });
                stepIndex++;

                // pre request tasks
                if (item.PreRequestTasks?.Any() == true)
                {
                    var substeps = item.PreRequestTasks.Select(t => new ActionStep {
                        Key = t.Id, Title = $"{t.TaskName} ({allTaskProviders.FirstOrDefault(p => p.Id == t.TaskTypeId)?.Title})", Description = t.Description
                    });

                    steps.Add(new ActionStep
                    {
                        Title       = $"{stepIndex}. Pre-Request Tasks",
                        Category    = "PreRequestTasks",
                        Description = $"Execute {substeps.Count()} Pre-Request Tasks",
                        Substeps    = substeps.ToList()
                    });
                    stepIndex++;
                }

                // cert request step
                var certRequest =
                    $"A Certificate Signing Request (CSR) will be submitted to the Certificate Authority, using the **{item.RequestConfig.CSRKeyAlg}** signing algorithm.";
                steps.Add(new ActionStep
                {
                    Title       = $"{stepIndex}. Certificate Request",
                    Category    = "CertificateRequest",
                    Description = certRequest
                });
                stepIndex++;


                // deployment & binding steps

                var deploymentDescription = new StringBuilder();
                var deploymentStep        = new ActionStep
                {
                    Title       = $"{stepIndex}. Deployment",
                    Category    = "Deployment",
                    Description = ""
                };

                if (
                    item.RequestConfig.DeploymentSiteOption == DeploymentOption.Auto ||
                    item.RequestConfig.DeploymentSiteOption == DeploymentOption.AllSites ||
                    item.RequestConfig.DeploymentSiteOption == DeploymentOption.SingleSite
                    )
                {
                    // deploying to single or multiple Site
                    if (item.ItemType == ManagedCertificateType.SSL_LetsEncrypt_LocalIIS)
                    {
                        if (item.RequestConfig.DeploymentBindingMatchHostname)
                        {
                            deploymentDescription.AppendLine(
                                "* Deploy to hostname bindings matching certificate domains.");
                        }

                        if (item.RequestConfig.DeploymentBindingBlankHostname)
                        {
                            deploymentDescription.AppendLine("* Deploy to bindings with blank hostname.");
                        }

                        if (item.RequestConfig.DeploymentBindingReplacePrevious)
                        {
                            deploymentDescription.AppendLine("* Deploy to bindings with previous certificate.");
                        }

                        if (item.RequestConfig.DeploymentBindingOption == DeploymentBindingOption.AddOrUpdate)
                        {
                            deploymentDescription.AppendLine("* Add or Update https bindings as required");
                        }

                        if (item.RequestConfig.DeploymentBindingOption == DeploymentBindingOption.UpdateOnly)
                        {
                            deploymentDescription.AppendLine("* Update https bindings as required (no auto-created https bindings)");
                        }

                        if (item.RequestConfig.DeploymentSiteOption == DeploymentOption.SingleSite)
                        {
                            if (!string.IsNullOrEmpty(item.ServerSiteId))
                            {
                                try
                                {
                                    var siteInfo = await serverProvider.GetSiteById(item.ServerSiteId);

                                    deploymentDescription.AppendLine($"## Deploying to Site" + newLine + newLine +
                                                                     $"`{siteInfo.Name}`" + newLine);
                                }
                                catch (Exception exp)
                                {
                                    deploymentDescription.AppendLine($"Error: **cannot identify selected site.** {exp.Message} ");
                                }
                            }
                        }
                        else
                        {
                            deploymentDescription.AppendLine($"## Deploying to all matching sites:");
                        }

                        // add deployment sub-steps (if any)
                        var bindingRequest = await certifyManager.DeployCertificate(item, null, true);

                        if (bindingRequest.Actions != null)
                        {
                            deploymentStep.Substeps = bindingRequest.Actions;
                        }

                        if (bindingRequest.Actions == null || !bindingRequest.Actions.Any())
                        {
                            deploymentStep.Substeps = new List <ActionStep>
                            {
                                new ActionStep {
                                    Description = newLine + "**There are no matching targets to deploy to. Certificate will be stored but currently no bindings will be updated.**"
                                }
                            };
                        }
                        else
                        {
                            deploymentDescription.AppendLine(" Action | Site | Binding ");
                            deploymentDescription.Append(" ------ | ---- | ------- ");
                        }
                    }
                    else
                    {
                        // no website selected, maybe validating http with a manually specified path
                        deploymentDescription.AppendLine($"Manual deployment to site.");
                    }
                }
                else if (item.RequestConfig.DeploymentSiteOption == DeploymentOption.DeploymentStoreOnly)
                {
                    deploymentDescription.AppendLine("* The certificate will be saved to the local machines Certificate Store only (Personal/My Store)");
                }
                else if (item.RequestConfig.DeploymentSiteOption == DeploymentOption.NoDeployment)
                {
                    deploymentDescription.AppendLine("* The certificate will be saved to local disk only.");
                }

                deploymentStep.Description = deploymentDescription.ToString();

                steps.Add(deploymentStep);
                stepIndex++;


                // post request deployment tasks
                if (item.PostRequestTasks?.Any() == true)
                {
                    var substeps = item.PostRequestTasks.Select(t => new ActionStep {
                        Key = t.Id, Title = $"{t.TaskName} ({allTaskProviders.FirstOrDefault(p => p.Id == t.TaskTypeId)?.Title})", Description = t.Description
                    });

                    steps.Add(new ActionStep
                    {
                        Title       = $"{stepIndex}. Post-Request (Deployment) Tasks",
                        Category    = "PostRequestTasks",
                        Description = $"Execute {substeps.Count()} Post-Request Tasks",
                        Substeps    = substeps.ToList()
                    });
                    stepIndex++;
                }

                stepIndex = steps.Count;
            }
            else
            {
                steps.Add(new ActionStep
                {
                    Title       = "Certificate has no domains",
                    Description = "No domains have been added to this certificate, so a certificate cannot be requested. Each certificate requires a primary domain (a 'subject') and an optional list of additional domains (subject alternative names)."
                });
            }

            return(steps);
        }
 public Task <StatusMessage> RevokeCertificate(ILog log, ManagedCertificate managedCertificate)
 {
     throw new System.NotImplementedException();
 }
예제 #13
0
        /// <summary>
        /// Add identifiers to a managed cert e.g. certify add 89ccaf11-d7c4-427a-b491-9d3582835c48 test1.test.com;test2.test.com (optionally with --perform-request and 'new' instead of id)
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        internal async Task AddIdentifiers(string[] args)
        {
            var managedCertId = args[1];
            var domains       = args[2]?.Split(";, ".ToCharArray());

            var performRequestNow = false;

            if (args.Contains("--perform-request"))
            {
                performRequestNow = true;
            }

            if (domains != null && domains.Any())
            {
                ManagedCertificate managedCert = null;

                if (managedCertId == "new")
                {
                    if (!IsRegistered())
                    {
                        Console.WriteLine("CLI automation is only available in the registered version of this application.");
                        return;
                    }

                    // create a new managed cert with http validation and auto deployment
                    managedCert = new ManagedCertificate
                    {
                        Id   = Guid.NewGuid().ToString(),
                        Name = domains[0],
                        IncludeInAutoRenew = true,
                        ItemType           = ManagedCertificateType.SSL_ACME
                    };
                    managedCert.RequestConfig.Challenges = new System.Collections.ObjectModel.ObservableCollection <CertRequestChallengeConfig>(
                        new List <CertRequestChallengeConfig> {
                        new CertRequestChallengeConfig {
                            ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP
                        }
                    });

                    managedCert.RequestConfig.DeploymentSiteOption = DeploymentOption.Auto;
                }
                else
                {
                    // update an existing managed cert
                    managedCert = await _certifyClient.GetManagedCertificate(managedCertId);
                }


                foreach (var d in domains.Where(i => !string.IsNullOrEmpty(i)).Select(i => i.Trim().ToLower()))
                {
                    var domainOption = managedCert.DomainOptions.FirstOrDefault(o => o.Domain == d);
                    if (domainOption != null)
                    {
                        domainOption.IsSelected = true;
                    }
                    else
                    {
                        managedCert.DomainOptions.Add(new DomainOption {
                            Domain = d, IsManualEntry = true, IsSelected = true, Type = "dns"
                        });
                    }

                    if (!managedCert.RequestConfig.SubjectAlternativeNames.Contains(d))
                    {
                        var newSanList = managedCert.RequestConfig.SubjectAlternativeNames.ToList();
                        newSanList.Add(d);
                        managedCert.RequestConfig.SubjectAlternativeNames = newSanList.ToArray();
                    }
                }

                // check we still have a primary domain, if not selected a default one
                if (!managedCert.DomainOptions.Any(o => o.IsPrimaryDomain))
                {
                    var defaultIdentifier = managedCert.DomainOptions.FirstOrDefault(o => o.IsSelected);
                    if (defaultIdentifier != null)
                    {
                        defaultIdentifier.IsPrimaryDomain       = true;
                        managedCert.RequestConfig.PrimaryDomain = defaultIdentifier.Domain;
                    }
                }

                var updatedManagedCert = await _certifyClient.UpdateManagedCertificate(managedCert);

                if (updatedManagedCert != null && performRequestNow)
                {
                    await _certifyClient.BeginCertificateRequest(updatedManagedCert.Id, true);
                }
            }
        }
예제 #14
0
        public async Task BindingMatchTestsVarious()
        {
            /*
             *  Single Site - Default Match
             *  www.test.com
             *  test.com*/

            /*
             *
             * All Sites(*.test.com)
             * All Sites(*.test.co.uk)
             */

            var managedCertificate = new ManagedCertificate

            {
                Id             = Guid.NewGuid().ToString(),
                Name           = "TestSite..",
                GroupId        = "test",
                UseStagingMode = true,
                RequestConfig  = new CertRequestConfig
                {
                    PrimaryDomain = "test.com",
                    Challenges    = new ObservableCollection <CertRequestChallengeConfig>(
                        new List <CertRequestChallengeConfig>
                    {
                        new CertRequestChallengeConfig {
                            ChallengeType = "dns-01"
                        }
                    }),
                    PerformAutomatedCertBinding = true,
                    WebsiteRootPath             = "c:\\inetpub\\wwwroot",
                    DeploymentSiteOption        = DeploymentOption.SingleSite
                },
                ItemType = ManagedCertificateType.SSL_ACME
            };

            var bindingManager = new BindingDeploymentManager();

            var deploymentTarget = new MockBindingDeploymentTarget();

            deploymentTarget.AllBindings = _allSites;

            managedCertificate.ServerSiteId = "ShouldNotMatch";
            var preview = await bindingManager.StoreAndDeployManagedCertificate(deploymentTarget, managedCertificate, null, pfxPwd : "", isPreviewOnly : true);

            Assert.IsFalse(preview.Any(), " Should not match any bindings");

            managedCertificate.ServerSiteId = "1.1";
            preview = await bindingManager.StoreAndDeployManagedCertificate(deploymentTarget, managedCertificate, null, pfxPwd : "", isPreviewOnly : true);

            Assert.IsFalse(preview.Any(), "Should not match any bindings (same domain, different sudomains no wildcard)");

            managedCertificate.ServerSiteId = "1";
            preview = await bindingManager.StoreAndDeployManagedCertificate(deploymentTarget, managedCertificate, null, pfxPwd : "", isPreviewOnly : true);

            Assert.IsTrue(preview.Count == 1, "Should match one binding");

            managedCertificate.ServerSiteId = "1";
            managedCertificate.RequestConfig.PrimaryDomain = "*.test.com";
            preview = await bindingManager.StoreAndDeployManagedCertificate(deploymentTarget, managedCertificate, null, pfxPwd : "", isPreviewOnly : true);

            Assert.IsTrue(preview.Count == 1, "Should match 1 binding (root level domain should be ignored using wildcard)");

            managedCertificate.ServerSiteId = "1";
            managedCertificate.RequestConfig.DeploymentSiteOption = DeploymentOption.AllSites;
            managedCertificate.RequestConfig.PrimaryDomain        = "test.com";
            preview = await bindingManager.StoreAndDeployManagedCertificate(deploymentTarget, managedCertificate, null, pfxPwd : "", isPreviewOnly : true);

            Assert.IsTrue(preview.Count == 1, "Should match 1 binding");

            managedCertificate.ServerSiteId = "1";
            managedCertificate.RequestConfig.DeploymentSiteOption = DeploymentOption.AllSites;
            managedCertificate.RequestConfig.PrimaryDomain        = "*.test.com";
            preview = await bindingManager.StoreAndDeployManagedCertificate(deploymentTarget, managedCertificate, null, pfxPwd : "", isPreviewOnly : true);

            Assert.IsTrue(preview.Count == 3, "Should match 3 bindings across all sites");

            managedCertificate.ServerSiteId = "5";
            managedCertificate.RequestConfig.DeploymentSiteOption = DeploymentOption.AllSites;
            managedCertificate.RequestConfig.PrimaryDomain        = "altport.com";

            preview = await bindingManager.StoreAndDeployManagedCertificate(deploymentTarget, managedCertificate, null, pfxPwd : "", isPreviewOnly : true);

            Assert.IsTrue(preview.Count == 2, "Should match 2 bindings across all sites");
            Assert.IsTrue(preview.Count(b => b.Category == "Deployment.UpdateBinding" && b.Description.Contains(":9000")) == 1, "Should have 1 port 9000 binding");
            Assert.IsTrue(preview.Count(b => b.Category == "Deployment.UpdateBinding" && b.Description.Contains(":9001")) == 1, "Should have 1 port 9001 binding");

            foreach (var a in preview)
            {
                System.Diagnostics.Debug.WriteLine(a.Description);
            }
        }
예제 #15
0
        public async Task TestBindingMatch()
        {
            // create test site with mix of hostname and IP only bindings
            var testStr = "abc123";

            PrimaryTestDomain = $"test-{testStr}." + PrimaryTestDomain;

            var testBindingSiteName = "TestAllBinding_" + testStr;

            var testSiteDomain = "test" + testStr + "." + PrimaryTestDomain;

            if (await iisManager.SiteExists(testBindingSiteName))
            {
                await iisManager.DeleteSite(testBindingSiteName);
            }

            // create site with IP all unassigned, no hostname
            var site = await iisManager.CreateSite(testBindingSiteName, "", PrimaryIISRoot, "DefaultAppPool", port : testSiteHttpPort);

            // add another hostname binding (matching cert and not matching cert)
            var testDomains = new List <string> {
                testSiteDomain, "label1." + testSiteDomain, "nested.label." + testSiteDomain
            };
            await iisManager.AddSiteBindings(site.Id.ToString(), testDomains, testSiteHttpPort);

            // get fresh instance of site since updates
            site = await iisManager.GetIISSiteById(site.Id.ToString());

            var bindingsBeforeApply = site.Bindings.ToList();

            Assert.AreEqual(site.Name, testBindingSiteName);

            var dummyCertPath      = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx";
            var managedCertificate = new ManagedCertificate
            {
                Id             = Guid.NewGuid().ToString(),
                Name           = testSiteName,
                ServerSiteId   = site.Id.ToString(),
                UseStagingMode = true,
                RequestConfig  = new CertRequestConfig
                {
                    PrimaryDomain = testSiteDomain,
                    Challenges    = new ObservableCollection <CertRequestChallengeConfig>(
                        new List <CertRequestChallengeConfig>
                    {
                        new CertRequestChallengeConfig {
                            ChallengeType = "http-01"
                        }
                    }),
                    PerformAutoConfig                = true,
                    PerformAutomatedCertBinding      = true,
                    PerformChallengeFileCopy         = true,
                    PerformExtensionlessConfigChecks = true,
                    WebsiteRootPath                  = testSitePath,
                    DeploymentSiteOption             = DeploymentOption.SingleSite,
                    DeploymentBindingMatchHostname   = true,
                    DeploymentBindingBlankHostname   = true,
                    DeploymentBindingReplacePrevious = true,
                    SubjectAlternativeNames          = new string[] { testSiteDomain, "label1." + testSiteDomain }
                },
                ItemType        = ManagedCertificateType.SSL_ACME,
                CertificatePath = dummyCertPath
            };

            var actions = await new BindingDeploymentManager().StoreAndDeploy(
                iisManager.GetDeploymentTarget(),
                managedCertificate, dummyCertPath, "",
                false);

            foreach (var a in actions)
            {
                System.Console.WriteLine(a.Description);
            }
            // get cert info to compare hash
            var certInfo = CertificateManager.LoadCertificate(managedCertificate.CertificatePath);

            // check IIS site bindings
            site = await iisManager.GetIISSiteById(site.Id.ToString());

            var finalBindings = site.Bindings.ToList();

            Assert.IsTrue(bindingsBeforeApply.Count < finalBindings.Count, "Should have new bindings");

            try
            {
                // check we have the new bindings we expected

                // blank hostname binding
                var testBinding = finalBindings.FirstOrDefault(b => b.Host == "" && b.Protocol == "https");
                Assert.IsTrue(IsCertHashEqual(testBinding.CertificateHash, certInfo.GetCertHash()), "Blank hostname binding should be added and have certificate set");

                // TODO: testDomains includes matches and not matches to test
                foreach (var d in testDomains)
                {
                    // check san domain now has an https binding
                    testBinding = finalBindings.FirstOrDefault(b => b.Host == d && b.Protocol == "https");
                    if (!d.StartsWith("nested."))
                    {
                        Assert.IsNotNull(testBinding);
                        Assert.IsTrue(IsCertHashEqual(testBinding.CertificateHash, certInfo.GetCertHash()), "hostname binding should be added and have certificate set");
                    }
                    else
                    {
                        Assert.IsNull(testBinding, "nested binding should be null");
                    }
                }

                // check existing bindings have been updated as expected

                /*foreach (var b in finalBindings)
                 * {
                 *  if (b.Protocol == "https")
                 *  {
                 *      // check this item is one we should have included (is matching domain or has
                 *      // no hostname)
                 *      bool shouldBeIncluded = false;
                 *
                 *      if (!String.IsNullOrEmpty(b.Host))
                 *      {
                 *          if (testDomains.Contains(b.Host))
                 *          {
                 *              shouldBeIncluded = true;
                 *          }
                 *      }
                 *      else
                 *      {
                 *          shouldBeIncluded = true;
                 *      }
                 *
                 *      bool isCertMatch = StructuralComparisons.StructuralEqualityComparer.Equals(b.CertificateHash, certInfo.GetCertHash());
                 *
                 *      if (shouldBeIncluded)
                 *      {
                 *          Assert.IsTrue(isCertMatch, "Binding should have been updated with cert hash but was not.");
                 *      }
                 *      else
                 *      {
                 *          Assert.IsFalse(isCertMatch, "Binding should not have been updated with cert hash but was.");
                 *      }
                 *  }
                 * }*/
            }
            finally
            {
                // clean up IIS either way
                await iisManager.DeleteSite(testBindingSiteName);

                if (certInfo != null)
                {
                    CertificateManager.RemoveCertificate(certInfo);
                }
            }
        }
예제 #16
0
 public Task <CertificateRequestResult> PerformCertificateRequest(ILog log, ManagedCertificate managedCertificate, IProgress <RequestProgressState> progress = null, bool resumePaused = false, bool skipRequest = false, bool failOnSkip = false)
 {
     throw new NotImplementedException();
 }
예제 #17
0
        /// <summary>
        /// Add identifiers to a managed cert e.g. certify add 89ccaf11-d7c4-427a-b491-9d3582835c48 test1.test.com;test2.test.com (optionally with --perform-request and 'new' instead of id)
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        internal async Task AddIdentifiers(string[] args)
        {
            if (args.Length < 3)
            {
                Console.WriteLine("Not enough arguments");
                return;
            }

            var managedCertId = args[1];
            var domains       = args[2]?.Split(";, ".ToCharArray());

            var performRequestNow = false;

            if (args.Contains("--perform-request"))
            {
                performRequestNow = true;
            }

            if (domains != null && domains.Any())
            {
                ManagedCertificate managedCert = null;

                if (managedCertId == "new")
                {
                    InitPlugins();

                    if (!IsRegistered())
                    {
                        Console.WriteLine("CLI automation is only available in the licensed version of this application.");
                        return;
                    }

                    // optional load a single managed certificate tempalte from json
                    ManagedCertificate templateCert = null;

                    var jsonArgIndex = Array.IndexOf(args, "--template");

                    if (jsonArgIndex != -1)
                    {
                        if (args.Length + 1 >= jsonArgIndex + 1)
                        {
                            var pathArg = args[jsonArgIndex + 1];

                            try
                            {
                                var jsonTemplate = System.IO.File.ReadAllText(pathArg);
                                templateCert = JsonConvert.DeserializeObject <ManagedCertificate>(jsonTemplate);
                            }
                            catch (Exception)
                            {
                                Console.WriteLine($"Failed to read or parse managed certificate template json. " + pathArg);
                            }
                        }
                        else
                        {
                            Console.WriteLine($"Input file path argument is required for json template.");
                        }
                    }

                    // create a new managed cert with http validation and auto deployment
                    if (templateCert == null)
                    {
                        managedCert = new ManagedCertificate
                        {
                            Id   = Guid.NewGuid().ToString(),
                            Name = domains[0],
                            IncludeInAutoRenew = true,
                            ItemType           = ManagedCertificateType.SSL_ACME
                        };

                        managedCert.RequestConfig.Challenges = new System.Collections.ObjectModel.ObservableCollection <CertRequestChallengeConfig>(
                            new List <CertRequestChallengeConfig> {
                            new CertRequestChallengeConfig {
                                ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP
                            }
                        });

                        managedCert.RequestConfig.DeploymentSiteOption = DeploymentOption.Auto;
                    }
                    else
                    {
                        managedCert = templateCert.CopyAsTemplate();



                        // if no managed cert name specifed, use first domain
                        if (string.IsNullOrEmpty(managedCert.Name))
                        {
                            managedCert.Name = domains[0];
                        }
                    }
                }
                else
                {
                    // update an existing managed cert
                    managedCert = await _certifyClient.GetManagedCertificate(managedCertId);
                }


                foreach (var d in domains.Where(i => !string.IsNullOrEmpty(i)).Select(i => i.Trim().ToLower()))
                {
                    var domainOption = managedCert.DomainOptions.FirstOrDefault(o => o.Domain == d);
                    if (domainOption != null)
                    {
                        domainOption.IsSelected = true;
                    }
                    else
                    {
                        managedCert.DomainOptions.Add(new DomainOption {
                            Domain = d, IsManualEntry = true, IsSelected = true, Type = "dns"
                        });
                    }

                    if (!managedCert.RequestConfig.SubjectAlternativeNames.Contains(d))
                    {
                        var newSanList = managedCert.RequestConfig.SubjectAlternativeNames.ToList();
                        newSanList.Add(d);
                        managedCert.RequestConfig.SubjectAlternativeNames = newSanList.ToArray();
                    }
                }

                // check we still have a primary domain, if not selected a default one
                if (!managedCert.DomainOptions.Any(o => o.IsPrimaryDomain))
                {
                    var defaultIdentifier = managedCert.DomainOptions.FirstOrDefault(o => o.IsSelected);
                    if (defaultIdentifier != null)
                    {
                        defaultIdentifier.IsPrimaryDomain       = true;
                        managedCert.RequestConfig.PrimaryDomain = defaultIdentifier.Domain;
                    }
                }

                var updatedManagedCert = await _certifyClient.UpdateManagedCertificate(managedCert);

                if (updatedManagedCert != null && performRequestNow)
                {
                    await _certifyClient.BeginCertificateRequest(updatedManagedCert.Id, true, false);
                }
            }
        }
예제 #18
0
 public Task <ManagedCertificate> UpdateManagedCertificate(ManagedCertificate site)
 {
     throw new NotImplementedException();
 }
예제 #19
0
        public async Task TestCreateManyManagedCertificates()
        {
            var itemManager = new ItemManager(TEST_PATH);

            var testItem = new ManagedCertificate
            {
                Id             = Guid.NewGuid().ToString(),
                Name           = "TestSite..",
                GroupId        = "test",
                UseStagingMode = true,
                RequestConfig  = new CertRequestConfig
                {
                    PrimaryDomain = "testsite.com",
                    Challenges    = new ObservableCollection <CertRequestChallengeConfig>(
                        new List <CertRequestChallengeConfig>
                    {
                        new CertRequestChallengeConfig {
                            ChallengeType = "http-01"
                        }
                    }),
                    PerformAutoConfig                = true,
                    PerformAutomatedCertBinding      = true,
                    PerformChallengeFileCopy         = true,
                    PerformExtensionlessConfigChecks = true
                },
                ItemType = ManagedCertificateType.SSL_ACME
            };

            // create competing sets of tasks to create managed items

            var numItems = 100; // 100,000 items takes about 40 mins to generate

            // now attempt async creation of bindings
            var taskSet = new Task[numItems];

            var timer = Stopwatch.StartNew();

            for (var i = 0; i < numItems; i++)
            {
                testItem.Name = "MultiTest_" + i;
                testItem.Id   = Guid.NewGuid().ToString();

                taskSet[i] = itemManager.UpdatedManagedCertificate(testItem);
            }

            // create a large number of managed items, to see if we encounter isses saving/loading from DB async
            try
            {
                await Task.WhenAll(taskSet);

                timer.Stop();

                System.Diagnostics.Debug.WriteLine($"Created {numItems} in { timer.ElapsedMilliseconds}ms avg:{ timer.ElapsedMilliseconds / numItems}ms");
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                // now clean up
#if DEBUG
                await itemManager.DeleteManagedCertificatesByName("MultiTest_");
#endif
            }
        }
예제 #20
0
        public async Task <List <ManagedCertificate> > GetManagedCertificates(ManagedCertificateFilter filter = null)
        {
            var list = new List <ManagedCertificate>();

            if (await IsPresent())
            {
                var directorySearch = new DirectoryInfo(Path.Combine(_settingsPath, "renewal"));

                if (directorySearch.Exists)
                {
                    var configFiles = directorySearch.GetFiles("*.conf", SearchOption.AllDirectories);

                    foreach (var config in configFiles)
                    {
                        try
                        {
                            var id = config.Name.Replace(".conf", "");

                            var managedCert = new ManagedCertificate
                            {
                                Id         = "certbot://" + id,
                                Name       = id,
                                ItemType   = ManagedCertificateType.SSL_ExternallyManaged,
                                SourceId   = Definition.Id,
                                SourceName = Definition.Title
                            };

                            var certFile = new FileInfo(Path.Combine(_settingsPath, "live", id, "cert.pem"));
                            if (certFile.Exists)
                            {
                                try
                                {
                                    var cert       = Certify.Management.CertificateManager.ReadCertificateFromPem(certFile.FullName);
                                    var parsedCert = new System.Security.Cryptography.X509Certificates.X509Certificate2(cert.GetEncoded()); managedCert.DateStart = cert.NotBefore;
                                    managedCert.DateExpiry                = cert.NotAfter;
                                    managedCert.DateRenewed               = cert.NotBefore;
                                    managedCert.DateLastRenewalAttempt    = cert.NotBefore;
                                    managedCert.CertificateThumbprintHash = parsedCert.Thumbprint;
                                    managedCert.CertificatePath           = certFile.FullName;
                                    managedCert.LastRenewalStatus         = RequestState.Success;

                                    if (cert.NotAfter < DateTime.Now.AddDays(29))
                                    {
                                        // assume certs with less than 30 days left have failed to renew
                                        managedCert.LastRenewalStatus     = RequestState.Error;
                                        managedCert.RenewalFailureMessage = "Check certbot configuration. This certificate will expire in less than 30 days and has not yet automatically renewed.";
                                    }

                                    managedCert.RequestConfig = new CertRequestConfig
                                    {
                                        PrimaryDomain = parsedCert.SubjectName.Name
                                    };

                                    var sn = ((System.Collections.ArrayList)cert.GetSubjectAlternativeNames());

                                    List <string> sans = new List <string>();
                                    foreach (System.Collections.ArrayList s in sn)
                                    {
                                        sans.Add(s[1].ToString());
                                    }

                                    managedCert.RequestConfig.SubjectAlternativeNames = sans.ToArray();

                                    managedCert.DomainOptions = new System.Collections.ObjectModel.ObservableCollection <DomainOption>
                                    {
                                        new DomainOption {
                                            Domain          = managedCert.RequestConfig.PrimaryDomain,
                                            IsPrimaryDomain = true,
                                            IsManualEntry   = true,
                                            IsSelected      = true
                                        }
                                    };
                                }
                                catch (Exception exp)
                                {
                                    System.Diagnostics.Debug.WriteLine("Failed to parse cert: " + exp);
                                }
                            }

                            //var cfg = ParseIni(File.ReadAllText(config.FullName));

                            managedCert.IsChanged = false;
                            list.Add(managedCert);
                        }
                        catch (Exception exp)
                        {
                            System.Diagnostics.Debug.WriteLine($"Failed to parse config: [{config}] " + exp);
                        }
                    }
                }
            }
            return(list);
        }
예제 #21
0
        /// <summary>
        /// Prepares IIS to respond to a http-01 challenge
        /// </summary>
        /// <returns> Test the challenge response locally. </returns>
        private async Task <ActionResult> PerformChallengeResponse_Http01(ILog log, ICertifiedServer iisManager, string domain, ManagedCertificate managedCertificate, PendingAuthorization pendingAuth)
        {
            var requestConfig = managedCertificate.RequestConfig;
            var httpChallenge = pendingAuth.Challenges.FirstOrDefault(c => c.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP);

            if (httpChallenge == null)
            {
                var msg = $"No http challenge to complete for {managedCertificate.Name}. Request cannot continue.";
                log.Warning(msg);
                return(new ActionResult {
                    IsSuccess = false, Message = msg
                });
            }

            log.Information($"Preparing challenge response for Let's Encrypt server to check at: {httpChallenge.ResourceUri} with content {httpChallenge.Value}");
            log.Information("If the challenge response file is not accessible at this exact URL the validation will fail and a certificate will not be issued.");

            // get website root path (from challenge config or fallback to deprecated
            // WebsiteRootPath), expand environment variables if required
            var websiteRootPath = requestConfig.WebsiteRootPath;
            var challengeConfig = managedCertificate.GetChallengeConfig(domain);

            if (!string.IsNullOrEmpty(challengeConfig.ChallengeRootPath))
            {
                websiteRootPath = challengeConfig.ChallengeRootPath;
            }

            if (!string.IsNullOrEmpty(managedCertificate.ServerSiteId))
            {
                var siteInfo = await iisManager.GetSiteById(managedCertificate.ServerSiteId);

                if (siteInfo == null)
                {
                    return(new ActionResult {
                        IsSuccess = false, Message = "IIS Website unavailable. Site may be removed or IIS is unavailable"
                    });
                }

                // if website root path not specified, determine it now
                if (string.IsNullOrEmpty(websiteRootPath))
                {
                    websiteRootPath = siteInfo.Path;
                }

                if (!string.IsNullOrEmpty(websiteRootPath) && websiteRootPath.Contains("%"))
                {
                    // if websiteRootPath contains %websiteroot% variable, replace that with the
                    // current physical path for the site
                    if (websiteRootPath.Contains("%websiteroot%"))
                    {
                        // sets env variable for this process only
                        Environment.SetEnvironmentVariable("websiteroot", siteInfo.Path);
                    }
                    // expand any environment variables present in site path
                    websiteRootPath = Environment.ExpandEnvironmentVariables(websiteRootPath);
                }
            }

            log.Information("Using website path {path}", websiteRootPath);

            if (string.IsNullOrEmpty(websiteRootPath) || !Directory.Exists(websiteRootPath))
            {
                // our website no longer appears to exist on disk, continuing would potentially
                // create unwanted folders, so it's time for us to give up

                var msg = $"The website root path for {managedCertificate.Name} could not be determined. Request cannot continue.";
                log.Error(msg);
                return(new ActionResult {
                    IsSuccess = false, Message = msg
                });
            }

            // copy temp file to path challenge expects in web folder
            var destFile = Path.Combine(websiteRootPath, httpChallenge.ResourcePath);
            var destPath = Path.GetDirectoryName(destFile);

            if (!Directory.Exists(destPath))
            {
                try
                {
                    Directory.CreateDirectory(destPath);
                }
                catch (Exception exp)
                {
                    // failed to create directory, probably permissions or may be invalid config

                    var msg = $"Pre-config check failed: Could not create directory: {destPath}";
                    log.Error(exp, msg);
                    return(new ActionResult {
                        IsSuccess = false, Message = msg
                    });
                }
            }

            // copy challenge response to web folder /.well-known/acme-challenge. Check if it already
            // exists (as in 'configcheck' file) as can cause conflicts.
            if (!File.Exists(destFile) || !destFile.EndsWith("configcheck"))
            {
                try
                {
                    File.WriteAllText(destFile, httpChallenge.Value);
                }
                catch (Exception exp)
                {
                    // failed to create configcheck file, probably permissions or may be invalid config

                    var msg = $"Pre-config check failed: Could not create file: {destFile}";
                    log.Error(exp, msg);
                    return(new ActionResult {
                        IsSuccess = false, Message = msg
                    });
                }
            }

            // prepare cleanup - should this be configurable? Because in some case many sites
            // renewing may all point to the same web root, we keep the configcheck file
            pendingAuth.Cleanup = () =>
            {
                if (!destFile.EndsWith("configcheck") && File.Exists(destFile))
                {
                    log.Debug("Challenge Cleanup: Removing {file}", destFile);
                    try
                    {
                        File.Delete(destFile);
                    }
                    catch { }
                }
            };

            // if config checks are enabled but our last renewal was successful, skip auto config
            // until we have failed twice
            if (requestConfig.PerformExtensionlessConfigChecks)
            {
                if (managedCertificate.DateRenewed != null && managedCertificate.RenewalFailureCount < 2)
                {
                    return(new ActionResult {
                        IsSuccess = true, Message = $"Skipping URL access checks and auto config (if applicable): {httpChallenge.ResourceUri}. Will resume checks if renewal failure count exceeds 2 attempts."
                    });
                }

                // first check if it already works with no changes
                if (await _netUtil.CheckURL(log, httpChallenge.ResourceUri))
                {
                    return(new ActionResult {
                        IsSuccess = true, Message = $"Verified URL is accessible: {httpChallenge.ResourceUri}"
                    });
                }

                // initial check didn't work, if auto config enabled attempt to find a working config
                if (requestConfig.PerformAutoConfig)
                {
                    // FIXME: need to only overwrite config we have auto populated, not user
                    // specified config, compare to our preconfig and only overwrite if same as ours?
                    // Or include preset key in our config, or make behaviour configurable
                    LogAction($"Pre-config check failed: Auto-config will overwrite existing config: {destPath}\\web.config");

                    var configOptions = Directory.EnumerateFiles(Environment.CurrentDirectory + "\\Scripts\\Web.config\\", "*.config");

                    foreach (var configFile in configOptions)
                    {
                        // create a web.config for extensionless files, then test it (make a request
                        // for the extensionless configcheck file over http)

                        var webConfigContent = File.ReadAllText(configFile);

                        // no existing config, attempt auto config and perform test
                        LogAction($"Testing config alternative: " + configFile);

                        try
                        {
                            System.IO.File.WriteAllText(destPath + "\\web.config", webConfigContent);
                        }
                        catch (Exception exp)
                        {
                            LogAction($"Failed to write config: " + exp.Message);
                        }

                        if (await _netUtil.CheckURL(log, httpChallenge.ResourceUri))
                        {
                            return(new ActionResult {
                                IsSuccess = true, Message = $"Verified URL is accessible: {httpChallenge.ResourceUri}"
                            });
                        }
                    }
                }

                // failed to auto configure or confirm resource is accessible
                return(new ActionResult
                {
                    IsSuccess = false,
                    Message = $"Could not verify URL is accessible: {httpChallenge.ResourceUri}"
                });
            }
            else
            {
                return(new ActionResult
                {
                    IsSuccess = false,
                    Message = $"Config checks disabled. Did not verify URL access: {httpChallenge.ResourceUri}"
                });
            }
        }
예제 #22
0
 public ProgressLogSink(IProgress <RequestProgressState> progress, ManagedCertificate item, ICertifyManager certifyManager)
 {
     _progress       = progress;
     _item           = item;
     _certifyManager = certifyManager;
 }
예제 #23
0
        private Func <bool> PrepareChallengeResponse_TlsSni01(ILog log, ICertifiedServer iisManager, string domain, ManagedCertificate managedCertificate, PendingAuthorization pendingAuth)
        {
            var requestConfig = managedCertificate.RequestConfig;

            var tlsSniChallenge = pendingAuth.Challenges.FirstOrDefault(c => c.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_SNI);

            if (tlsSniChallenge == null)
            {
                log.Warning($"No tls-sni-01 challenge to complete for {managedCertificate.Name}. Request cannot continue.");
                return(() => false);
            }

            var sha256 = System.Security.Cryptography.SHA256.Create();

            var z = new byte[tlsSniChallenge.HashIterationCount][];

            // compute n sha256 hashes, where n=challengedata.iterationcount
            z[0] = sha256.ComputeHash(Encoding.UTF8.GetBytes(tlsSniChallenge.Value));

            for (var i = 1; i < z.Length; i++)
            {
                z[i] = sha256.ComputeHash(z[i - 1]);
            }

            // generate certs and install iis bindings
            var cleanupQueue = new List <Action>();

            var checkQueue = new List <Func <bool> >();

            foreach (var hex in z.Select(b =>
                                         BitConverter.ToString(b).Replace("-", "").ToLower()))
            {
                var sni = $"{hex.Substring(0, 32)}.{hex.Substring(32)}.acme.invalid";

                log.Information($"Preparing binding at: https://{domain}, sni: {sni}");

                var x509 = CertificateManager.GenerateSelfSignedCertificate(sni);

                CertificateManager.StoreCertificate(x509);

                var certStoreName = CertificateManager.GetDefaultStore().Name;

                // iisManager.InstallCertificateforBinding(certStoreName, x509.GetCertHash(),
                // managedCertificate.ServerSiteId, sni);

                // add check to the queue
                checkQueue.Add(() => _netUtil.CheckSNI(domain, sni).Result);

                // add cleanup actions to queue
                cleanupQueue.Add(() => iisManager.RemoveHttpsBinding(managedCertificate.ServerSiteId, sni));

                cleanupQueue.Add(() => CertificateManager.RemoveCertificate(x509));
            }

            // configure cleanup to execute the cleanup queue
            pendingAuth.Cleanup = () => cleanupQueue.ForEach(a => a());

            // perform our own config checks
            return(() => checkQueue.All(check => check()));
        }
예제 #24
0
        public async Task <ManagedCertificate> Update(ManagedCertificate site)
        {
            DebugLog();

            return(await _certifyManager.UpdateManagedCertificate(site));
        }
예제 #25
0
        public async Task ImportCSV(string[] args)
        {
            InitPlugins();

            if (!IsRegistered())
            {
                Console.WriteLine("Import is only available in the registered version of this application.");
                return;
            }

            bool isNumeric(string input)
            {
                return(int.TryParse(input, out _));
            }

            var filename = args[args.Length - 1];

            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("Importing CSV: " + filename);

            var currentManagedCertificates = await _certifyClient.GetManagedCertificates(new ManagedCertificateFilter()
            {
            });

            var rows          = System.IO.File.ReadAllLines(filename);
            var csvHasHeaders = false;
            var rowID         = 0;

            // set default column index values
            int?siteIdIdx                          = 0,
               nameIdx                             = 1,
               domainsIdx                          = 2,
               primaryDomainIdx                    = null,
               includeInAutoRenewIdx               = null,
               performAutoConfigIdx                = null,
               performChallengeFileCopyIdx         = null,
               performExtensionlessConfigChecksIdx = null,
               performTlsSniBindingConfigChecksIdx = null,
               performAutomatedCertBindingIdx      = null,
               enableFailureNotificationsIdx       = null,
               preRequestPowerShellScriptIdx       = null,
               postRequestPowerShellScriptIdx      = null,
               webhookTriggerIdx                   = null,
               webhookMethodIdx                    = null,
               webhookUrlIdx                       = null,
               webhookContentTypeIdx               = null,
               webhookContentBodyIdx               = null;

            foreach (var row in rows)
            {
                try
                {
                    // does the first row contain headers in the csv file?
                    if ((rowID == 0) && row.Contains("siteid") && row.Contains("domains"))
                    {
                        csvHasHeaders = true;
                    }

                    // first row contains headers, we need to figure out the position of each column
                    if ((rowID == 0) && csvHasHeaders)
                    {
                        var columnTitles = row.Split(',');
                        var colID        = 0;

                        foreach (var title in columnTitles)
                        {
                            // because we never know how people are going to put data in the csv,
                            // convert titles to lowercase before searching for the column index
                            var cleanTitle = title.Trim().ToLower();

                            // set the column ids
                            switch (cleanTitle)
                            {
                            case "siteid":
                                siteIdIdx = colID;
                                break;

                            case "name":
                                nameIdx = colID;
                                break;

                            case "domains":
                                domainsIdx = colID;
                                break;

                            case "primarydomain":
                                primaryDomainIdx = colID;
                                break;

                            case "includeinautorenew":
                                includeInAutoRenewIdx = colID;
                                break;

                            case "performautoconfig":
                                performAutoConfigIdx = colID;
                                break;

                            case "performchallengefilecopy":
                                performChallengeFileCopyIdx = colID;
                                break;

                            case "performextensionlessconfigchecks":
                                performExtensionlessConfigChecksIdx = colID;
                                break;

                            case "performtlssnibindingconfigchecks":
                                performTlsSniBindingConfigChecksIdx = colID;
                                break;

                            case "performautomatedcertbinding":
                                performAutomatedCertBindingIdx = colID;
                                break;

                            case "enablefailurenotifications":
                                enableFailureNotificationsIdx = colID;
                                break;

                            case "prerequestpowershellscript":
                                preRequestPowerShellScriptIdx = colID;
                                break;

                            case "postrequestpowershellscript":
                                postRequestPowerShellScriptIdx = colID;
                                break;

                            case "webhooktrigger":
                                webhookTriggerIdx = colID;
                                break;

                            case "webhookmethod":
                                webhookMethodIdx = colID;
                                break;

                            case "webhookurl":
                                webhookUrlIdx = colID;
                                break;

                            case "webhookcontenttype":
                                webhookContentTypeIdx = colID;
                                break;

                            case "webhookcontentbody":
                                webhookContentBodyIdx = colID;
                                break;
                            }

                            colID++;
                        }
                    }
                    else
                    {
                        // required fields SiteId, Name, Domain;Domain2;Domain3
                        var values   = Regex.Split(row, @",(?![^\{]*\})"); // get all values separated by commas except those found between {}
                        var siteId   = values[(int)siteIdIdx].Trim();
                        var siteName = values[(int)nameIdx].Trim();
                        var domains  = values[(int)domainsIdx].Trim().Split(';');

                        // optional fields
                        bool IncludeInAutoRenew               = true,
                             PerformAutoConfig                = true,
                             PerformChallengeFileCopy         = true,
                             PerformExtensionlessConfigChecks = true,
                             PerformTlsSniBindingConfigChecks = true,
                             PerformAutomatedCertBinding      = true,
                             EnableFailureNotifications       = true;
                        string primaryDomain = "",
                               PreRequestPowerShellScript  = "",
                               PostRequestPowerShellScript = "",
                               WebhookTrigger     = Webhook.ON_NONE,
                               WebhookMethod      = "",
                               WebhookUrl         = "",
                               WebhookContentType = "",
                               WebhookContentBody = "";

                        if (primaryDomainIdx != null)
                        {
                            primaryDomain = values[(int)primaryDomainIdx].Trim();
                        }

                        if (includeInAutoRenewIdx != null)
                        {
                            IncludeInAutoRenew = Convert.ToBoolean(values[(int)includeInAutoRenewIdx].Trim());
                        }

                        if (performAutoConfigIdx != null)
                        {
                            PerformAutoConfig = Convert.ToBoolean(values[(int)performAutoConfigIdx].Trim());
                        }

                        if (performChallengeFileCopyIdx != null)
                        {
                            PerformChallengeFileCopy = Convert.ToBoolean(values[(int)performChallengeFileCopyIdx].Trim());
                        }

                        if (performExtensionlessConfigChecksIdx != null)
                        {
                            PerformExtensionlessConfigChecks = Convert.ToBoolean(values[(int)performExtensionlessConfigChecksIdx].Trim());
                        }

                        if (performTlsSniBindingConfigChecksIdx != null)
                        {
                            PerformTlsSniBindingConfigChecks = Convert.ToBoolean(values[(int)performTlsSniBindingConfigChecksIdx].Trim());
                        }

                        if (performAutomatedCertBindingIdx != null)
                        {
                            PerformAutomatedCertBinding = Convert.ToBoolean(values[(int)performAutomatedCertBindingIdx].Trim());
                        }

                        if (enableFailureNotificationsIdx != null)
                        {
                            EnableFailureNotifications = Convert.ToBoolean(values[(int)enableFailureNotificationsIdx].Trim());
                        }

                        if (preRequestPowerShellScriptIdx != null)
                        {
                            PreRequestPowerShellScript = values[(int)preRequestPowerShellScriptIdx].Trim();
                        }

                        if (postRequestPowerShellScriptIdx != null)
                        {
                            PostRequestPowerShellScript = values[(int)postRequestPowerShellScriptIdx].Trim();
                        }

                        if (webhookTriggerIdx != null)
                        {
                            WebhookTrigger = values[(int)webhookTriggerIdx].Trim();

                            // the webhook trigger text is case sensitive
                            switch (WebhookTrigger.ToLower())
                            {
                            case "none":
                                WebhookTrigger = Webhook.ON_NONE;
                                break;

                            case "on success":
                                WebhookTrigger = Webhook.ON_SUCCESS;
                                break;

                            case "on error":
                                WebhookTrigger = Webhook.ON_ERROR;
                                break;

                            case "on success or error":
                                WebhookTrigger = Webhook.ON_SUCCESS_OR_ERROR;
                                break;
                            }

                            if (webhookMethodIdx != null)
                            {
                                var tmpWebhookMethod = values[(int)webhookMethodIdx].Trim();
                                WebhookMethod = tmpWebhookMethod.ToUpper();

                                if (WebhookMethod == "POST")
                                {
                                    if (webhookUrlIdx != null)
                                    {
                                        WebhookContentType = values[(int)webhookContentTypeIdx].Trim();
                                    }

                                    if (webhookContentBodyIdx != null)
                                    {
                                        WebhookContentBody = values[(int)webhookContentBodyIdx].Trim();

                                        // cleanup json values from csv conversion
                                        WebhookContentBody = Regex.Replace(WebhookContentBody, @"(""|'')|(""|'')", "");
                                        WebhookContentBody = WebhookContentBody.Replace("\"\"", "\"");
                                    }
                                }
                            }

                            if (webhookUrlIdx != null)
                            {
                                WebhookUrl = values[(int)webhookUrlIdx].Trim();
                            }
                        }

                        if (string.IsNullOrEmpty(siteId) || !isNumeric(siteId))
                        {
                            throw new Exception("Error: SiteID column is blank or contains non-numeric characters.");
                        }

                        var newManagedCertificate = new ManagedCertificate
                        {
                            Id                 = Guid.NewGuid().ToString(),
                            GroupId            = siteId,
                            ServerSiteId       = siteId,
                            Name               = siteName,
                            IncludeInAutoRenew = IncludeInAutoRenew,
                            ItemType           = ManagedCertificateType.SSL_ACME
                        };

                        newManagedCertificate.RequestConfig.Challenges = new System.Collections.ObjectModel.ObservableCollection <CertRequestChallengeConfig>(
                            new List <CertRequestChallengeConfig> {
                            new CertRequestChallengeConfig {
                                ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP
                            }
                        });

                        newManagedCertificate.RequestConfig.PerformAutoConfig                = PerformAutoConfig;
                        newManagedCertificate.RequestConfig.PerformChallengeFileCopy         = PerformChallengeFileCopy;
                        newManagedCertificate.RequestConfig.PerformExtensionlessConfigChecks = PerformExtensionlessConfigChecks;
                        newManagedCertificate.RequestConfig.PerformTlsSniBindingConfigChecks = PerformTlsSniBindingConfigChecks;
                        newManagedCertificate.RequestConfig.PerformAutomatedCertBinding      = PerformAutomatedCertBinding;
                        newManagedCertificate.RequestConfig.EnableFailureNotifications       = EnableFailureNotifications;
                        newManagedCertificate.RequestConfig.PreRequestPowerShellScript       = PreRequestPowerShellScript;
                        newManagedCertificate.RequestConfig.PostRequestPowerShellScript      = PostRequestPowerShellScript;
                        newManagedCertificate.RequestConfig.WebhookTrigger     = WebhookTrigger;
                        newManagedCertificate.RequestConfig.WebhookMethod      = WebhookMethod;
                        newManagedCertificate.RequestConfig.WebhookUrl         = WebhookUrl;
                        newManagedCertificate.RequestConfig.WebhookContentType = WebhookContentType;
                        newManagedCertificate.RequestConfig.WebhookContentBody = WebhookContentBody;

                        var isPrimaryDomain = true;

                        // if we have passed in a primary domain into the csv file, use that instead
                        // of the first domain in the list
                        if (primaryDomain != "")
                        {
                            isPrimaryDomain = false;
                        }

                        var sans = new List <string>();
                        foreach (var d in domains)
                        {
                            if (!string.IsNullOrWhiteSpace(d))
                            {
                                var cleanDomainName = d.Trim();

                                if ((isPrimaryDomain) || (cleanDomainName == primaryDomain.Trim()))
                                {
                                    newManagedCertificate.RequestConfig.PrimaryDomain = cleanDomainName;
                                    isPrimaryDomain = true;
                                }

                                var sanExists = false;

                                // check for existing SAN entry
                                foreach (var site in currentManagedCertificates)
                                {
                                    if (!sanExists)
                                    {
                                        var filtered = site.DomainOptions.Where(options => options.Domain == cleanDomainName);

                                        if (filtered.Count() > 0)
                                        {
                                            Console.WriteLine("Processing Row: " + rowID + " - Domain entry (" + cleanDomainName + ") already exists in certificate (" + site.Name + ")");
                                            sanExists = true;
                                        }
                                    }
                                }

                                // if the current san entry doesn't exist in our certificate list,
                                // let's add it
                                if (!sanExists)
                                {
                                    newManagedCertificate.DomainOptions.Add(new DomainOption {
                                        Domain = cleanDomainName, IsPrimaryDomain = isPrimaryDomain, IsSelected = true, Title = d
                                    });

                                    sans.Add(cleanDomainName);
                                }

                                isPrimaryDomain = false;
                            }
                        }

                        // if the new certificate to be imported has sans, then add the certificate
                        // request to the system
                        if (sans.Count() > 0)
                        {
                            newManagedCertificate.RequestConfig.SubjectAlternativeNames = sans.ToArray();

                            // add managed site
                            Console.WriteLine("Creating Managed Certificate: " + newManagedCertificate.Name);
                            await _certifyClient.UpdateManagedCertificate(newManagedCertificate);

                            // add the new certificate request to our in-memory list
                            currentManagedCertificates.Add(newManagedCertificate);
                        }
                    }
                }
                catch (Exception exp)
                {
                    Console.WriteLine("There was a problem importing row " + rowID + " - " + exp.ToString());
                }

                rowID++;
            }
        }
예제 #26
0
        public async Task <List <ActionStep> > PreviewActions(ManagedCertificate site)
        {
            DebugLog();

            return(await _certifyManager.GeneratePreview(site));
        }
        /// <summary>
        /// Creates or updates the https bindings associated with the dns names in the current
        /// request config, using the requested port/ips or autobinding
        /// </summary>
        /// <param name="requestConfig">  </param>
        /// <param name="pfxPath">  </param>
        /// <param name="cleanupCertStore">  </param>
        /// <returns>  </returns>
        public async Task <List <ActionStep> > StoreAndDeployManagedCertificate(IBindingDeploymentTarget deploymentTarget, ManagedCertificate managedCertificate, string pfxPath, bool cleanupCertStore, bool isPreviewOnly)
        {
            var actions = new List <ActionStep>();

            var requestConfig = managedCertificate.RequestConfig;

            if (!isPreviewOnly)
            {
                if (new System.IO.FileInfo(pfxPath).Length == 0)
                {
                    throw new ArgumentException("InstallCertForRequest: Invalid PFX File");
                }
            }

            //store cert against primary domain
            var certStoreName           = CertificateManager.GetStore().Name;
            X509Certificate2 storedCert = null;

            byte[] certHash = null;

            // unless user has opted not to store cert, store it now
            if (requestConfig.DeploymentSiteOption != DeploymentOption.NoDeployment)
            {
                if (!isPreviewOnly)
                {
                    try
                    {
                        storedCert = await CertificateManager.StoreCertificate(requestConfig.PrimaryDomain, pfxPath, isRetry : false, enableRetryBehaviour : _enableCertDoubleImportBehaviour);

                        if (storedCert != null)
                        {
                            certHash = storedCert.GetCertHash();

                            actions.Add(new ActionStep {
                                HasError = false, Title = "Certificate Stored", Category = "Certificate Storage", Description = "Certificate stored OK"
                            });
                        }
                    }
                    catch (Exception exp)
                    {
                        actions.Add(new ActionStep {
                            HasError = true, Title = "Certificate Storage Failed", Category = "Certificate Storage", Description = "Error storing certificate." + exp.Message
                        });
                        return(actions);
                    }
                }
                else
                {
                    //fake cert for preview only
                    storedCert = new X509Certificate2();
                    certHash   = new byte[] { 0x00, 0x01, 0x02 };
                }
            }

            if (storedCert != null)
            {
                //get list of domains we need to create/update https bindings for
                var dnsHosts = new List <string> {
                    requestConfig.PrimaryDomain
                };

                if (requestConfig.SubjectAlternativeNames != null)
                {
                    foreach (var san in requestConfig.SubjectAlternativeNames)
                    {
                        dnsHosts.Add(san);
                    }
                }

                dnsHosts = dnsHosts
                           .Distinct()
                           .Where(d => !string.IsNullOrEmpty(d))
                           .ToList();

                // depending on our deployment mode we decide which sites/bindings to update:

                var deployments = await DeployToAllTargetBindings(deploymentTarget, managedCertificate, requestConfig, certStoreName, certHash, dnsHosts, isPreviewOnly);

                actions.AddRange(deployments);

                // if required, cleanup old certs we are replacing. Only applied if we have deployed
                // the certificate, otherwise we keep the old one

                // FIXME: need strategy to analyse if there are any users of cert we haven't
                // accounted for (manually added etc) otherwise we are disposing of a cert which
                // could still be in use

                /*if (!isPreviewOnly)
                 * {
                 *  if (cleanupCertStore
                 *      && requestConfig.DeploymentSiteOption != DeploymentOption.DeploymentStoreOnly
                 *       && requestConfig.DeploymentSiteOption != DeploymentOption.NoDeployment
                 *      )
                 *  {
                 *      //remove old certs for this primary domain
                 *      //FIXME:
                 *     CertificateManager.CleanupCertificateDuplicates(storedCert, requestConfig.PrimaryDomain);
                 *  }
                 * }*/
            }
            else
            {
                if (requestConfig.DeploymentSiteOption != DeploymentOption.NoDeployment)
                {
                    actions.Add(new ActionStep {
                        HasError = true, Title = "Certificate Storage", Description = "Certificate could not be stored."
                    });
                }
            }

            // deployment tasks completed
            return(actions);
        }
예제 #28
0
        /// <summary>
        /// Convert legacy pre/post request scripts, webhooks and deployments to Pre/Post Deployment Tasks
        /// </summary>
        /// <param name="managedCertificate"></param>
        /// <returns></returns>
        public Tuple <ManagedCertificate, bool> MigrateDeploymentTasks(ManagedCertificate managedCertificate)
        {
            bool requiredMigration = false;

            if (managedCertificate.PreRequestTasks == null)
            {
                managedCertificate.PreRequestTasks = new System.Collections.ObjectModel.ObservableCollection <DeploymentTaskConfig>();
            }

            if (managedCertificate.PostRequestTasks == null)
            {
                managedCertificate.PostRequestTasks = new System.Collections.ObjectModel.ObservableCollection <DeploymentTaskConfig>();
            }

            if (!string.IsNullOrEmpty(managedCertificate.RequestConfig.PreRequestPowerShellScript))
            {
                //add pre-request script task
                var task = new DeploymentTaskConfig
                {
                    Id                = Guid.NewGuid().ToString(),
                    TaskTypeId        = StandardTaskTypes.POWERSHELL,
                    ChallengeProvider = StandardAuthTypes.STANDARD_AUTH_LOCAL,
                    TaskName          = "[Pre-Request Script]",
                    IsFatalOnError    = true,

                    Parameters = new List <ProviderParameterSetting> {
                        new ProviderParameterSetting("scriptpath", managedCertificate.RequestConfig.PreRequestPowerShellScript),
                        new ProviderParameterSetting("inputresult", "true")
                    }
                };

                if (!managedCertificate.PreRequestTasks.Any(t => t.TaskName == "[Pre-Request Script]"))
                {
                    managedCertificate.PreRequestTasks.Insert(0, task);
                    requiredMigration = true;
                }

                managedCertificate.RequestConfig.PreRequestPowerShellScript = null;
            }

            if (!string.IsNullOrEmpty(managedCertificate.RequestConfig.PostRequestPowerShellScript))
            {
                //add post-request script task
                var task = new DeploymentTaskConfig
                {
                    Id                = Guid.NewGuid().ToString(),
                    TaskTypeId        = StandardTaskTypes.POWERSHELL,
                    ChallengeProvider = StandardAuthTypes.STANDARD_AUTH_LOCAL,
                    TaskName          = "[Post-Request Script]",
                    IsFatalOnError    = true,
                    TaskTrigger       = TaskTriggerType.ON_SUCCESS,
                    Parameters        = new List <ProviderParameterSetting> {
                        new ProviderParameterSetting("scriptpath", managedCertificate.RequestConfig.PostRequestPowerShellScript),
                        new ProviderParameterSetting("inputresult", "true")
                    }
                };

                if (!managedCertificate.PostRequestTasks.Any(t => t.TaskName == "[Post-Request Script]"))
                {
                    managedCertificate.PostRequestTasks.Insert(0, task);
                    requiredMigration = true;
                }

                managedCertificate.RequestConfig.PostRequestPowerShellScript = null;
            }

            if (!string.IsNullOrEmpty(managedCertificate.RequestConfig.WebhookUrl))
            {
                //add post-request script task for webhook, migrate trigger type to task trigger type

                var triggerType = TaskTriggerType.ANY_STATUS;

                if (managedCertificate.RequestConfig.WebhookTrigger == Webhook.ON_NONE)
                {
                    triggerType = TaskTriggerType.NOT_ENABLED;
                }
                else if (managedCertificate.RequestConfig.WebhookTrigger == Webhook.ON_SUCCESS)
                {
                    triggerType = TaskTriggerType.ON_SUCCESS;
                }
                else if (managedCertificate.RequestConfig.WebhookTrigger == Webhook.ON_ERROR)
                {
                    triggerType = TaskTriggerType.ON_ERROR;
                }
                else if (managedCertificate.RequestConfig.WebhookTrigger == Webhook.ON_SUCCESS_OR_ERROR)
                {
                    triggerType = TaskTriggerType.ANY_STATUS;
                }

                var task = new DeploymentTaskConfig
                {
                    Id = Guid.NewGuid().ToString(),
                    ChallengeProvider = StandardAuthTypes.STANDARD_AUTH_LOCAL,
                    TaskName          = "[Post-Request Webhook]",
                    IsFatalOnError    = false,
                    TaskTrigger       = triggerType,
                    TaskTypeId        = StandardTaskTypes.WEBHOOK,
                    Parameters        = new List <ProviderParameterSetting> {
                        new ProviderParameterSetting("url", managedCertificate.RequestConfig.WebhookUrl),
                        new ProviderParameterSetting("method", managedCertificate.RequestConfig.WebhookMethod),
                        new ProviderParameterSetting("contenttype", managedCertificate.RequestConfig.WebhookContentType),
                        new ProviderParameterSetting("contentbody", managedCertificate.RequestConfig.WebhookContentBody)
                    }
                };

                if (!managedCertificate.PostRequestTasks.Any(t => t.TaskName == "[Post-Request Webhook]"))
                {
                    managedCertificate.PostRequestTasks.Insert(0, task);
                    requiredMigration = true;
                }

                managedCertificate.RequestConfig.WebhookUrl     = null;
                managedCertificate.RequestConfig.WebhookTrigger = Webhook.ON_NONE;
            }

            // #516 check for any post-request webhooks incorrectly set to be powershell

            if (managedCertificate.PostRequestTasks?.Any(t => t.TaskTypeId == StandardTaskTypes.POWERSHELL && t.Parameters?.Any(p => p.Key == "url") == true) == true)
            {
                var webhookTask = managedCertificate.PostRequestTasks.First(t => t.TaskTypeId == StandardTaskTypes.POWERSHELL && t.Parameters?.Any(p => p.Key == "url") == true);
                if (webhookTask != null)
                {
                    webhookTask.TaskTypeId = StandardTaskTypes.WEBHOOK;
                    requiredMigration      = true;
                }
            }

            return(new Tuple <ManagedCertificate, bool>(managedCertificate, requiredMigration));
        }
 public async Task <List <ActionStep> > GeneratePreview(ManagedCertificate item) => await new PreviewManager().GeneratePreview(item, _serverProvider, this);
예제 #30
0
        public async Task TestChallengeRequestDNSWildcard()
        {
            var testStr = Guid.NewGuid().ToString().Substring(0, 6);

            PrimaryTestDomain = $"test-{testStr}." + PrimaryTestDomain;
            var wildcardDomain       = "*.test." + PrimaryTestDomain;
            var testWildcardSiteName = "TestWildcard_" + testStr;

            if (await iisManager.SiteExists(testWildcardSiteName))
            {
                await iisManager.DeleteSite(testWildcardSiteName);
            }

            var site = await iisManager.CreateSite(testWildcardSiteName, "test" + testStr + "." + PrimaryTestDomain, PrimaryIISRoot, "DefaultAppPool", port : testSiteHttpPort);

            ManagedCertificate managedCertificate = null;
            X509Certificate2   certInfo           = null;

            try
            {
                managedCertificate = new ManagedCertificate
                {
                    Id             = Guid.NewGuid().ToString(),
                    Name           = testWildcardSiteName,
                    GroupId        = site.Id.ToString(),
                    UseStagingMode = true,
                    RequestConfig  = new CertRequestConfig
                    {
                        PrimaryDomain                    = wildcardDomain,
                        PerformAutoConfig                = true,
                        PerformAutomatedCertBinding      = true,
                        PerformChallengeFileCopy         = true,
                        PerformExtensionlessConfigChecks = true,
                        WebsiteRootPath                  = testSitePath,
                        Challenges = new ObservableCollection <CertRequestChallengeConfig>
                        {
                            new CertRequestChallengeConfig {
                                ChallengeType          = SupportedChallengeTypes.CHALLENGE_TYPE_DNS,
                                ChallengeProvider      = "DNS01.API.Cloudflare",
                                ChallengeCredentialKey = _testCredStorageKey,
                                ZoneId = ConfigSettings["Cloudflare_ZoneId"]
                            }
                        }
                    },
                    ItemType = ManagedCertificateType.SSL_ACME
                };

                var result = await certifyManager.PerformCertificateRequest(_log, managedCertificate);

                //ensure cert request was successful
                Assert.IsTrue(result.IsSuccess, "Certificate Request Not Completed");

                //check details of cert, subject alternative name should include domain and expiry must be great than 89 days in the future
                var managedCertificates = await certifyManager.GetManagedCertificates();

                managedCertificate = managedCertificates.FirstOrDefault(m => m.Id == managedCertificate.Id);

                //emsure we have a new managed site
                Assert.IsNotNull(managedCertificate);

                //have cert file details
                Assert.IsNotNull(managedCertificate.CertificatePath);

                var fileExists = System.IO.File.Exists(managedCertificate.CertificatePath);
                Assert.IsTrue(fileExists);

                //check cert is correct
                certInfo = CertificateManager.LoadCertificate(managedCertificate.CertificatePath);
                Assert.IsNotNull(certInfo);

                var isRecentlyCreated = Math.Abs((DateTime.UtcNow - certInfo.NotBefore).TotalDays) < 2;
                Assert.IsTrue(isRecentlyCreated);

                var expiresInFuture = (certInfo.NotAfter - DateTime.UtcNow).TotalDays >= 89;
                Assert.IsTrue(expiresInFuture);
            }
            finally
            {
                // remove IIS site
                await iisManager.DeleteSite(testWildcardSiteName);

                // remove managed site
                if (managedCertificate != null)
                {
                    await certifyManager.DeleteManagedCertificate(managedCertificate.Id);
                }

                // cleanup certificate
                if (certInfo != null)
                {
                    CertificateManager.RemoveCertificate(certInfo);
                }
            }
        }