Commit 23a1c881 authored by Jon Cole's avatar Jon Cole

Add SslProtocols property to ConfigurationOptions

parent c6d8aec2
......@@ -2,6 +2,7 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Authentication;
using System.Threading.Tasks;
using NUnit.Framework;
using StackExchange.Redis;
......@@ -187,6 +188,42 @@ public void CreateDisconnectedNonsenseConnection_DNS()
}
}
[Test]
public void SslProtocols_SingleValue()
{
var log = new StringWriter();
var options = ConfigurationOptions.Parse("myhost,sslProtocols=Tls11");
Assert.AreEqual(SslProtocols.Tls11, options.SslProtocols.Value);
}
[Test]
public void SslProtocols_MultipleValues()
{
var log = new StringWriter();
var options = ConfigurationOptions.Parse("myhost,sslProtocols=Tls11|Tls12");
Assert.AreEqual(SslProtocols.Tls11|SslProtocols.Tls12, options.SslProtocols.Value);
}
[Test]
public void SslProtocols_UsingIntegerValue()
{
var log = new StringWriter();
// The below scenario is for cases where the *targeted*
// .NET framework version (e.g. .NET 4.0) doesn't define an enum value (e.g. Tls11)
// but the OS has been patched with support
int integerValue = (int)(SslProtocols.Tls11 | SslProtocols.Tls12);
var options = ConfigurationOptions.Parse("myhost,sslProtocols=" + integerValue);
Assert.AreEqual(SslProtocols.Tls11 | SslProtocols.Tls12, options.SslProtocols.Value);
}
[Test]
public void SslProtocols_InvalidValue()
{
var log = new StringWriter();
Assert.Throws<ArgumentOutOfRangeException>(() => ConfigurationOptions.Parse("myhost,sslProtocols=InvalidSslProtocol"));
}
[Test]
public void ConfigurationOptionsDefaultForAzure()
{
......
......@@ -5,6 +5,7 @@
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
......@@ -65,7 +66,18 @@ internal static Proxy ParseProxy(string key, string value)
if (!Enum.TryParse(value, true, out tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires a proxy value");
return tmp;
}
#if !CORE_CLR
internal static SslProtocols ParseSslProtocols(string key, string value)
{
SslProtocols tmp;
//Flags expect commas as separators, but we need to use '|' since commas are already used in the connection string to mean something else
value = value?.Replace("|", ",");
if (!Enum.TryParse(value, true, out tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires an SslProtocol value (multiple values separated by '|').");
return tmp;
}
#endif
internal static void Unknown(string key)
{
throw new ArgumentException("Keyword '" + key + "' is not supported");
......@@ -78,6 +90,10 @@ internal static void Unknown(string key)
ConfigChannel = "configChannel", AbortOnConnectFail = "abortConnect", ResolveDns = "resolveDns",
ChannelPrefix = "channelPrefix", Proxy = "proxy", ConnectRetry = "connectRetry",
ConfigCheckSeconds = "configCheckSeconds", ResponseTimeout = "responseTimeout", DefaultDatabase = "defaultDatabase";
#if !CORE_CLR
internal const string SslProtocols = "sslProtocols";
#endif
private static readonly Dictionary<string, string> normalizedOptions = new[]
{
AllowAdmin, SyncTimeout,
......@@ -87,6 +103,9 @@ internal static void Unknown(string key)
ConfigChannel, AbortOnConnectFail, ResolveDns,
ChannelPrefix, Proxy, ConnectRetry,
ConfigCheckSeconds, DefaultDatabase,
#if !CORE_CLR
SslProtocols,
#endif
}.ToDictionary(x => x, StringComparer.OrdinalIgnoreCase);
public static string TryNormalize(string value)
......@@ -156,6 +175,13 @@ public static string TryNormalize(string value)
/// </summary>
public bool Ssl { get { return ssl.GetValueOrDefault(); } set { ssl = value; } }
#if !CORE_CLR
/// <summary>
/// Configures which Ssl/TLS protocols should be allowed. If not set, defaults are chosen by the .NET framework.
/// </summary>
public SslProtocols? SslProtocols { get; set; }
#endif
/// <summary>
/// Automatically encodes and decodes channels
/// </summary>
......@@ -359,6 +385,9 @@ public ConfigurationOptions Clone()
responseTimeout = responseTimeout,
defaultDatabase = defaultDatabase,
ReconnectRetryPolicy = reconnectRetryPolicy,
#if !CORE_CLR
SslProtocols = SslProtocols,
#endif
};
foreach (var item in endpoints)
options.endpoints.Add(item);
......@@ -622,6 +651,11 @@ private void DoParse(string configuration, bool ignoreUnknown)
case OptionKeys.DefaultDatabase:
defaultDatabase = OptionKeys.ParseInt32(key, value);
break;
#if !CORE_CLR
case OptionKeys.SslProtocols:
SslProtocols = OptionKeys.ParseSslProtocols(key, value);
break;
#endif
default:
if (!string.IsNullOrEmpty(key) && key[0] == '$')
{
......
......@@ -786,7 +786,18 @@ SocketMode ISocketCallback.Connected(Stream stream, TextWriter log)
#if CORE_CLR
ssl.AuthenticateAsClientAsync(host).GetAwaiter().GetResult();
#else
if (config.SslProtocols.HasValue)
{
var allowedProtocols = config.SslProtocols.Value;
ssl.AuthenticateAsClient(host, new X509CertificateCollection(), allowedProtocols, checkCertificateRevocation: true);
}
else
{
// default to defaults for the .NET framework
ssl.AuthenticateAsClient(host);
}
Multiplexer.LogLocked(log, $"SSL connection established successfully using protocol: {ssl.SslProtocol}");
#endif
}
catch (AuthenticationException authexception)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment