Skip to content

Commit

Permalink
support server ALPN on macOS (#79434)
Browse files Browse the repository at this point in the history
* support server ALPN on macOS

* reset status
  • Loading branch information
wfurt authored Dec 13, 2022
1 parent ea44671 commit 2d76178
Show file tree
Hide file tree
Showing 17 changed files with 330 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ internal enum PAL_TlsHandshakeState
ServerAuthCompleted,
ClientAuthCompleted,
ClientCertRequested,
ClientHelloReceived,
}

internal enum PAL_TlsIo
Expand Down Expand Up @@ -100,6 +101,12 @@ private static partial int AppleCryptoNative_SslSetBreakOnClientAuth(
int setBreak,
out int pOSStatus);

[LibraryImport(Interop.Libraries.AppleCryptoNative)]
private static partial int AppleCryptoNative_SslSetBreakOnClientHello(
SafeSslHandle sslHandle,
int setBreak,
out int pOSStatus);

[LibraryImport(Interop.Libraries.AppleCryptoNative)]
private static partial int AppleCryptoNative_SslSetBreakOnCertRequested(
SafeSslHandle sslHandle,
Expand All @@ -121,6 +128,9 @@ private static partial int AppleCryptoNative_SslSetTargetName(
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SSLSetALPNProtocols")]
internal static partial int SSLSetALPNProtocols(SafeSslHandle ctx, SafeCreateHandle cfProtocolsRefs, out int osStatus);

[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SSLSetALPNProtocol")]
internal static unsafe partial int SSLSetALPNProtocol(SafeSslHandle ctx, void* protocol, int length, out int osStatus);

[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SslGetAlpnSelected")]
internal static partial int SslGetAlpnSelected(SafeSslHandle ssl, out SafeCFDataHandle protocol);

Expand Down Expand Up @@ -289,6 +299,25 @@ internal static void SslBreakOnClientAuth(SafeSslHandle sslHandle, bool setBreak
throw new SslException();
}

internal static void SslBreakOnClientHello(SafeSslHandle sslHandle, bool setBreak)
{
int osStatus;
int result = AppleCryptoNative_SslSetBreakOnClientHello(sslHandle, setBreak ? 1 : 0, out osStatus);

if (result == 1)
{
return;
}

if (result == 0)
{
throw CreateExceptionForOSStatus(osStatus);
}

Debug.Fail($"AppleCryptoNative_SslSetBreakOnClientHello returned {result}");
throw new SslException();
}

internal static void SslBreakOnCertRequested(SafeSslHandle sslHandle, bool setBreak)
{
int osStatus;
Expand Down Expand Up @@ -398,6 +427,22 @@ internal static unsafe void SslCtxSetAlpnProtos(SafeSslHandle ctx, List<SslAppli
}
}

internal static unsafe bool SslCtxSetAlpnProtocol(SafeSslHandle ctx, SslApplicationProtocol protocol)
{
int osStatus;

fixed (void* ptr = &MemoryMarshal.GetReference(protocol.Protocol.Span))
{
int result = SSLSetALPNProtocol(ctx, ptr, protocol.Protocol.Length, out osStatus);
if (result != 1)
{
throw CreateExceptionForOSStatus(osStatus);
}
}

return osStatus == 0;
}

internal static byte[]? SslGetAlpnSelected(SafeSslHandle ssl)
{
SafeCFDataHandle protocol;
Expand Down
1 change: 1 addition & 0 deletions src/libraries/Common/src/System/Net/SecurityStatusPal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ internal enum SecurityStatusPalErrorCode
CredentialsNeeded,
Renegotiate,
TryAgain,
HandshakeStarted,

// Errors
OutOfMemory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,6 @@ public static bool IsMetadataTokenSupported

// Windows - Schannel supports alpn from win8.1/2012 R2 and higher.
// Linux - OpenSsl supports alpn from openssl 1.0.2 and higher.
// OSX - SecureTransport doesn't expose alpn APIs. TODO https://github.com/dotnet/runtime/issues/27727
// Android - Platform supports alpn from API level 29 and higher
private static readonly Lazy<bool> s_supportsAlpn = new Lazy<bool>(GetAlpnSupport);
private static bool GetAlpnSupport()
Expand All @@ -281,6 +280,11 @@ private static bool GetAlpnSupport()
return Interop.AndroidCrypto.SSLSupportsApplicationProtocolsConfiguration();
}

if (IsOSX)
{
return true;
}

return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ internal sealed class SafeDeleteSslContext : SafeDeleteContext
private ArrayBuffer _outputBuffer = new ArrayBuffer(InitialBufferSize);

public SafeSslHandle SslContext => _sslContext;
public SslApplicationProtocol SelectedApplicationProtocol;
public bool IsServer;

public SafeDeleteSslContext(SslAuthenticationOptions sslAuthenticationOptions)
: base(IntPtr.Zero)
Expand Down Expand Up @@ -74,11 +76,16 @@ public SafeDeleteSslContext(SslAuthenticationOptions sslAuthenticationOptions)

if (sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0)
{
// On OSX coretls supports only client side. For server, we will silently ignore the option.
if (!sslAuthenticationOptions.IsServer)
if (sslAuthenticationOptions.IsClient)
{
// On macOS coreTls supports only client side.
Interop.AppleCrypto.SslCtxSetAlpnProtos(_sslContext, sslAuthenticationOptions.ApplicationProtocols);
}
else
{
// For Server, we do the selection in SslStream and we set it later
Interop.AppleCrypto.SslBreakOnClientHello(_sslContext, true);
}
}
}
catch (Exception ex)
Expand All @@ -102,6 +109,8 @@ public SafeDeleteSslContext(SslAuthenticationOptions sslAuthenticationOptions)

if (sslAuthenticationOptions.IsServer)
{
IsServer = true;

if (sslAuthenticationOptions.RemoteCertRequired)
{
Interop.AppleCrypto.SslSetAcceptClientCert(_sslContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ namespace System.Net.Security
{
internal partial struct SslConnectionInfo
{
public void UpdateSslConnectionInfo(SafeSslHandle sslContext)
public void UpdateSslConnectionInfo(SafeDeleteSslContext context)
{
SafeSslHandle sslContext = context.SslContext;
SslProtocols protocol;
TlsCipherSuite cipherSuite;

Expand All @@ -26,7 +27,32 @@ public void UpdateSslConnectionInfo(SafeSslHandle sslContext)

Protocol = (int)protocol;
TlsCipherSuite = cipherSuite;
ApplicationProtocol = Interop.AppleCrypto.SslGetAlpnSelected(sslContext);
if (context.IsServer)
{
if (context.SelectedApplicationProtocol.Protocol.Length > 0)
{
if (context.SelectedApplicationProtocol.Equals(SslApplicationProtocol.Http11.Protocol))
{
ApplicationProtocol = s_http1;
}
else if (context.SelectedApplicationProtocol.Equals(SslApplicationProtocol.Http2.Protocol))
{
ApplicationProtocol = s_http2;
}
else if (context.SelectedApplicationProtocol.Equals(SslApplicationProtocol.Http3.Protocol))
{
ApplicationProtocol = s_http3;
}
else
{
ApplicationProtocol = context.SelectedApplicationProtocol.Protocol.ToArray();
}
}
}
else
{
ApplicationProtocol = Interop.AppleCrypto.SslGetAlpnSelected(sslContext);
}

MapCipherSuite(cipherSuite);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,12 @@ private async ValueTask<ProtocolToken> ReceiveBlobAsync<TIOAdapter>(Cancellation
TlsFrameHelper.ProcessingOptions options = NetEventSource.Log.IsEnabled() ?
TlsFrameHelper.ProcessingOptions.All :
TlsFrameHelper.ProcessingOptions.ServerName;
if (OperatingSystem.IsMacOS() && _sslAuthenticationOptions.IsServer)
{
// macOS cannot process ALPN on server at the momennt.
// We fallback to our own process similar to SNI bellow.
options |= TlsFrameHelper.ProcessingOptions.RawApplicationProtocol;
}

// Process SNI from Client Hello message
if (!TlsFrameHelper.TryGetFrameInfo(_buffer.EncryptedReadOnlySpan, ref _lastFrame, options))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,24 @@ private SecurityStatusPal GenerateToken(ReadOnlySpan<byte> inputBuffer, ref byte
inputBuffer,
ref result,
_sslAuthenticationOptions);
if (status.ErrorCode == SecurityStatusPalErrorCode.HandshakeStarted)
{
status = SslStreamPal.SelectApplicationProtocol(
_credentialsHandle!,
_securityContext!,
_sslAuthenticationOptions,
_lastFrame.RawApplicationProtocols);

if (status.ErrorCode == SecurityStatusPalErrorCode.OK)
{
status = SslStreamPal.AcceptSecurityContext(
ref _credentialsHandle!,
ref _securityContext,
ReadOnlySpan<byte>.Empty,
ref result,
_sslAuthenticationOptions);
}
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ public static void VerifyPackageInfo()
{
}

public static SecurityStatusPal SelectApplicationProtocol(
SafeFreeCredentials? credentialsHandle,
SafeDeleteSslContext? context,
SslAuthenticationOptions sslAuthenticationOptions,
ReadOnlySpan<byte> clientProtocols)
{
throw new PlatformNotSupportedException(nameof(SelectApplicationProtocol));
}

public static SecurityStatusPal AcceptSecurityContext(
ref SafeFreeCredentials credential,
ref SafeDeleteSslContext? context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Cryptography.X509Certificates;
Expand Down Expand Up @@ -31,6 +33,50 @@ public static void VerifyPackageInfo()
{
}

public static SecurityStatusPal SelectApplicationProtocol(
SafeFreeCredentials? _,
SafeDeleteSslContext context,
SslAuthenticationOptions sslAuthenticationOptions,
ReadOnlySpan<byte> clientProtocols)
{
// Client did not provide ALPN or APLN is not needed
if (clientProtocols.Length == 0 ||
sslAuthenticationOptions.ApplicationProtocols == null || sslAuthenticationOptions.ApplicationProtocols.Count == 0)
{
return new SecurityStatusPal(SecurityStatusPalErrorCode.OK);
}

// We do server side ALPN e.g. walk the intersect in server order
foreach (SslApplicationProtocol applicationProtcol in sslAuthenticationOptions.ApplicationProtocols)
{
ReadOnlySpan<byte> protocols = clientProtocols;

while (protocols.Length > 0)
{
byte length = protocols[0];
if (protocols.Length < length + 1)
{
break;
}
ReadOnlySpan<byte> protocol = protocols.Slice(1, length);
if (protocol.SequenceCompareTo<byte>(applicationProtcol.Protocol.Span) == 0)
{
if (Interop.AppleCrypto.SslCtxSetAlpnProtocol(context.SslContext, applicationProtcol))
{
context.SelectedApplicationProtocol = applicationProtcol;
}

// We ignore failure and we will move on with ALPN
return new SecurityStatusPal(SecurityStatusPalErrorCode.OK);
}

protocols = protocols.Slice(protocol.Length + 1);
}
}

return new SecurityStatusPal(SecurityStatusPalErrorCode.ApplicationProtocolMismatch);
}

#pragma warning disable IDE0060
public static SecurityStatusPal AcceptSecurityContext(
ref SafeFreeCredentials credential,
Expand Down Expand Up @@ -216,7 +262,7 @@ public static void QueryContextConnectionInfo(
SafeDeleteSslContext securityContext,
ref SslConnectionInfo connectionInfo)
{
connectionInfo.UpdateSslConnectionInfo(securityContext.SslContext);
connectionInfo.UpdateSslConnectionInfo(securityContext);
}

private static SecurityStatusPal HandshakeInternal(
Expand All @@ -243,6 +289,7 @@ private static SecurityStatusPal HandshakeInternal(

SafeSslHandle sslHandle = sslContext!.SslContext;
SecurityStatusPal status = PerformHandshake(sslHandle);

if (status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded && clientCertificateSelectionCallback != null)
{
X509Certificate2? clientCertificate = clientCertificateSelectionCallback(out bool _);
Expand Down Expand Up @@ -288,6 +335,8 @@ private static SecurityStatusPal PerformHandshake(SafeSslHandle sslHandle)
break;
case PAL_TlsHandshakeState.ClientCertRequested:
return new SecurityStatusPal(SecurityStatusPalErrorCode.CredentialsNeeded);
case PAL_TlsHandshakeState.ClientHelloReceived:
return new SecurityStatusPal(SecurityStatusPalErrorCode.HandshakeStarted);
default:
return new SecurityStatusPal(
SecurityStatusPalErrorCode.InternalError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ public static void VerifyPackageInfo()
{
}

public static SecurityStatusPal SelectApplicationProtocol(
SafeFreeCredentials? credentialsHandle,
SafeDeleteSslContext? context,
SslAuthenticationOptions sslAuthenticationOptions,
ReadOnlySpan<byte> clientProtocols)
{
throw new PlatformNotSupportedException(nameof(SelectApplicationProtocol));
}

#pragma warning disable IDE0060
public static SecurityStatusPal AcceptSecurityContext(
ref SafeFreeCredentials? credential,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ private static unsafe void SetAlpn(ref InputSecurityBuffers inputBuffers, List<S
}
}

public static SecurityStatusPal SelectApplicationProtocol(
SafeFreeCredentials? credentialsHandle,
SafeDeleteSslContext? context,
SslAuthenticationOptions sslAuthenticationOptions,
ReadOnlySpan<byte> clientProtocols)
{
throw new PlatformNotSupportedException(nameof(SelectApplicationProtocol));
}

public static unsafe SecurityStatusPal AcceptSecurityContext(
ref SafeFreeCredentials? credentialsHandle,
ref SafeDeleteSslContext? context,
Expand Down
Loading

0 comments on commit 2d76178

Please sign in to comment.