using System; using System.Collections.Generic; using System.Net; using System.Security.Cryptography.X509Certificates; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Services; using Umbraco.Web.HealthCheck.Checks.Config; namespace Umbraco.Web.HealthCheck.Checks.Security { [HealthCheck( "EB66BB3B-1BCD-4314-9531-9DA2C1D6D9A7", "HTTPS Configuration", Description = "Checks if your site is configured to work over HTTPS and if the Umbraco related configuration for that is correct.", Group = "Security")] public class HttpsCheck : HealthCheck { private readonly ILocalizedTextService _textService; private readonly IRuntimeState _runtime; private readonly IGlobalSettings _globalSettings; private readonly IContentSection _contentSection; private const string FixHttpsSettingAction = "fixHttpsSetting"; public HttpsCheck(ILocalizedTextService textService, IRuntimeState runtime, IGlobalSettings globalSettings, IContentSection contentSection) { _textService = textService; _runtime = runtime; _globalSettings = globalSettings; _contentSection = contentSection; } /// /// Get the status for this health check /// /// public override IEnumerable GetStatus() { //return the statuses return new[] { CheckIfCurrentSchemeIsHttps(), CheckHttpsConfigurationSetting(), CheckForValidCertificate() }; } /// /// Executes the action and returns it's status /// /// /// public override HealthCheckStatus ExecuteAction(HealthCheckAction action) { switch (action.Alias) { case FixHttpsSettingAction: return FixHttpsSetting(); default: throw new InvalidOperationException("HttpsCheck action requested is either not executable or does not exist"); } } private HealthCheckStatus CheckForValidCertificate() { var message = string.Empty; StatusResultType result; // Attempt to access the site over HTTPS to see if it HTTPS is supported // and a valid certificate has been configured var url = _runtime.ApplicationUrl.ToString().Replace("http:", "https:"); var request = (HttpWebRequest) WebRequest.Create(url); request.AllowAutoRedirect = false; try { var response = (HttpWebResponse)request.GetResponse(); // Check for 301/302 as a external login provider such as UmbracoID might be in use if (response.StatusCode == HttpStatusCode.Moved || response.StatusCode == HttpStatusCode.Redirect) { // Reset request to use the static login background image var absoluteLoginBackgroundImage = $"{url}/{_contentSection.LoginBackgroundImage}"; request = (HttpWebRequest)WebRequest.Create(absoluteLoginBackgroundImage); response = (HttpWebResponse)request.GetResponse(); } if (response.StatusCode == HttpStatusCode.OK) { // Got a valid response, check now for if certificate expiring within 14 days // Hat-tip: https://stackoverflow.com/a/15343898/489433 const int NumberOfDaysForExpiryWarning = 14; var cert = request.ServicePoint.Certificate; using (var cert2 = new X509Certificate2(cert)) { var expirationDate = cert2.NotAfter; var daysToExpiry = (int)Math.Floor((cert2.NotAfter - DateTime.Now).TotalDays); if (daysToExpiry <= 0) { result = StatusResultType.Error; message = _textService.Localize("healthcheck", "httpsCheckExpiredCertificate"); } else if (daysToExpiry < NumberOfDaysForExpiryWarning) { result = StatusResultType.Warning; message = _textService.Localize("healthcheck", "httpsCheckExpiringCertificate", new[] { daysToExpiry.ToString() }); } else { result = StatusResultType.Success; message = _textService.Localize("healthcheck", "httpsCheckValidCertificate"); } } } else { result = StatusResultType.Error; message = _textService.Localize("healthcheck", "healthCheckInvalidUrl", new[] { url, response.StatusDescription }); } } catch (Exception ex) { var exception = ex as WebException; if (exception != null) { message = exception.Status == WebExceptionStatus.TrustFailure ? _textService.Localize("healthcheck", "httpsCheckInvalidCertificate", new [] { exception.Message }) : _textService.Localize("healthcheck", "healthCheckInvalidUrl", new [] { url, exception.Message }); } else { message = _textService.Localize("healthcheck", "healthCheckInvalidUrl", new[] { url, ex.Message }); } result = StatusResultType.Error; } var actions = new List(); return new HealthCheckStatus(message) { ResultType = result, Actions = actions }; } private HealthCheckStatus CheckIfCurrentSchemeIsHttps() { var uri = _runtime.ApplicationUrl; var success = uri.Scheme == "https"; var actions = new List(); return new HealthCheckStatus(_textService.Localize("healthcheck", "httpsCheckIsCurrentSchemeHttps", new[] { success ? string.Empty : "not" })) { ResultType = success ? StatusResultType.Success : StatusResultType.Error, Actions = actions }; } private HealthCheckStatus CheckHttpsConfigurationSetting() { var httpsSettingEnabled = _globalSettings.UseHttps; var uri = _runtime.ApplicationUrl; var actions = new List(); string resultMessage; StatusResultType resultType; if (uri.Scheme != "https") { resultMessage = _textService.Localize("healthcheck", "httpsCheckConfigurationRectifyNotPossible"); resultType = StatusResultType.Info; } else { if (httpsSettingEnabled == false) actions.Add(new HealthCheckAction(FixHttpsSettingAction, Id) { Name = _textService.Localize("healthcheck", "httpsCheckEnableHttpsButton"), Description = _textService.Localize("healthcheck", "httpsCheckEnableHttpsDescription") }); resultMessage = _textService.Localize("healthcheck", "httpsCheckConfigurationCheckResult", new[] {httpsSettingEnabled.ToString(), httpsSettingEnabled ? string.Empty : "not"}); resultType = httpsSettingEnabled ? StatusResultType.Success: StatusResultType.Error; } return new HealthCheckStatus(resultMessage) { ResultType = resultType, Actions = actions }; } private HealthCheckStatus FixHttpsSetting() { var configFile = IOHelper.MapPath("~/Web.config"); const string xPath = "/configuration/appSettings/add[@key='Umbraco.Core.UseHttps']/@value"; var configurationService = new ConfigurationService(configFile, xPath, _textService); var updateConfigFile = configurationService.UpdateConfigFile("true"); if (updateConfigFile.Success) { return new HealthCheckStatus(_textService.Localize("healthcheck", "httpsCheckEnableHttpsSuccess")) { ResultType = StatusResultType.Success }; } return new HealthCheckStatus(_textService.Localize("healthcheck", "httpsCheckEnableHttpsError", new [] { updateConfigFile.Result })) { ResultType = StatusResultType.Error }; } } }