Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using System.IO;
using System.Net;
using System.Net.Security;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Authentication.ExtendedProtection;
Expand Down Expand Up @@ -45,14 +44,19 @@ internal static partial class OpenSsl
return bindingHandle;
}

internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX509Handle? certHandle, SafeEvpPKeyHandle? certKeyHandle, EncryptionPolicy policy, SslAuthenticationOptions sslAuthenticationOptions)
// This essentially wraps SSL_CTX* aka SSL_CTX_new + setting
internal static SafeSslContextHandle AllocateSslContext(SafeFreeSslCredentials credential, SslAuthenticationOptions sslAuthenticationOptions)
{
SafeSslHandle? context = null;
SslProtocols protocols = sslAuthenticationOptions.EnabledSslProtocols;
SafeX509Handle? certHandle = credential.CertHandle;
SafeEvpPKeyHandle? certKeyHandle = credential.CertKeyHandle;


// Always use SSLv23_method, regardless of protocols. It supports negotiating to the highest
// mutually supported version and can thus handle any of the set protocols, and we then use
// SetProtocolOptions to ensure we only allow the ones requested.
using (SafeSslContextHandle innerContext = Ssl.SslCtxCreate(Ssl.SslMethods.SSLv23_method))
SafeSslContextHandle innerContext = Ssl.SslCtxCreate(Ssl.SslMethods.SSLv23_method);
try
{
if (innerContext.IsInvalid)
{
Expand All @@ -61,14 +65,14 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50

if (!Interop.Ssl.Tls13Supported)
{
if (protocols != SslProtocols.None &&
if (sslAuthenticationOptions.EnabledSslProtocols != SslProtocols.None &&
CipherSuitesPolicyPal.WantsTls13(protocols))
{
protocols = protocols & (~SslProtocols.Tls13);
}
}
else if (CipherSuitesPolicyPal.WantsTls13(protocols) &&
CipherSuitesPolicyPal.ShouldOptOutOfTls13(sslAuthenticationOptions.CipherSuitesPolicy, policy))
CipherSuitesPolicyPal.ShouldOptOutOfTls13(sslAuthenticationOptions.CipherSuitesPolicy, sslAuthenticationOptions.EncryptionPolicy))
{
if (protocols == SslProtocols.None)
{
Expand All @@ -81,31 +85,33 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50
{
// user explicitly asks for TLS 1.3 but their policy is not compatible with TLS 1.3
throw new SslException(
SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy));
SR.Format(SR.net_ssl_encryptionpolicy_notsupported, sslAuthenticationOptions.EncryptionPolicy));
}
}

if (CipherSuitesPolicyPal.ShouldOptOutOfLowerThanTls13(sslAuthenticationOptions.CipherSuitesPolicy, policy))
if (CipherSuitesPolicyPal.ShouldOptOutOfLowerThanTls13(sslAuthenticationOptions.CipherSuitesPolicy, sslAuthenticationOptions.EncryptionPolicy))
{
if (!CipherSuitesPolicyPal.WantsTls13(protocols))
{
// We cannot provide neither TLS 1.3 or non TLS 1.3, user disabled all cipher suites
throw new SslException(
SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy));
SR.Format(SR.net_ssl_encryptionpolicy_notsupported, sslAuthenticationOptions.EncryptionPolicy));
}

protocols = SslProtocols.Tls13;
}

// Configure allowed protocols. It's ok to use DangerousGetHandle here without AddRef/Release as we just
// create the handle, it's rooted by the using, no one else has a reference to it, etc.
Ssl.SetProtocolOptions(innerContext.DangerousGetHandle(), protocols);
Ssl.SetProtocolOptions(innerContext, protocols);

// Sets policy and security level
if (!Ssl.SetEncryptionPolicy(innerContext, policy))
if (sslAuthenticationOptions.EncryptionPolicy != EncryptionPolicy.RequireEncryption)
{
throw new SslException(
SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy));
// Sets policy and security level
if (!Ssl.SetEncryptionPolicy(innerContext, sslAuthenticationOptions.EncryptionPolicy))
{
throw new SslException( SR.Format(SR.net_ssl_encryptionpolicy_notsupported, sslAuthenticationOptions.EncryptionPolicy));
}
}

