Runtime: SSLStream Allow Configuration of CipherSuites

Created on 12 Oct 2017  路  22Comments  路  Source: dotnet/runtime

Rationale

Today you can only configure the available CipherSuites (if at all) on a Machine Wide (maybe process maybe not) Level.

This is problematic let me give you a basic example

I have a webservice, it needs to connect to an internal "Legacy" service so I have to support some old or slow (DHE at large key sizes for instance) set of ciphers. But this means now my webserver has to support clients connecting to it with the same bad crypto.

Solution

Add a method to the SslStream Options bag that is a "list" of supported ciphers. Existing behaviour (System wide settings) would be used if the Cipher Suite List is unsupported.

Also add a few "sets" of industry standard suites to allow users to "fall into the pit of success".

API (With some ideas from @PeterSmithRedmond)

public class SslServerAuthenticationOptions
{
    public ReadOnlyCollection<CipherSuite> {get;set;}
}

public class SslClientAuthenticationOptions
{
     public ReadOnlyCollection<CipherSuite> {get;set;}
}

public struct CipherSuite
{
         public CipherSuite(string cipherSuiteName);

         public static ReadOnlyCollection<CipherSuite> FipsCipherSuite2017 {get;}
         public static ReadOnlyCollection<CipherSuite> MozillaModernCipherSuite {get;}
         public static ReadOnlyCollection<CipherSuite> MozillaIntermediateCipherSuite {get;}
         public static ReadOnlyCollection<CipherSuite> MozillaOldCipherSuite {get;}
         public static ReadOnlyColleciton<CipherSuite> OSStrongCipherSuite {get;}
}

The last one I am not 100% on.

The "defaults" will help people select the right choice.

Refs

dotnet/corefx#24389 This depends on the OptionBag in this issue
dotnet/runtime#22507 This feature wanted on the HttpClient/Handler which this would facilitate
dotnet/runtime#19914 A previous request

api-needs-work area-System.Net.Security

Most helpful comment

@krwq is going to start prototyping a solution here in an upcoming sprint

All 22 comments

FYI this is a blocker on using Kestrel in some of our situations because it needs to connect to older services on the back.

This would also facilitate network security zoning, where communication with endpoints on different zones requires different cipher suites for compliance.

The cipher list supplied should be some form of ordered collection to indicate preference (IReadOnlyList<T> I guess?).

Additionally, I'm not sure if this is relevant to .net's underlying implementation but just in case:

OpenSSL, when running as a server, by default uses the client's cipher list order of preference. It has the SSL_OP_CIPHER_SERVER_PREFERENCE flag to use the server's preference ordering instead.

The implementation of this issue in a server context should either:

  • Always use the user supplied cipher list preference ordering
  • Make the behaviour configurable

@Priya91 @wfurt what do you think about the API proposal? Is it ready for API review?

Does schannel, openssl and securetransport provide knobs to allow this configuration? Is CipherSuite just a string? How does one structure this data?

What is the corresponding RFC that defines the cipher suites, will help in laying down the structure for designing this class

OpenSsl for sure, SChannel... pretty sure, secure transport no idea.

There is no single "RFC" for cipher strings which is a bit of a pain. But there is an IANA registry which tends to follow a single pattern (for the most part).

https://tools.ietf.org/html/rfc5246#appendix-D.3

https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml

I just looked at the online msdn documentation for Schannel, they provide a list of cipher strings and the priority order that the schannel cipher provider picks by default for each windows OS Version, but I couldn't get data on how it can be configured. @davidsh Can you look into this?

For openssl, there's an api called, SSL_CTX_set_cipher_list which just takes in a control string, and has a specific format on how the ciphers need to be concatenated and passed. But it doesn't document anything about the priority order for picking the ciphers, but @DaveRandom notes, it's the order of the client cipher list.

SecureTransport also has a way to configure ciphers, through func SSLSetEnabledCiphers(SSLContext, UnsafePointer<SSLCipherSuite>, Int) and it stores the ciphers as unsigned 32 bit integers typealias SSLCipherSuite = UInt32. So we'll have to maintain a local mapping of the string to int value for osx. There's no documentation about the priority of the chosen cipher.

