HTTPS certificate validation options
New: Enable HTTPS certificate validation by default New: Option to disable certificate validation for all or only local addresses
This commit is contained in:
parent
439870a546
commit
7d06e5d684
|
@ -10,6 +10,18 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
|
|||
import FormInputButton from 'Components/Form/FormInputButton';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
|
||||
const authenticationMethodOptions = [
|
||||
{ key: 'none', value: 'None' },
|
||||
{ key: 'basic', value: 'Basic (Browser Popup)' },
|
||||
{ key: 'forms', value: 'Forms (Login Page)' }
|
||||
];
|
||||
|
||||
const certificateValidationOptions = [
|
||||
{ key: 'enabled', value: 'Enabled' },
|
||||
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' },
|
||||
{ key: 'disabled', value: 'Disabled' }
|
||||
];
|
||||
|
||||
class SecuritySettings extends Component {
|
||||
|
||||
//
|
||||
|
@ -57,15 +69,10 @@ class SecuritySettings extends Component {
|
|||
authenticationMethod,
|
||||
username,
|
||||
password,
|
||||
apiKey
|
||||
apiKey,
|
||||
certificateValidation
|
||||
} = settings;
|
||||
|
||||
const authenticationMethodOptions = [
|
||||
{ key: 'none', value: 'None' },
|
||||
{ key: 'basic', value: 'Basic (Browser Popup)' },
|
||||
{ key: 'forms', value: 'Forms (Login Page)' }
|
||||
];
|
||||
|
||||
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
|
||||
|
||||
return (
|
||||
|
@ -146,6 +153,19 @@ class SecuritySettings extends Component {
|
|||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Certificate Validation</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="certificateValidation"
|
||||
values={certificateValidationOptions}
|
||||
helpText="Change how strict HTTPS certification validation is"
|
||||
onChange={onInputChange}
|
||||
{...certificateValidation}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={this.state.isConfirmApiKeyResetModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
using System.Net;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Test.ExtensionTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class IPAddressExtensionsFixture
|
||||
{
|
||||
[TestCase("::1")]
|
||||
[TestCase("10.64.5.1")]
|
||||
[TestCase("127.0.0.1")]
|
||||
[TestCase("172.16.0.1")]
|
||||
[TestCase("192.168.5.1")]
|
||||
public void should_return_true_for_local_ip_address(string ipAddress)
|
||||
{
|
||||
IPAddress.Parse(ipAddress).IsLocalAddress().Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("1.2.3.4")]
|
||||
[TestCase("172.55.0.1")]
|
||||
[TestCase("192.55.0.1")]
|
||||
public void should_return_false_for_public_ip_address(string ipAddress)
|
||||
{
|
||||
IPAddress.Parse(ipAddress).IsLocalAddress().Should().BeFalse();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -84,6 +84,7 @@
|
|||
<Compile Include="ExtensionTests\IEnumerableExtensionTests\ExceptByFixture.cs" />
|
||||
<Compile Include="ExtensionTests\IEnumerableExtensionTests\IntersectByFixture.cs" />
|
||||
<Compile Include="ExtensionTests\Int64ExtensionFixture.cs" />
|
||||
<Compile Include="ExtensionTests\IPAddressExtensionsFixture.cs" />
|
||||
<Compile Include="ExtensionTests\UrlExtensionsFixture.cs" />
|
||||
<Compile Include="HashUtilFixture.cs" />
|
||||
<Compile Include="Http\HttpClientFixture.cs" />
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
using System.Net;
|
||||
|
||||
namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
public static class IPAddressExtensions
|
||||
{
|
||||
public static bool IsLocalAddress(this IPAddress ipAddress)
|
||||
{
|
||||
if (ipAddress.ToString() == "::1")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
byte[] bytes = ipAddress.GetAddressBytes();
|
||||
switch (bytes[0])
|
||||
{
|
||||
case 10:
|
||||
case 127:
|
||||
return true;
|
||||
case 172:
|
||||
return bytes[1] < 32 && bytes[1] >= 16;
|
||||
case 192:
|
||||
return bytes[1] == 168;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ using System.Net;
|
|||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Common.Security;
|
||||
|
||||
namespace NzbDrone.Common.Http.Dispatchers
|
||||
{
|
||||
|
@ -75,11 +74,6 @@ namespace NzbDrone.Common.Http.Dispatchers
|
|||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
if (e.Status == WebExceptionStatus.SecureChannelFailure && OsInfo.IsWindows)
|
||||
{
|
||||
SecurityProtocolPolicy.DisableTls12();
|
||||
}
|
||||
|
||||
httpWebResponse = (HttpWebResponse)e.Response;
|
||||
|
||||
if (httpWebResponse == null)
|
||||
|
|
|
@ -102,6 +102,7 @@
|
|||
<Compile Include="Disk\DestinationAlreadyExistsException.cs" />
|
||||
<Compile Include="Exceptions\SonarrStartupException.cs" />
|
||||
<Compile Include="EnvironmentInfo\RuntimeMode.cs" />
|
||||
<Compile Include="Extensions\IPAddressExtensions.cs" />
|
||||
<Compile Include="Extensions\RegexExtensions.cs" />
|
||||
<Compile Include="Extensions\DictionaryExtensions.cs" />
|
||||
<Compile Include="Disk\OsPath.cs" />
|
||||
|
@ -216,8 +217,6 @@
|
|||
<Compile Include="Properties\SharedAssemblyInfo.cs" />
|
||||
<Compile Include="Reflection\ReflectionExtensions.cs" />
|
||||
<Compile Include="Extensions\ResourceExtensions.cs" />
|
||||
<Compile Include="Security\SecurityProtocolPolicy.cs" />
|
||||
<Compile Include="Security\X509CertificateValidationPolicy.cs" />
|
||||
<Compile Include="Serializer\HttpUriConverter.cs" />
|
||||
<Compile Include="Serializer\IntConverter.cs" />
|
||||
<Compile Include="Serializer\Json.cs" />
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
|
||||
namespace NzbDrone.Common.Security
|
||||
{
|
||||
public static class SecurityProtocolPolicy
|
||||
{
|
||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(SecurityProtocolPolicy));
|
||||
|
||||
private const SecurityProtocolType Tls11 = (SecurityProtocolType)768;
|
||||
private const SecurityProtocolType Tls12 = (SecurityProtocolType)3072;
|
||||
|
||||
public static void Register()
|
||||
{
|
||||
if (OsInfo.IsNotWindows)
|
||||
{
|
||||
// This was never meant to be used on mono, and will cause issues with mono 5 and higher if btls is enabled.
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: In v3 we should drop support for SSL3 because its very insecure. Only leaving it enabled because some people might rely on it.
|
||||
var protocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
|
||||
|
||||
if (Enum.IsDefined(typeof(SecurityProtocolType), Tls11))
|
||||
{
|
||||
protocol |= Tls11;
|
||||
}
|
||||
|
||||
// Enabling Tls1.2 invalidates certificates using md5, so we disable Tls12 on the fly if that happens.
|
||||
if (Enum.IsDefined(typeof(SecurityProtocolType), Tls12))
|
||||
{
|
||||
protocol |= Tls12;
|
||||
}
|
||||
|
||||
ServicePointManager.SecurityProtocol = protocol;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug(ex, "Failed to set TLS security protocol.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void DisableTls12()
|
||||
{
|
||||
try
|
||||
{
|
||||
var protocol = ServicePointManager.SecurityProtocol;
|
||||
if (protocol.HasFlag(Tls12))
|
||||
{
|
||||
Logger.Warn("Disabled Tls1.2 due to remote certificate error.");
|
||||
|
||||
ServicePointManager.SecurityProtocol = protocol & ~Tls12;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug(ex, "Failed to disable TLS 1.2 security protocol.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
|
||||
namespace NzbDrone.Common.Security
|
||||
{
|
||||
public static class X509CertificateValidationPolicy
|
||||
{
|
||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(X509CertificateValidationPolicy));
|
||||
|
||||
public static void Register()
|
||||
{
|
||||
ServicePointManager.ServerCertificateValidationCallback = ShouldByPassValidationError;
|
||||
}
|
||||
|
||||
private static bool ShouldByPassValidationError(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
var request = sender as HttpWebRequest;
|
||||
|
||||
if (request == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var req = sender as HttpWebRequest;
|
||||
var cert2 = certificate as X509Certificate2;
|
||||
if (cert2 != null && req != null && cert2.SignatureAlgorithm.FriendlyName == "md5RSA")
|
||||
{
|
||||
Logger.Error("https://{0} uses the obsolete md5 hash in it's https certificate, if that is your certificate, please (re)create certificate with better algorithm as soon as possible.", req.RequestUri.Authority);
|
||||
}
|
||||
|
||||
if (sslPolicyErrors == SslPolicyErrors.None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Logger.Debug("Certificate validation for {0} failed. {1}", request.Address, sslPolicyErrors);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ using NzbDrone.Core.Configuration.Events;
|
|||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Security;
|
||||
|
||||
namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
|
@ -345,6 +346,9 @@ namespace NzbDrone.Core.Configuration
|
|||
|
||||
public int BackupRetention => GetValueInt("BackupRetention", 28);
|
||||
|
||||
public CertificateValidationType CertificateValidation =>
|
||||
GetValueEnum("CertificateValidation", CertificateValidationType.Enabled);
|
||||
|
||||
private string GetValue(string key)
|
||||
{
|
||||
return GetValue(key, string.Empty);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Security;
|
||||
|
||||
namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
|
@ -82,5 +83,7 @@ namespace NzbDrone.Core.Configuration
|
|||
string BackupFolder { get; }
|
||||
int BackupInterval { get; }
|
||||
int BackupRetention { get; }
|
||||
|
||||
CertificateValidationType CertificateValidation { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ using System.Text;
|
|||
|
||||
namespace NzbDrone.Core
|
||||
{
|
||||
public static class Security
|
||||
public static class Hashing
|
||||
{
|
||||
public static string SHA256Hash(this string input)
|
||||
{
|
|
@ -1166,7 +1166,9 @@
|
|||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="RootFolders\UnmappedFolder.cs" />
|
||||
<Compile Include="Security.cs" />
|
||||
<Compile Include="Hashing.cs" />
|
||||
<Compile Include="Security\CertificateValidationType.cs" />
|
||||
<Compile Include="Security\X509CertificateValidationPolicy.cs" />
|
||||
<Compile Include="SeriesStats\SeasonStatistics.cs" />
|
||||
<Compile Include="SeriesStats\SeriesStatistics.cs" />
|
||||
<Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
namespace NzbDrone.Core.Security
|
||||
{
|
||||
public enum CertificateValidationType
|
||||
{
|
||||
Enabled = 0,
|
||||
DisabledForLocalAddresses = 1,
|
||||
Disabled = 2
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Core.Security
|
||||
{
|
||||
public interface IX509CertificateValidationPolicy
|
||||
{
|
||||
void Register();
|
||||
}
|
||||
|
||||
public class X509CertificateValidationPolicy : IX509CertificateValidationPolicy
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public X509CertificateValidationPolicy(IConfigService configService, Logger logger)
|
||||
{
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Register()
|
||||
{
|
||||
ServicePointManager.ServerCertificateValidationCallback = ShouldByPassValidationError;
|
||||
}
|
||||
|
||||
private bool ShouldByPassValidationError(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
var request = sender as HttpWebRequest;
|
||||
|
||||
if (request == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var req = sender as HttpWebRequest;
|
||||
var cert2 = certificate as X509Certificate2;
|
||||
if (cert2 != null && req != null && cert2.SignatureAlgorithm.FriendlyName == "md5RSA")
|
||||
{
|
||||
_logger.Error("https://{0} uses the obsolete md5 hash in it's https certificate, if that is your certificate, please (re)create certificate with better algorithm as soon as possible.", req.RequestUri.Authority);
|
||||
}
|
||||
|
||||
if (sslPolicyErrors == SslPolicyErrors.None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var host = Dns.GetHostEntry(req.Host);
|
||||
var certificateValidation = _configService.CertificateValidation;
|
||||
|
||||
if (certificateValidation == CertificateValidationType.Disabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (certificateValidation == CertificateValidationType.DisabledForLocalAddresses && host.AddressList.All(i => i.IsLocalAddress()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
_logger.Error("Certificate validation for {0} failed. {1}", request.Address, sslPolicyErrors);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,9 +7,9 @@ using NzbDrone.Common.EnvironmentInfo;
|
|||
using NzbDrone.Common.Exceptions;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using NzbDrone.Common.Processes;
|
||||
using NzbDrone.Common.Security;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Instrumentation;
|
||||
using NzbDrone.Core.Security;
|
||||
|
||||
namespace NzbDrone.Host
|
||||
{
|
||||
|
@ -22,9 +22,6 @@ namespace NzbDrone.Host
|
|||
{
|
||||
try
|
||||
{
|
||||
SecurityProtocolPolicy.Register();
|
||||
X509CertificateValidationPolicy.Register();
|
||||
|
||||
Logger.Info("Starting Sonarr - {0} - Version {1}", Assembly.GetCallingAssembly().Location, Assembly.GetExecutingAssembly().GetName().Version);
|
||||
|
||||
if (!PlatformValidation.IsValidate(userAlert))
|
||||
|
@ -39,6 +36,7 @@ namespace NzbDrone.Host
|
|||
var appMode = GetApplicationMode(startupContext);
|
||||
|
||||
Start(appMode, startupContext);
|
||||
_container.Resolve<IX509CertificateValidationPolicy>().Register();
|
||||
|
||||
if (startCallback != null)
|
||||
{
|
||||
|
|
|
@ -7,7 +7,6 @@ using NzbDrone.Common.EnvironmentInfo;
|
|||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using NzbDrone.Common.Processes;
|
||||
using NzbDrone.Common.Security;
|
||||
using NzbDrone.Update.UpdateEngine;
|
||||
|
||||
namespace NzbDrone.Update
|
||||
|
@ -30,9 +29,6 @@ namespace NzbDrone.Update
|
|||
{
|
||||
try
|
||||
{
|
||||
SecurityProtocolPolicy.Register();
|
||||
X509CertificateValidationPolicy.Register();
|
||||
|
||||
var startupContext = new StartupContext(args);
|
||||
NzbDroneLogger.Register(startupContext, true, true);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Security;
|
||||
using NzbDrone.Core.Update;
|
||||
using Sonarr.Http.REST;
|
||||
|
||||
|
@ -34,6 +35,7 @@ namespace Sonarr.Api.V3.Config
|
|||
public string ProxyPassword { get; set; }
|
||||
public string ProxyBypassFilter { get; set; }
|
||||
public bool ProxyBypassLocalAddresses { get; set; }
|
||||
public CertificateValidationType CertificateValidation { get; set; }
|
||||
public string BackupFolder { get; set; }
|
||||
public int BackupInterval { get; set; }
|
||||
public int BackupRetention { get; set; }
|
||||
|
@ -72,6 +74,7 @@ namespace Sonarr.Api.V3.Config
|
|||
ProxyPassword = configService.ProxyPassword,
|
||||
ProxyBypassFilter = configService.ProxyBypassFilter,
|
||||
ProxyBypassLocalAddresses = configService.ProxyBypassLocalAddresses,
|
||||
CertificateValidation = configService.CertificateValidation,
|
||||
BackupFolder = configService.BackupFolder,
|
||||
BackupInterval = configService.BackupInterval,
|
||||
BackupRetention = configService.BackupRetention
|
||||
|
|
Loading…
Reference in New Issue