// The logic in SafeSslHandle.Disconnect is simple because we are doing a quiet
Expand All @@ -117,26 +123,11 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50
// https://www.openssl.org/docs/manmaster/ssl/SSL_shutdown.html
Ssl.SslCtxSetQuietShutdown(innerContext);

byte[]? cipherList =
CipherSuitesPolicyPal.GetOpenSslCipherList(sslAuthenticationOptions.CipherSuitesPolicy, protocols, policy);

Debug.Assert(cipherList == null || (cipherList.Length >= 1 && cipherList[cipherList.Length - 1] == 0));

byte[]? cipherSuites =
CipherSuitesPolicyPal.GetOpenSslCipherSuites(sslAuthenticationOptions.CipherSuitesPolicy, protocols, policy);

Debug.Assert(cipherSuites == null || (cipherSuites.Length >= 1 && cipherSuites[cipherSuites.Length - 1] == 0));

unsafe
if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0)
{
fixed (byte* cipherListStr = cipherList)
fixed (byte* cipherSuitesStr = cipherSuites)
unsafe
{
if (!Ssl.SetCiphers(innerContext, cipherListStr, cipherSuitesStr))
{
Crypto.ErrClearError();
throw new PlatformNotSupportedException(SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy));
}
Interop.Ssl.SslCtxSetAlpnSelectCb(innerContext, &AlpnServerSelectCallback, IntPtr.Zero);
}
}

Expand All @@ -149,45 +140,75 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50
SetSslCertificate(innerContext, certHandle!, certKeyHandle!);
}

if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.RemoteCertRequired)
{
unsafe
{
Ssl.SslCtxSetVerify(innerContext, &VerifyClientCertificate);
}
}
}
catch
{
innerContext.Dispose();
throw;
}

return innerContext;
}