Depending on how Schannel expects the cipher input to be specified, we can pick how we expose this information to the user.

cc @bartonjs What's the best way we expose cipher values to the user?

I'm a fan of being fairly transparent, given that standard identifiers exist.

https://github.com/dotnet/corefx/blob/1f30ed083ecb9a09bc8c3bbc28f19c13c020a36c/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs#L46

That's "their name from an RFC, and their value from that RFC". Maybe it would need to be prettied up / whittled down; but it seems good to me. Then the input/output is just a List of the enum type. Might require a bit of work to get the various platforms to work with it, but it's the right value from a modelling perspective.

It's also (I believe) a way better thing to report than the current three-part value exposure, since modern TLS has a 4 part value... or a single short-based "suite ID".

Yeah I would go with an enum list, and then you can also give a platform not supported error quickly :)

Another note, TLS isn't all 4 part

TLS_RSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

So even TLS ones are sometimes "Key Exchange"_"Cert Key Type"_"Bulk Cipher"_"Hash"
and other times there is no key exchange (if it matches the cert key type).

In a word, its messy so an enum is clearer,
also OpenSSL uses "ECDHE-ECDSA-AES256-GCM-SHA384" dashes just to mix it up :)

Depending on how Schannel expects the cipher input to be specified, we can pick how we expose this information to the user.

SCHANNEL uses registry keys primarily to set this. And it can be done via Windows Group Policy.

https://docs.microsoft.com/en-us/windows-server/security/tls/tls-registry-settings

https://docs.microsoft.com/en-us/windows-server/security/tls/manage-tls#configuring-tls-cipher-suite-order

I'm not sure there is a way to do it via APIs in SCHANNEL. But you could ask Andrei offline about this.

So SChannel is murky, with really really old documentation but it looks like there are ALG_ID's (you can build a specific allowed cipher suite from the flags)

#define CALG_AES_128            (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_128)
#define CALG_AES_192            (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_192)
#define CALG_AES_256            (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_256)
#define CALG_AES                (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES)
#endif //(NTDDI_VERSION >= NTDDI_WINXP)
#if (NTDDI_VERSION > NTDDI_WINXPSP2)
#define CALG_SHA_256            (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256)
#define CALG_SHA_384            (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_384)
#define CALG_SHA_512            (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_512)
#endif //(NTDDI_VERSION > NTDDI_WINXPSP2)
#if (NTDDI_VERSION >= NTDDI_VISTA)
#define CALG_ECDH               (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_ECDH)
#define CALG_ECDH_EPHEM         (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_ECDH | ALG_SID_ECDH_EPHEM)
#define CALG_ECMQV              (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_ANY | ALG_SID_ECMQV)
#define CALG_ECDSA              (ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | ALG_SID_ECDSA)
#define CALG_NULLCIPHER         (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_ANY | 0)

They are in WinCrypt.h

Next in SCHANNEL when you make the SCHANNEL_CRED at the start you can specify an array of these with the fields

DWORD           cSupportedAlgs;
ALG_ID *        palgSupportedAlgs;

So I believe it is possible, but as you say you have internal people that can help (and maybe they could do something about the docs :P )

https://msdn.microsoft.com/en-us/library/windows/desktop/aa379810(v=vs.85).aspx

There are typically 2 things that people want to do, in my experience;

1) Specify a set of cipher suites in priority order, where every property for each cipher suite is specified explicitly, with some looser specifications at the end of the list
2) Disallow/require certain values for individual properties

OpenSSL's cipher string format allows for both of these things and a combination of the two - specifying preferred exact ciphers, forbidding certain things if the remote party does not support any of the preferred exact combinations. It is, however, somewhat difficult to work with in the sense that it is not particularly readable.

Forbidding certain values for individual components seems like it should be reasonably simple to implement in a sensible way with bitmasks, something like:

