diff --git a/src/Lidarr.Api.V1/Config/HostConfigModule.cs b/src/Lidarr.Api.V1/Config/HostConfigModule.cs index 494ebaf13..c3311479d 100644 --- a/src/Lidarr.Api.V1/Config/HostConfigModule.cs +++ b/src/Lidarr.Api.V1/Config/HostConfigModule.cs @@ -1,6 +1,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Security.Cryptography.X509Certificates; using FluentValidation; using Lidarr.Http; using NzbDrone.Common.Extensions; @@ -18,7 +19,10 @@ namespace Lidarr.Api.V1.Config private readonly IConfigService _configService; private readonly IUserService _userService; - public HostConfigModule(IConfigFileProvider configFileProvider, IConfigService configService, IUserService userService) + public HostConfigModule(IConfigFileProvider configFileProvider, + IConfigService configService, + IUserService userService, + FileExistsValidator fileExistsValidator) : base("/config/host") { _configFileProvider = configFileProvider; @@ -43,7 +47,14 @@ namespace Lidarr.Api.V1.Config SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl); SharedValidator.RuleFor(c => c.SslPort).NotEqual(c => c.Port).When(c => c.EnableSsl); - SharedValidator.RuleFor(c => c.SslCertPath).NotEmpty().When(c => c.EnableSsl); + + SharedValidator.RuleFor(c => c.SslCertPath) + .Cascade(CascadeMode.StopOnFirstFailure) + .NotEmpty() + .IsValidPath() + .SetValidator(fileExistsValidator) + .Must((resource, path) => IsValidSslCertificate(resource)).WithMessage("Invalid SSL certificate file or password") + .When(c => c.EnableSsl); SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default"); SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script); @@ -53,6 +64,21 @@ namespace Lidarr.Api.V1.Config SharedValidator.RuleFor(c => c.BackupRetention).InclusiveBetween(1, 90); } + private bool IsValidSslCertificate(HostConfigResource resource) + { + X509Certificate2 cert; + try + { + cert = new X509Certificate2(resource.SslCertPath, resource.SslCertPassword, X509KeyStorageFlags.DefaultKeySet); + } + catch + { + return false; + } + + return cert != null; + } + private HostConfigResource GetHostConfig() { var resource = _configFileProvider.ToResource(_configService); diff --git a/src/NzbDrone.Core/Validation/Paths/FileExistsValidator.cs b/src/NzbDrone.Core/Validation/Paths/FileExistsValidator.cs new file mode 100644 index 000000000..9adb200aa --- /dev/null +++ b/src/NzbDrone.Core/Validation/Paths/FileExistsValidator.cs @@ -0,0 +1,26 @@ +using FluentValidation.Validators; +using NzbDrone.Common.Disk; + +namespace NzbDrone.Core.Validation.Paths +{ + public class FileExistsValidator : PropertyValidator + { + private readonly IDiskProvider _diskProvider; + + public FileExistsValidator(IDiskProvider diskProvider) + : base("File does not exist") + { + _diskProvider = diskProvider; + } + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue == null) + { + return false; + } + + return _diskProvider.FileExists(context.PropertyValue.ToString()); + } + } +} diff --git a/src/NzbDrone.Host/WebHost/WebHostController.cs b/src/NzbDrone.Host/WebHost/WebHostController.cs index 056b51db4..3407dad32 100644 --- a/src/NzbDrone.Host/WebHost/WebHostController.cs +++ b/src/NzbDrone.Host/WebHost/WebHostController.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -10,6 +11,7 @@ using Microsoft.Extensions.Logging; using NLog; using NLog.Extensions.Logging; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Exceptions; using NzbDrone.Common.Extensions; using NzbDrone.Common.Serializer; using NzbDrone.Core.Configuration; @@ -69,8 +71,21 @@ namespace NzbDrone.Host { options.ConfigureHttpsDefaults(configureOptions => { - var certificate = new X509Certificate2(); - certificate.Import(_configFileProvider.SslCertPath, _configFileProvider.SslCertPassword, X509KeyStorageFlags.DefaultKeySet); + X509Certificate2 certificate; + + try + { + certificate = new X509Certificate2(sslCertPath, _configFileProvider.SslCertPassword, X509KeyStorageFlags.DefaultKeySet); + } + catch (CryptographicException ex) + { + if (ex.HResult == 0x2 || ex.HResult == 0x2006D080) + { + throw new LidarrStartupException(ex, $"The SSL certificate file {sslCertPath} does not exist"); + } + + throw new LidarrStartupException(ex); + } configureOptions.ServerCertificate = certificate; });