TLS 1.0 og 1.1 går snart på pension, Microsoft har derfor været så rare at dedikere en hel side til formålet.

TLDR: Frygt ej, der defaulter til TLS 1.2, hvis man ikke har angivet andet i klienten som forbinder til Redis cachen, som dokumentationen så smukt siger her:

Redis .NET clients use the earliest TLS version by default on .NET Framework 4.5.2 or earlier, and use the latest TLS version on .NET Framework 4.6 or later …. Redis .NET Core clients use the latest TLS version by default.

Hvilket i bund og grund betyder: bruger du .NET Core, eller .NET >= 4.6, er du homesafe, mens du på .NET <= 4.5.2 skal du huske at sætte ssl=true,sslprotocols=tls12 hvis du bruger StackExchange.Redis, som alle selvfølgelig gør (da det er en anbefaling fra Microsofts side, og vi elsker at følge anbefalingerne som Microsoft kommer med!). Spøg til side, StackExchange.Redis bliver brugt af Microsoft selv, og det er bare en rimelig fed open source klient, som bliver backed af nogle kloge hoveder, som ved hvad de gør.

Du undrer dig nok over overstående, ligesom mig! Gør du ikke det? sslprotocols, bliver parset til SslProtocols=som defaulter til =None og som sikkert bliver brugt til AuthenticateAsClient(String, X509CertificateCollection, SslProtocols, Boolean)=kaldet i den underliggende =SslStream, så hvorfor skal jeg dog bekymre mig om det? Tjoo, men så har du nok ikke været helt så skarp til at læse Microsofts dokumentation (som jeg hellere ikke har været i dette tilfælde). Lad mig forklare.

Disclaimer: Overstående er faktisk kun den halve sandhed, da jeg ved at /=SslProtocols=* på /=ConfigurationOptions= er en nullable property type, som kan … ja defaulte til ingenting, og hvad sker der så egentlig hvis du i din connectionstring ikke angiver en TLS version? Hvordan håndterer StackExchange.Redis det? SPÆNDENDE!! Lad os dykke ned i koden! Og ja, StackExchange.Redis er open source, så vi kan rent faktisk dykke ned, eller som man siger i Enterprise: lave en deep dive, og få tilfredsstillet vores hunger efter sandheden. *

Din StackExchange.Redis klient gør som sagt brug af en connectionstring til at forbinde til Redis serveren. Denne connectionstring kan bla indeholde sslprotocols=tls11|tls12, ssl=true, eller ssl=true uden en defineret sslprotocols. Findes der en sslProtocols, parses strengen i ConfigurationOptions, i denne metode:

    internal static SslProtocols ParseSslProtocols(string key, string value)
    {
        //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 SslProtocols tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires an SslProtocol value (multiple values separated by '|').");

        return tmp;
    }

ParseSslProtocols retur værdi gemmes i en Nullable property defineret i selvsamme ConfigurationOptions:

    /// <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; }

Selve kommentaren kunne godt give os et hint om hvad der sker hvis sslprotocols ikke er defineret, dog er koden lidt mærkelig, for hvis man sammenligner System.Security.Authentication.SslProtocols enum'en, som er defineret således

    [Flags]
    public enum SslProtocols
    {
        None = 0,
        Ssl2 = 12,
        Ssl3 = 48,
        Tls = 192,
        Default = 240,
        Tls11 = 768,
        Tls12 = 3072
    }

med den SslProtocols property som er defineret i ConfigurationOptions, og sammenholder den med overstående enum, så vil man ved første øjekast tro at den defaulter til None. Denne SslProtocols property bliver brugt i en extension metode til SslStream, ved navn AuthenticateAsClient:

    internal static void AuthenticateAsClient(this SslStream ssl, string host, SslProtocols? allowedProtocols, bool checkCertificateRevocation)
    {
        if (!allowedProtocols.HasValue)
        {
            //Default to the sslProtocols defined by the .NET Framework
            AuthenticateAsClientUsingDefaultProtocols(ssl, host);
            return;
        }

        var certificateCollection = new X509CertificateCollection();
        ssl.AuthenticateAsClient(host, certificateCollection, allowedProtocols.Value, checkCertificateRevocation);
    }

Kaldet hertil ser således ud

    ssl.AuthenticateAsClient(host, config.SslProtocols, config.CheckCertificateRevocation);

hvor ssl er en SslStream og config er vores ConfigurationOptions, og her sprænger kæden. SslProtocols, defaulter til None, men SslProtocols er en nullable, så er sslprotocols ikke defineret i ens connectionstring, indeholder SslProtocols ingenting, og kan derfor ikke defaulte til None, og =None=/betyder/ at det er op til det den underliggende styresystem, at vælge den bedste TLS protokol, MEN FRYGT EJ, for kaldet til AuthenticateAsClientUsingDefaultProtocols kalder vitterligt bare metoden AuthenticateAsClientSslStream objektet. SslStream er beskrevet i stor detalje her og det er selve remark'en, som bringer glæde:

Starting with .NET Framework 4.7, this method authenticates using None, which allows the operating system to choose the best protocol to use, and to block protocols that are not secure. In .NET Framework 4.6 (and .NET Framework 4.5 with the latest security patches installed), the allowed TLS/SSL protocols versions are 1.2, 1.1, and 1.0 (unless you disable strong cryptography by editing the Windows Registry). No client certificates are used in the authentication. The certificate revocation list is not checked during authentication. The value specified for targetHost must match the name on the server's certificate.

Og så giver det hele lige pludselig mening. Det er lige nøjagtig derfor StackExchange.Redis kan lave det some Nullable, og det er lige præcis derfor Microsoft kan skrive som de gør i deres dokumentation omkring pensionering af TLS 1.0 og TLS 1.1. Jeg gik nemlig udfra at den eneste metode der fandtes på SslStream var AuthenticateAsClient(String, X509CertificateCollection, SslProtocols, Boolean) og jeg blev forvirret af StackExchange.Redis' Nullable SslProtocols, da den netop ikke kan falde tilbage til None. Forvirringen var komplet.

Jeg lærte lidt i dag; Det håber jeg også du gjorde.

Det næste du skal gøre, er at se om du falder indenfor det segment, som enten skal tilføje sslProtocols=tls12, ssl=true eller lave de fornødne rettelser, hvis du bruger ServiceStack.Redis. Bruger du .NET Core bruger du allerede seneste TLS version.

Aftermath

Jeg har dog oprettet et issue ved Microsoft som spørger lidt mere ind til ordlyden på deres måde at sige at man kan være sikker på at seneste TLS version bliver brugt, hvis bare man bruger .NET Core, dog er jeg rimelig sikker på at man siden  juni 2018 i Azure (og faktisk siden Windows Server 2016) har shipped med TLS 1.2. Vil man se hvad ens webapp kører med kan man:

  1. vælg webapp resourcen
  2. Via menuen, klik "TLS/SSL Settings" under Settings

Her vil der stå hvad TLS version webapp'en bruger under Mimimum TLS Version. Har man ikke rørt noget ved webapp'ens settings, og er ens webapp født efter juni 2018, så burde den defaulte til TLS 1.2.

Redis Icon by Icon Mafia