```c#
// Only accept SHA-256/384
ctx.AllowedHashAlgorithms = HashAlgorithmType.Sha256 | HashAlgorithmType.Sha384;

// Explicitly forbid MD5
ctx.AllowedHashAlgorithms = HashAlgorithmType.Any & ~HashAlgorithmType.Md5;

// Don't accept DES
ctx.AllowedCipherAlgorithms = CipherAlgorithmType.Any & ~CipherAlgorithmType.Des;

// etc

The explicitly preferred ciphers can specified, perhaps, using something like this:

```c#
ctx.PreferredCiphers = new List<CipherSuite>
{
    // All options explicitly specified
    new CipherSuite(ProtocolType.Tls12, KeyExchangeAlgorithmType.EcDhe, SignatureAlgorithmType.Rsa, CipherAlgorithmType.Aes128, CipherMode.Gcm, HashAlgorithmType.Sha256),

    // Some options left unspecified via overload, defaulting to "any" for that property
    new CipherSuite(ProtocolType.TlsAny, KeyExchangeAlgorithmType.EcDhe, SignatureAlgorithmType.Rsa, CipherAlgorithmType.Aes128),

    // etc
};

The PreferredCiphers are a preference list only, items not present in the list can be used (in the system preference order) if none of the preferred ciphers are supported by the remote party, unless...

```c#
enum PreferredCiphersBehaviours
{
// use the system default preferred cipher order, taking ctx.AllowedCipherAlgorithms etc into account
System,
// use the order specified in ctx.PreferredCiphers, taking ctx.AllowedCipherAlgorithms etc into account
User,
// use the order specified in ctx.PreferredCiphers, ignoring ctx.AllowedCipherAlgorithms etc
// and rejecting anything that does not match
UserStrict,
}

// Default is current behaviour with additional options
ctx.PreferredCiphersBehaviour = PreferredCiphersBehaviours.System;
```

In the context of OpenSSL, this approach would (imho) provide an API that allows the underlying implementation to build a ciphers(1) string while remaining readable.

I do not know how this would interplay with other back-ends, but it seems like it should provide granular enough data to interface with anything that understands these options.

Disclaimers:

This is more of a wishlist than something I have fully researched and believe to be practical. I'm certain it can be made to work as advertised with OpenSSL, though.

Note also that in the above code samples I have used names of things which already exist with members that aren't currently there (e.g. HashAlgorithmType.Sha256), and I suspect the existing entities are probably fundamentally incompatible with using them as above due to their current values not behaving like bitmasks. It's likely that some things would need to be redefined/duplicated to make this work :-/

So you basically described something similar to what I have in my demo managed TLS implementation at

https://github.com/Drawaes/Leto/blob/master/src/Leto/CipherSuites/CipherSuite.cs

Then a bunch of "default" ones setup

https://github.com/Drawaes/Leto/blob/master/src/Leto/CipherSuites/PredefinedCipherSuites.cs

This discussion seems to have petered out.

@Priya91, @bartonjs, @davidsh, what was the verdict on supporting this on Windows?

And, assuming it can be supported well, was there convergence on how this should be exposed to account for cross-platform differences, maintainability as suites grow/change in the future, OS defaults/preferences, etc.? Sounds like enums get a lot of support; could someone in the know provide an updated proposal?

Thanks.

@krwq is going to start prototyping a solution here in an upcoming sprint

@krwq is going to start prototyping a solution here in an upcoming sprint

Any feedback on this @joshfree ?

@TachyonRSA the work is in progress and linked from @krwq above. 馃槃

@TachyonRSA this: https://github.com/dotnet/corefx/pull/35029 contains majority of the code but not the feature itself yet - once I get API approval for that the feature I will create another PR with ciphersuite policy (very likely the API will be different to what is proposed in the other issue - specifically it very likely will be whitelist)

@krwq what is this issue tracking compared to dotnet/corefx#33809?

Note: OpenSsl 1.1.1 or OSX is required. Windows is not supported at the moment.

@krwq where's the tracking issue for Windows?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sahithreddyk picture sahithreddyk  路  3Comments

omajid picture omajid  路  3Comments

noahfalk picture noahfalk  路  3Comments

aggieben picture aggieben  路  3Comments

chunseoklee picture chunseoklee  路  3Comments