// This essentially wraps SSL* SSL_new()
internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credential, SslAuthenticationOptions sslAuthenticationOptions)
{
SafeSslHandle? context = null;
SafeSslContextHandle? sslCtx = null;
SafeSslContextHandle? innerContext = null;
SslProtocols protocols = sslAuthenticationOptions.EnabledSslProtocols;
bool cacheSslContext = sslAuthenticationOptions.EncryptionPolicy == EncryptionPolicy.RequireEncryption &&
(!sslAuthenticationOptions.IsServer || (sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0));

if (sslAuthenticationOptions.CertificateContext != null && cacheSslContext)
{
sslAuthenticationOptions.CertificateContext.contexts.TryGetValue((int)sslAuthenticationOptions.EnabledSslProtocols, out sslCtx);
}

if (sslCtx == null)
{
// We did not get SslContext from cache
sslCtx = innerContext = AllocateSslContext(credential, sslAuthenticationOptions);
}

try
{
GCHandle alpnHandle = default;
try
{
context = SafeSslHandle.Create(sslCtx, sslAuthenticationOptions.IsServer);
Debug.Assert(context != null, "Expected non-null return value from SafeSslHandle.Create");
if (context.IsInvalid)
{
context.Dispose();
throw CreateSslException(SR.net_allocate_ssl_context_failed);
}

if (sslAuthenticationOptions.EncryptionPolicy != EncryptionPolicy.RequireEncryption)
{
// Sets policy and security level
if (!Ssl.SetEncryptionPolicy(sslCtx, sslAuthenticationOptions.EncryptionPolicy))
{
throw new SslException( SR.Format(SR.net_ssl_encryptionpolicy_notsupported, sslAuthenticationOptions.EncryptionPolicy));
}
}

if (sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0)
{
if (sslAuthenticationOptions.IsServer)
{
alpnHandle = GCHandle.Alloc(sslAuthenticationOptions.ApplicationProtocols);

unsafe
{
Interop.Ssl.SslCtxSetAlpnSelectCb(innerContext, &AlpnServerSelectCallback, GCHandle.ToIntPtr(alpnHandle));
}
Interop.Ssl.SslSetData(context, GCHandle.ToIntPtr(alpnHandle));
}
else
{
if (Interop.Ssl.SslCtxSetAlpnProtos(innerContext, sslAuthenticationOptions.ApplicationProtocols) != 0)
if (Interop.Ssl.SslSetAlpnProtos(context, sslAuthenticationOptions.ApplicationProtocols) != 0)
{
throw CreateSslException(SR.net_alpn_config_failed);
}
}
}

context = SafeSslHandle.Create(innerContext, sslAuthenticationOptions.IsServer);
Debug.Assert(context != null, "Expected non-null return value from SafeSslHandle.Create");
if (context.IsInvalid)
{
context.Dispose();
throw CreateSslException(SR.net_allocate_ssl_context_failed);
}

if (!sslAuthenticationOptions.IsServer)
{
// The IdnMapping converts unicode input into the IDNA punycode sequence.
Expand All @@ -200,6 +221,29 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50
}
}

byte[]? cipherList =
CipherSuitesPolicyPal.GetOpenSslCipherList(sslAuthenticationOptions.CipherSuitesPolicy, protocols, sslAuthenticationOptions.EncryptionPolicy);

Debug.Assert(cipherList == null || (cipherList.Length >= 1 && cipherList[cipherList.Length - 1] == 0));

byte[]? cipherSuites =
CipherSuitesPolicyPal.GetOpenSslCipherSuites(sslAuthenticationOptions.CipherSuitesPolicy, protocols, sslAuthenticationOptions.EncryptionPolicy);

Debug.Assert(cipherSuites == null || (cipherSuites.Length >= 1 && cipherSuites[cipherSuites.Length - 1] == 0));

unsafe
{
fixed (byte* cipherListStr = cipherList)
fixed (byte* cipherSuitesStr = cipherSuites)
{
if (!Ssl.SslSetCiphers(context, cipherListStr, cipherSuitesStr))
{
Crypto.ErrClearError();
throw new PlatformNotSupportedException(SR.Format(SR.net_ssl_encryptionpolicy_notsupported, sslAuthenticationOptions.EncryptionPolicy));
}
}
}

if (sslAuthenticationOptions.CertificateContext != null && sslAuthenticationOptions.CertificateContext.IntermediateCertificates.Length > 0)
{
if (!Ssl.AddExtraChainCertificates(context, sslAuthenticationOptions.CertificateContext!.IntermediateCertificates))
Expand All @@ -209,6 +253,15 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50
}

context.AlpnHandle = alpnHandle;


if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.RemoteCertRequired)
{
unsafe
{
Ssl.SslSetVerifyPeer(context);
}
}
}
catch
{
Expand All @@ -220,6 +273,18 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50
throw;
}
}
finally
{
if (innerContext != null && cacheSslContext)
{
// We allocated new context
if (sslAuthenticationOptions.CertificateContext?.contexts == null ||
!sslAuthenticationOptions.CertificateContext.contexts.TryAdd((int)sslAuthenticationOptions.EnabledSslProtocols, innerContext))
{
innerContext.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the event that we're not caching, what happens here? Seems like we'll have created an SSL_CTX, from that we created an SSL, then without doing anything yet we call dispose on the SSL_CTX handle, which might call SSL_shutdown.

I feel like I must be missing something.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think disposing the context would call SSL_shutdown. It does not have data to do so and it would only call Interop.Ssl.SslCtxDestroy().

We already do that 100% in existing code:

internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX509Handle? certHandle, SafeEvpPKeyHandle? certKeyHandle, EncryptionPolicy policy, SslAuthenticationOptions sslAuthenticationOptions)
{
SafeSslHandle? context = null;
// Always use SSLv23_method, regardless of protocols. It supports negotiating to the highest
// mutually supported version and can thus handle any of the set protocols, and we then use
// SetProtocolOptions to ensure we only allow the ones requested.
using (SafeSslContextHandle innerContext = Ssl.SslCtxCreate(Ssl.SslMethods.SSLv23_method))

This logic is there to skip it if we put the context to cache.

}
}
}

