From 28199ab4be64da1b6c4d024e9af912a262ea5c6f Mon Sep 17 00:00:00 2001 From: JigSaw Date: Sat, 14 May 2016 00:46:56 +0200 Subject: [PATCH] SSL Fix by default, Added support of TLS 1.1 & 1.2 (#337) * SSL Fix by default, Now use TLS (1.2, 1.1, 1) by default * Workaround to use TLS 1.2 & 1.1 on Mono < 4.3 --- src/CurlSharp/SSLFix.cs | 27 +- src/Jackett/CurlHelper.cs | 4 +- src/Jackett/Utils/Clients/HttpWebClient.cs | 7 + .../Utils/Clients/UnixSafeCurlWebClient.cs | 334 +++++++++--------- 4 files changed, 197 insertions(+), 175 deletions(-) diff --git a/src/CurlSharp/SSLFix.cs b/src/CurlSharp/SSLFix.cs index 940767f93..d1af77f7b 100644 --- a/src/CurlSharp/SSLFix.cs +++ b/src/CurlSharp/SSLFix.cs @@ -1,13 +1,28 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.ObjectModel; namespace CurlSharp { + /// + /// Our SSL FIX for CURL contain authorized Ciphers for SSL Communications + /// public class SSLFix { - public const string CipherList = "rsa_aes_128_sha,ecdhe_rsa_aes_256_sha,ecdhe_ecdsa_aes_128_sha"; + // Our CiphersList + private static readonly ReadOnlyCollection Ciphers = new ReadOnlyCollection( new[] { + // Default supported ciphers by Jackett + "rsa_aes_128_sha", + "ecdhe_rsa_aes_256_sha", + "ecdhe_ecdsa_aes_128_sha" + }); + + /// + /// List of ciphers supported by Jackett + /// + /// Formatted string of ciphers + public static string CiphersList() + { + // Comma-Separated list of ciphers + return string.Join(",", Ciphers); + } } } diff --git a/src/Jackett/CurlHelper.cs b/src/Jackett/CurlHelper.cs index fe58143e4..82e369700 100644 --- a/src/Jackett/CurlHelper.cs +++ b/src/Jackett/CurlHelper.cs @@ -127,11 +127,11 @@ namespace Jackett } } - if (Startup.DoSSLFix == true) + if (Startup.DoSSLFix.GetValueOrDefault(true)) { // http://stackoverflow.com/questions/31107851/how-to-fix-curl-35-cannot-communicate-securely-with-peer-no-common-encryptio // https://git.fedorahosted.org/cgit/mod_nss.git/plain/docs/mod_nss.html - easy.SslCipherList = SSLFix.CipherList; + easy.SslCipherList = SSLFix.CiphersList(); easy.FreshConnect = true; easy.ForbidReuse = true; } diff --git a/src/Jackett/Utils/Clients/HttpWebClient.cs b/src/Jackett/Utils/Clients/HttpWebClient.cs index 2e2602d05..9bf8e5c42 100644 --- a/src/Jackett/Utils/Clients/HttpWebClient.cs +++ b/src/Jackett/Utils/Clients/HttpWebClient.cs @@ -69,6 +69,13 @@ namespace Jackett.Utils.Clients proxyServer = new WebProxy(Startup.ProxyConnection, false); useProxy = true; } + + // SecurityProtocolType values below not available in Mono < 4.3 + const int SecurityProtocolTypeTls11 = 768; + const int SecurityProtocolTypeTls12 = 3072; + // Specify to use TLS 1.2 as default connection + ServicePointManager.SecurityProtocol |= (SecurityProtocolType)(SecurityProtocolTypeTls12 | SecurityProtocolTypeTls11); + var client = new HttpClient(new HttpClientHandler { CookieContainer = cookies, diff --git a/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs b/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs index cecef59ea..ce2cf615b 100644 --- a/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs +++ b/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs @@ -1,116 +1,116 @@ -using AutoMapper; -using CurlSharp; -using Jackett.Models; -using Jackett.Services; -using NLog; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; - -namespace Jackett.Utils.Clients -{ - public class UnixSafeCurlWebClient : IWebClient - { - IProcessService processService; - Logger logger; - IConfigurationService configService; - - public UnixSafeCurlWebClient(IProcessService p, Logger l, IConfigurationService c) - { - processService = p; - logger = l; - configService = c; - } - - public void Init() - { - } - - public async Task GetBytes(WebRequest request) - { - logger.Debug(string.Format("UnixSafeCurlWebClient:GetBytes(Url:{0})", request.Url)); - var result = await Run(request); - logger.Debug(string.Format("UnixSafeCurlWebClient: Returning {0} => {1} bytes", result.Status, (result.Content == null ? "" : result.Content.Length.ToString()))); - return result; - } - - public async Task GetString(WebRequest request) - { - logger.Debug(string.Format("UnixSafeCurlWebClient:GetString(Url:{0})", request.Url)); - var result = await Run(request); - logger.Debug(string.Format("UnixSafeCurlWebClient: Returning {0} => {1}", result.Status, (result.Content == null ? "" : Encoding.UTF8.GetString(result.Content)))); - return Mapper.Map(result); - } - - private async Task Run(WebRequest request) - { - var args = new StringBuilder(); +using AutoMapper; +using CurlSharp; +using Jackett.Models; +using Jackett.Services; +using NLog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Utils.Clients +{ + public class UnixSafeCurlWebClient : IWebClient + { + IProcessService processService; + Logger logger; + IConfigurationService configService; + + public UnixSafeCurlWebClient(IProcessService p, Logger l, IConfigurationService c) + { + processService = p; + logger = l; + configService = c; + } + + public void Init() + { + } + + public async Task GetBytes(WebRequest request) + { + logger.Debug(string.Format("UnixSafeCurlWebClient:GetBytes(Url:{0})", request.Url)); + var result = await Run(request); + logger.Debug(string.Format("UnixSafeCurlWebClient: Returning {0} => {1} bytes", result.Status, (result.Content == null ? "" : result.Content.Length.ToString()))); + return result; + } + + public async Task GetString(WebRequest request) + { + logger.Debug(string.Format("UnixSafeCurlWebClient:GetString(Url:{0})", request.Url)); + var result = await Run(request); + logger.Debug(string.Format("UnixSafeCurlWebClient: Returning {0} => {1}", result.Status, (result.Content == null ? "" : Encoding.UTF8.GetString(result.Content)))); + return Mapper.Map(result); + } + + private async Task Run(WebRequest request) + { + var args = new StringBuilder(); if (Startup.ProxyConnection != null) { args.AppendFormat("-x " + Startup.ProxyConnection + " "); } - - args.AppendFormat("--url \"{0}\" ", request.Url); - - if (request.EmulateBrowser) - args.AppendFormat("-i -sS --user-agent \"{0}\" ", BrowserUtil.ChromeUserAgent); - else - args.AppendFormat("-i -sS --user-agent \"{0}\" ", "Jackett/" + configService.GetVersion()); - - if (!string.IsNullOrWhiteSpace(request.Cookies)) - { - args.AppendFormat("--cookie \"{0}\" ", request.Cookies); - } - - if (!string.IsNullOrWhiteSpace(request.Referer)) - { - args.AppendFormat("--referer \"{0}\" ", request.Referer); - } - - if (!string.IsNullOrEmpty(request.RawBody)) - { - var postString = StringUtil.PostDataFromDict(request.PostData); - args.AppendFormat("--data \"{0}\" ", request.RawBody.Replace("\"", "\\\"")); - } else if (request.PostData != null && request.PostData.Count() > 0) - { - var postString = StringUtil.PostDataFromDict(request.PostData); - args.AppendFormat("--data \"{0}\" ", postString); - } - - var tempFile = Path.GetTempFileName(); - args.AppendFormat("--output \"{0}\" ", tempFile); - - if (Startup.DoSSLFix == true) - { - // http://stackoverflow.com/questions/31107851/how-to-fix-curl-35-cannot-communicate-securely-with-peer-no-common-encryptio - // https://git.fedorahosted.org/cgit/mod_nss.git/plain/docs/mod_nss.html - args.Append("--cipher " + SSLFix.CipherList); - } + + args.AppendFormat("--url \"{0}\" ", request.Url); + + if (request.EmulateBrowser) + args.AppendFormat("-i -sS --user-agent \"{0}\" ", BrowserUtil.ChromeUserAgent); + else + args.AppendFormat("-i -sS --user-agent \"{0}\" ", "Jackett/" + configService.GetVersion()); + + if (!string.IsNullOrWhiteSpace(request.Cookies)) + { + args.AppendFormat("--cookie \"{0}\" ", request.Cookies); + } + + if (!string.IsNullOrWhiteSpace(request.Referer)) + { + args.AppendFormat("--referer \"{0}\" ", request.Referer); + } + + if (!string.IsNullOrEmpty(request.RawBody)) + { + var postString = StringUtil.PostDataFromDict(request.PostData); + args.AppendFormat("--data \"{0}\" ", request.RawBody.Replace("\"", "\\\"")); + } else if (request.PostData != null && request.PostData.Count() > 0) + { + var postString = StringUtil.PostDataFromDict(request.PostData); + args.AppendFormat("--data \"{0}\" ", postString); + } + + var tempFile = Path.GetTempFileName(); + args.AppendFormat("--output \"{0}\" ", tempFile); + + if (Startup.DoSSLFix.GetValueOrDefault(true)) + { + // http://stackoverflow.com/questions/31107851/how-to-fix-curl-35-cannot-communicate-securely-with-peer-no-common-encryptio + // https://git.fedorahosted.org/cgit/mod_nss.git/plain/docs/mod_nss.html + args.Append("--cipher " + SSLFix.CiphersList()); + } if (Startup.IgnoreSslErrors == true) { args.Append("-k "); - } - args.Append("-H \"Accept-Language: en-US,en\" "); - args.Append("-H \"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\" "); - string stdout = null; - await Task.Run(() => - { - stdout = processService.StartProcessAndGetOutput(System.Environment.OSVersion.Platform == PlatformID.Unix ? "curl" : "curl.exe", args.ToString() , true); - }); - - var outputData = File.ReadAllBytes(tempFile); - File.Delete(tempFile); - stdout = Encoding.UTF8.GetString(outputData); - var result = new WebClientByteResult(); - var headSplit = stdout.IndexOf("\r\n\r\n"); - if (headSplit < 0) - throw new Exception("Invalid response"); - var headers = stdout.Substring(0, headSplit); + } + args.Append("-H \"Accept-Language: en-US,en\" "); + args.Append("-H \"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\" "); + string stdout = null; + await Task.Run(() => + { + stdout = processService.StartProcessAndGetOutput(System.Environment.OSVersion.Platform == PlatformID.Unix ? "curl" : "curl.exe", args.ToString() , true); + }); + + var outputData = File.ReadAllBytes(tempFile); + File.Delete(tempFile); + stdout = Encoding.UTF8.GetString(outputData); + var result = new WebClientByteResult(); + var headSplit = stdout.IndexOf("\r\n\r\n"); + if (headSplit < 0) + throw new Exception("Invalid response"); + var headers = stdout.Substring(0, headSplit); if (Startup.ProxyConnection != null) { // the proxy provided headers too so we need to split headers again @@ -121,39 +121,39 @@ namespace Jackett.Utils.Clients headSplit = headSplit1; } } - var headerCount = 0; - var cookieBuilder = new StringBuilder(); - var cookies = new List>(); - - foreach (var header in headers.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)) - { - if (headerCount == 0) - { - var responseCode = int.Parse(header.Split(' ')[1]); - result.Status = (HttpStatusCode)responseCode; - } - else - { - var headerSplitIndex = header.IndexOf(':'); - if (headerSplitIndex > 0) - { - var name = header.Substring(0, headerSplitIndex).ToLowerInvariant(); - var value = header.Substring(headerSplitIndex + 1); - switch (name) - { - case "set-cookie": - var nameSplit = value.IndexOf('='); - if (nameSplit > -1) - { - cookies.Add(new Tuple(value.Substring(0, nameSplit), value.Substring(0, value.IndexOf(';') + 1))); - } - break; - case "location": - result.RedirectingTo = value.Trim(); - break; - case "refresh": - //"Refresh: 8;URL=/cdn-cgi/l/chk_jschl?pass=1451000679.092-1vJFUJLb9R" - var redirval = ""; + var headerCount = 0; + var cookieBuilder = new StringBuilder(); + var cookies = new List>(); + + foreach (var header in headers.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)) + { + if (headerCount == 0) + { + var responseCode = int.Parse(header.Split(' ')[1]); + result.Status = (HttpStatusCode)responseCode; + } + else + { + var headerSplitIndex = header.IndexOf(':'); + if (headerSplitIndex > 0) + { + var name = header.Substring(0, headerSplitIndex).ToLowerInvariant(); + var value = header.Substring(headerSplitIndex + 1); + switch (name) + { + case "set-cookie": + var nameSplit = value.IndexOf('='); + if (nameSplit > -1) + { + cookies.Add(new Tuple(value.Substring(0, nameSplit), value.Substring(0, value.IndexOf(';') + 1))); + } + break; + case "location": + result.RedirectingTo = value.Trim(); + break; + case "refresh": + //"Refresh: 8;URL=/cdn-cgi/l/chk_jschl?pass=1451000679.092-1vJFUJLb9R" + var redirval = ""; var start = value.IndexOf("="); var end = value.IndexOf(";"); var len = value.Length; @@ -167,31 +167,31 @@ namespace Jackett.Utils.Clients result.Status = System.Net.HttpStatusCode.Redirect; var redirtime = Int32.Parse(value.Substring(0, end)); System.Threading.Thread.Sleep(redirtime * 1000); - } - break; - } - } - } - headerCount++; - } - - foreach (var cookieGroup in cookies.GroupBy(c => c.Item1)) - { - cookieBuilder.AppendFormat("{0} ", cookieGroup.Last().Item2); - } - - result.Cookies = cookieBuilder.ToString().Trim(); - result.Content = new byte[outputData.Length - (headSplit + 3)]; - var dest = 0; - for (int i = headSplit + 4; i < outputData.Length; i++) - { - result.Content[dest] = outputData[i]; - dest++; - } - - logger.Debug("WebClientByteResult returned " + result.Status); - ServerUtil.ResureRedirectIsFullyQualified(request, result); - return result; - } - } -} + } + break; + } + } + } + headerCount++; + } + + foreach (var cookieGroup in cookies.GroupBy(c => c.Item1)) + { + cookieBuilder.AppendFormat("{0} ", cookieGroup.Last().Item2); + } + + result.Cookies = cookieBuilder.ToString().Trim(); + result.Content = new byte[outputData.Length - (headSplit + 3)]; + var dest = 0; + for (int i = headSplit + 4; i < outputData.Length; i++) + { + result.Content[dest] = outputData[i]; + dest++; + } + + logger.Debug("WebClientByteResult returned " + result.Status); + ServerUtil.ResureRedirectIsFullyQualified(request, result); + return result; + } + } +}