| File: System\Net\Security\CipherSuitesPolicyPal.Linux.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.Security\src\System.Net.Security.csproj (System.Net.Security) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Security.Authentication; using System.Text; using Microsoft.Win32.SafeHandles; using OpenSsl = Interop.OpenSsl; using Ssl = Interop.Ssl; namespace System.Net.Security { internal sealed class CipherSuitesPolicyPal { private readonly byte[] _cipherSuites; private readonly byte[] _tls13CipherSuites; private readonly List<TlsCipherSuite> _tlsCipherSuites = new List<TlsCipherSuite>(); internal IEnumerable<TlsCipherSuite> GetCipherSuites() => _tlsCipherSuites; internal CipherSuitesPolicyPal(IEnumerable<TlsCipherSuite> allowedCipherSuites) { if (!Interop.Ssl.Capabilities.Tls13Supported) { throw new PlatformNotSupportedException(SR.net_ssl_ciphersuites_policy_not_supported); } using (SafeSslContextHandle innerContext = Ssl.SslCtxCreate(Ssl.SslMethods.SSLv23_method)) { if (innerContext.IsInvalid) { throw OpenSsl.CreateSslException(SR.net_allocate_ssl_context_failed); } // Create a client SSL object (so that we don't need to worry about certificates) // and use it to get the OpenSSL names for the cipher suites. using (SafeSslHandle ssl = SafeSslHandle.Create(innerContext, new SslAuthenticationOptions() { IsServer = false })) { if (ssl.IsInvalid) { throw OpenSsl.CreateSslException(SR.net_allocate_ssl_context_failed); } using (var tls13CipherSuites = new OpenSslStringBuilder()) using (var cipherSuites = new OpenSslStringBuilder()) { foreach (TlsCipherSuite cs in allowedCipherSuites) { string? name = Interop.Ssl.GetOpenSslCipherSuiteName( ssl, cs, out bool isTls12OrLower); if (name == null) { // we do not have a corresponding name // allowing less than user requested is OK continue; } _tlsCipherSuites.Add(cs); (isTls12OrLower ? cipherSuites : tls13CipherSuites).AllowCipherSuite(name); } _cipherSuites = cipherSuites.GetOpenSslString(); _tls13CipherSuites = tls13CipherSuites.GetOpenSslString(); } } } } internal static bool ShouldOptOutOfTls13(CipherSuitesPolicy? policy, EncryptionPolicy encryptionPolicy) { // if TLS 1.3 was explicitly requested the underlying code will throw // if default option (SslProtocols.None) is used we will opt-out of TLS 1.3 #pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete if (encryptionPolicy == EncryptionPolicy.NoEncryption) { // TLS 1.3 uses different ciphersuite restrictions than previous versions. // It has no equivalent to a NoEncryption option. return true; } #pragma warning restore SYSLIB0040 if (policy == null) { // null means default, by default OpenSSL will choose if it wants to opt-out or not return false; } Debug.Assert( policy.Pal._tls13CipherSuites.Length != 0 && policy.Pal._tls13CipherSuites[policy.Pal._tls13CipherSuites.Length - 1] == 0, "null terminated string expected"); // we should opt out only when policy is empty return policy.Pal._tls13CipherSuites.Length == 1; } internal static bool ShouldOptOutOfLowerThanTls13(CipherSuitesPolicy? policy) { if (policy == null) { // null means default, by default OpenSSL will choose if it wants to opt-out or not return false; } Debug.Assert( policy.Pal._cipherSuites.Length != 0 && policy.Pal._cipherSuites[policy.Pal._cipherSuites.Length - 1] == 0, "null terminated string expected"); // we should opt out only when policy is empty return policy.Pal._cipherSuites.Length == 1; } private static bool IsOnlyTls13(SslProtocols protocols) => protocols == SslProtocols.Tls13; internal static bool WantsTls13(SslProtocols protocols) => protocols == SslProtocols.None || (protocols & SslProtocols.Tls13) != 0; internal static ReadOnlySpan<byte> GetOpenSslCipherList( CipherSuitesPolicy? policy, SslProtocols protocols, EncryptionPolicy encryptionPolicy) { if (IsOnlyTls13(protocols)) { // older cipher suites will be disabled through protocols return default; } if (policy == null) { return CipherListFromEncryptionPolicy(encryptionPolicy); } #pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete if (encryptionPolicy == EncryptionPolicy.NoEncryption) { throw new PlatformNotSupportedException(SR.net_ssl_ciphersuites_policy_not_supported); } #pragma warning restore SYSLIB0040 return policy.Pal._cipherSuites; } internal static byte[]? GetOpenSslCipherSuites( CipherSuitesPolicy? policy, SslProtocols protocols, EncryptionPolicy encryptionPolicy) { if (!WantsTls13(protocols) || policy == null) { // do not call TLS 1.3 API, let OpenSSL choose what to do return null; } #pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete if (encryptionPolicy == EncryptionPolicy.NoEncryption) { throw new PlatformNotSupportedException(SR.net_ssl_ciphersuites_policy_not_supported); } #pragma warning restore SYSLIB0040 return policy.Pal._tls13CipherSuites; } private static ReadOnlySpan<byte> CipherListFromEncryptionPolicy(EncryptionPolicy policy) { switch (policy) { case EncryptionPolicy.RequireEncryption: return default; #pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete case EncryptionPolicy.AllowNoEncryption: return "ALL:eNULL\0"u8; case EncryptionPolicy.NoEncryption: return "eNULL\0"u8; #pragma warning restore SYSLIB0040 default: Debug.Fail($"Unknown EncryptionPolicy value ({policy})"); return default; } } private sealed class OpenSslStringBuilder : StreamWriter { private const string SSL_TXT_Separator = ":"; private static readonly byte[] EmptyString = new byte[1] { 0 }; private readonly MemoryStream _ms; private bool _first = true; public OpenSslStringBuilder() : base(new MemoryStream(), Encoding.ASCII) { _ms = (MemoryStream)BaseStream; } public void AllowCipherSuite(string cipherSuite) { if (_first) { _first = false; } else { Write(SSL_TXT_Separator); } Write(cipherSuite); } public byte[] GetOpenSslString() { if (_first) { return EmptyString; } Flush(); _ms.WriteByte(0); return _ms.ToArray(); } } } }