return context;
}
Expand Down Expand Up @@ -441,8 +506,9 @@ private static unsafe int AlpnServerSelectCallback(IntPtr ssl, byte** outp, byte
{
*outp = null;
*outlen = 0;
IntPtr sslData = Ssl.SslGetData(ssl);

GCHandle protocolHandle = GCHandle.FromIntPtr(arg);
GCHandle protocolHandle = GCHandle.FromIntPtr(sslData);
if (!(protocolHandle.Target is List<SslApplicationProtocol> protocolList))
{
return Ssl.SSL_TLSEXT_ERR_ALERT_FATAL;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
using System;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
internal static partial class Ssl
{
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SetProtocolOptions")]
internal static extern void SetProtocolOptions(IntPtr ctx, SslProtocols protocols);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SetProtocolOptions")]
internal static extern void SetProtocolOptions(SafeSslContextHandle ctx, SslProtocols protocols);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Security;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -44,6 +45,9 @@ internal static partial class Ssl
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetAcceptState")]
internal static extern void SslSetAcceptState(SafeSslHandle ssl);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetAlpnProtos")]
internal static extern int SslSetAlpnProtos(SafeSslHandle ssl, IntPtr protos, int len);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslGetVersion")]
internal static extern IntPtr SslGetVersion(SafeSslHandle ssl);

Expand Down Expand Up @@ -133,6 +137,28 @@ internal static partial class Ssl
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetOpenSslCipherSuiteName")]
private static extern IntPtr GetOpenSslCipherSuiteName(SafeSslHandle ssl, int cipherSuite, out int isTls12OrLower);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SetCiphers")]
internal static extern unsafe bool SslSetCiphers(SafeSslHandle ssl, byte* cipherList, byte* cipherSuites);


[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetVerifyPeer")]
internal static extern void SslSetVerifyPeer(SafeSslHandle ssl);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslGetData")]
internal static extern IntPtr SslGetData(IntPtr ssl);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetData")]
internal static extern int SslSetData(SafeSslHandle ssl, IntPtr data);

internal static unsafe int SslSetAlpnProtos(SafeSslHandle ssl, List<SslApplicationProtocol> protocols)
{
byte[] buffer = ConvertAlpnProtocolListToByteArray(protocols);
fixed (byte* b = buffer)
{
return SslSetAlpnProtos(ssl, (IntPtr)b, buffer.Length);
}
}

internal static string? GetOpenSslCipherSuiteName(SafeSslHandle ssl, TlsCipherSuite cipherSuite, out bool isTls12OrLower)
{
string? ret = Marshal.PtrToStringAnsi(GetOpenSslCipherSuiteName(ssl, (int)cipherSuite, out int isTls12OrLowerInt));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ internal static partial class Ssl
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetVerify")]
internal static extern unsafe void SslCtxSetVerify(SafeSslContextHandle ctx, delegate* unmanaged<int, IntPtr, int> callback);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SetCiphers")]
internal static extern unsafe bool SetCiphers(SafeSslContextHandle ctx, byte* cipherList, byte* cipherSuites);
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetCiphers")]
internal static extern unsafe bool SslCtxSetCiphers(SafeSslContextHandle ctx, byte* cipherList, byte* cipherSuites);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SetEncryptionPolicy")]
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetEncryptionPolicy")]
internal static extern bool SetEncryptionPolicy(SafeSslContextHandle ctx, EncryptionPolicy policy);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,7 @@ public SafeDeleteSslContext(SafeFreeSslCredentials credential, SslAuthentication

try
{
_sslContext = Interop.OpenSsl.AllocateSslContext(
credential.Protocols,
credential.CertHandle,
credential.CertKeyHandle,
credential.Policy,
sslAuthenticationOptions);
_sslContext = Interop.OpenSsl.AllocateSslHandle(credential, sslAuthenticationOptions);
}
catch (Exception ex)
{
Expand Down
Loading