diff --git a/frontend/src/Settings/General/SecuritySettings.js b/frontend/src/Settings/General/SecuritySettings.js
index 42c77a46f..07e11277f 100644
--- a/frontend/src/Settings/General/SecuritySettings.js
+++ b/frontend/src/Settings/General/SecuritySettings.js
@@ -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 {
/>
+
+ Certificate Validation
+
+
+
+
+
diff --git a/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs b/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs
new file mode 100644
index 000000000..ce4f3bb0b
--- /dev/null
+++ b/src/NzbDrone.Common/Extensions/IpAddressExtensions.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;
+ }
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs
index 0970a332c..6397f11a5 100644
--- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs
+++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs
@@ -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)
diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj
index 7378e855d..f01f87010 100644
--- a/src/NzbDrone.Common/NzbDrone.Common.csproj
+++ b/src/NzbDrone.Common/NzbDrone.Common.csproj
@@ -102,6 +102,7 @@
+
@@ -216,8 +217,6 @@
-
-
diff --git a/src/NzbDrone.Common/Security/SecurityProtocolPolicy.cs b/src/NzbDrone.Common/Security/SecurityProtocolPolicy.cs
deleted file mode 100644
index 17c625ef6..000000000
--- a/src/NzbDrone.Common/Security/SecurityProtocolPolicy.cs
+++ /dev/null
@@ -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.");
- }
- }
- }
-}
diff --git a/src/NzbDrone.Common/Security/X509CertificateValidationPolicy.cs b/src/NzbDrone.Common/Security/X509CertificateValidationPolicy.cs
deleted file mode 100644
index 1ef25694e..000000000
--- a/src/NzbDrone.Common/Security/X509CertificateValidationPolicy.cs
+++ /dev/null
@@ -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;
- }
- }
-}
diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs
index 105ddfc8d..3568ec7b9 100644
--- a/src/NzbDrone.Core/Configuration/ConfigService.cs
+++ b/src/NzbDrone.Core/Configuration/ConfigService.cs
@@ -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);
diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs
index 33cc40aff..6cf46831d 100644
--- a/src/NzbDrone.Core/Configuration/IConfigService.cs
+++ b/src/NzbDrone.Core/Configuration/IConfigService.cs
@@ -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; }
}
}
diff --git a/src/NzbDrone.Core/Security.cs b/src/NzbDrone.Core/Hashing.cs
similarity index 96%
rename from src/NzbDrone.Core/Security.cs
rename to src/NzbDrone.Core/Hashing.cs
index 840466e98..bf7b9a52b 100644
--- a/src/NzbDrone.Core/Security.cs
+++ b/src/NzbDrone.Core/Hashing.cs
@@ -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)
{
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index 05aab29e3..67f2c49ad 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -1166,7 +1166,9 @@
Code
-
+
+
+
diff --git a/src/NzbDrone.Core/Security/CertificateValidationType.cs b/src/NzbDrone.Core/Security/CertificateValidationType.cs
new file mode 100644
index 000000000..affb2c040
--- /dev/null
+++ b/src/NzbDrone.Core/Security/CertificateValidationType.cs
@@ -0,0 +1,9 @@
+namespace NzbDrone.Core.Security
+{
+ public enum CertificateValidationType
+ {
+ Enabled = 0,
+ DisabledForLocalAddresses = 1,
+ Disabled = 2
+ }
+}
diff --git a/src/NzbDrone.Core/Security/X509CertificateValidationPolicy.cs b/src/NzbDrone.Core/Security/X509CertificateValidationPolicy.cs
new file mode 100644
index 000000000..2dea7f7f0
--- /dev/null
+++ b/src/NzbDrone.Core/Security/X509CertificateValidationPolicy.cs
@@ -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;
+ }
+ }
+}
diff --git a/src/NzbDrone.Host/Bootstrap.cs b/src/NzbDrone.Host/Bootstrap.cs
index 95190dd5d..aec953298 100644
--- a/src/NzbDrone.Host/Bootstrap.cs
+++ b/src/NzbDrone.Host/Bootstrap.cs
@@ -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().Register();
if (startCallback != null)
{
diff --git a/src/NzbDrone.Update/UpdateApp.cs b/src/NzbDrone.Update/UpdateApp.cs
index eda4f17a5..928bbb865 100644
--- a/src/NzbDrone.Update/UpdateApp.cs
+++ b/src/NzbDrone.Update/UpdateApp.cs
@@ -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);
diff --git a/src/Sonarr.Api.V3/Config/HostConfigResource.cs b/src/Sonarr.Api.V3/Config/HostConfigResource.cs
index 4baa5aba1..8fd147dac 100644
--- a/src/Sonarr.Api.V3/Config/HostConfigResource.cs
+++ b/src/Sonarr.Api.V3/Config/HostConfigResource.cs
@@ -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