Skip to content

Commit 2d76178

Browse files
authored
support server ALPN on macOS (#79434)
* support server ALPN on macOS * reset status
1 parent ea44671 commit 2d76178

File tree

17 files changed

+330
-37
lines changed

17 files changed

+330
-37
lines changed

src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ internal enum PAL_TlsHandshakeState
4747
ServerAuthCompleted,
4848
ClientAuthCompleted,
4949
ClientCertRequested,
50+
ClientHelloReceived,
5051
}
5152

5253
internal enum PAL_TlsIo
@@ -100,6 +101,12 @@ private static partial int AppleCryptoNative_SslSetBreakOnClientAuth(
100101
int setBreak,
101102
out int pOSStatus);
102103

104+
[LibraryImport(Interop.Libraries.AppleCryptoNative)]
105+
private static partial int AppleCryptoNative_SslSetBreakOnClientHello(
106+
SafeSslHandle sslHandle,
107+
int setBreak,
108+
out int pOSStatus);
109+
103110
[LibraryImport(Interop.Libraries.AppleCryptoNative)]
104111
private static partial int AppleCryptoNative_SslSetBreakOnCertRequested(
105112
SafeSslHandle sslHandle,
@@ -121,6 +128,9 @@ private static partial int AppleCryptoNative_SslSetTargetName(
121128
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SSLSetALPNProtocols")]
122129
internal static partial int SSLSetALPNProtocols(SafeSslHandle ctx, SafeCreateHandle cfProtocolsRefs, out int osStatus);
123130

131+
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SSLSetALPNProtocol")]
132+
internal static unsafe partial int SSLSetALPNProtocol(SafeSslHandle ctx, void* protocol, int length, out int osStatus);
133+
124134
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SslGetAlpnSelected")]
125135
internal static partial int SslGetAlpnSelected(SafeSslHandle ssl, out SafeCFDataHandle protocol);
126136

@@ -289,6 +299,25 @@ internal static void SslBreakOnClientAuth(SafeSslHandle sslHandle, bool setBreak
289299
throw new SslException();
290300
}
291301

302+
internal static void SslBreakOnClientHello(SafeSslHandle sslHandle, bool setBreak)
303+
{
304+
int osStatus;
305+
int result = AppleCryptoNative_SslSetBreakOnClientHello(sslHandle, setBreak ? 1 : 0, out osStatus);
306+
307+
if (result == 1)
308+
{
309+
return;
310+
}
311+
312+
if (result == 0)
313+
{
314+
throw CreateExceptionForOSStatus(osStatus);
315+
}
316+
317+
Debug.Fail($"AppleCryptoNative_SslSetBreakOnClientHello returned {result}");
318+
throw new SslException();
319+
}
320+
292321
internal static void SslBreakOnCertRequested(SafeSslHandle sslHandle, bool setBreak)
293322
{
294323
int osStatus;
@@ -398,6 +427,22 @@ internal static unsafe void SslCtxSetAlpnProtos(SafeSslHandle ctx, List<SslAppli
398427
}
399428
}
400429

430+
internal static unsafe bool SslCtxSetAlpnProtocol(SafeSslHandle ctx, SslApplicationProtocol protocol)
431+
{
432+
int osStatus;
433+
434+
fixed (void* ptr = &MemoryMarshal.GetReference(protocol.Protocol.Span))
435+
{
436+
int result = SSLSetALPNProtocol(ctx, ptr, protocol.Protocol.Length, out osStatus);
437+
if (result != 1)
438+
{
439+
throw CreateExceptionForOSStatus(osStatus);
440+
}
441+
}
442+
443+
return osStatus == 0;
444+
}
445+
401446
internal static byte[]? SslGetAlpnSelected(SafeSslHandle ssl)
402447
{
403448
SafeCFDataHandle protocol;

src/libraries/Common/src/System/Net/SecurityStatusPal.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ internal enum SecurityStatusPalErrorCode
3333
CredentialsNeeded,
3434
Renegotiate,
3535
TryAgain,
36+
HandshakeStarted,
3637

3738
// Errors
3839
OutOfMemory,

src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,6 @@ public static bool IsMetadataTokenSupported
256256

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

283+
if (IsOSX)
284+
{
285+
return true;
286+
}
287+
284288
return false;
285289
}
286290

src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ internal sealed class SafeDeleteSslContext : SafeDeleteContext
2424
private ArrayBuffer _outputBuffer = new ArrayBuffer(InitialBufferSize);
2525

2626
public SafeSslHandle SslContext => _sslContext;
27+
public SslApplicationProtocol SelectedApplicationProtocol;
28+
public bool IsServer;
2729

2830
public SafeDeleteSslContext(SslAuthenticationOptions sslAuthenticationOptions)
2931
: base(IntPtr.Zero)
@@ -74,11 +76,16 @@ public SafeDeleteSslContext(SslAuthenticationOptions sslAuthenticationOptions)
7476

7577
if (sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0)
7678
{
77-
// On OSX coretls supports only client side. For server, we will silently ignore the option.
78-
if (!sslAuthenticationOptions.IsServer)
79+
if (sslAuthenticationOptions.IsClient)
7980
{
81+
// On macOS coreTls supports only client side.
8082
Interop.AppleCrypto.SslCtxSetAlpnProtos(_sslContext, sslAuthenticationOptions.ApplicationProtocols);
8183
}
84+
else
85+
{
86+
// For Server, we do the selection in SslStream and we set it later
87+
Interop.AppleCrypto.SslBreakOnClientHello(_sslContext, true);
88+
}
8289
}
8390
}
8491
catch (Exception ex)
@@ -102,6 +109,8 @@ public SafeDeleteSslContext(SslAuthenticationOptions sslAuthenticationOptions)
102109

103110
if (sslAuthenticationOptions.IsServer)
104111
{
112+
IsServer = true;
113+
105114
if (sslAuthenticationOptions.RemoteCertRequired)
106115
{
107116
Interop.AppleCrypto.SslSetAcceptClientCert(_sslContext);

src/libraries/System.Net.Security/src/System/Net/Security/SslConnectionInfo.OSX.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ namespace System.Net.Security
99
{
1010
internal partial struct SslConnectionInfo
1111
{
12-
public void UpdateSslConnectionInfo(SafeSslHandle sslContext)
12+
public void UpdateSslConnectionInfo(SafeDeleteSslContext context)
1313
{
14+
SafeSslHandle sslContext = context.SslContext;
1415
SslProtocols protocol;
1516
TlsCipherSuite cipherSuite;
1617

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

2728
Protocol = (int)protocol;
2829
TlsCipherSuite = cipherSuite;
29-
ApplicationProtocol = Interop.AppleCrypto.SslGetAlpnSelected(sslContext);
30+
if (context.IsServer)
31+
{
32+
if (context.SelectedApplicationProtocol.Protocol.Length > 0)
33+
{
34+
if (context.SelectedApplicationProtocol.Equals(SslApplicationProtocol.Http11.Protocol))
35+
{
36+
ApplicationProtocol = s_http1;
37+
}
38+
else if (context.SelectedApplicationProtocol.Equals(SslApplicationProtocol.Http2.Protocol))
39+
{
40+
ApplicationProtocol = s_http2;
41+
}
42+
else if (context.SelectedApplicationProtocol.Equals(SslApplicationProtocol.Http3.Protocol))
43+
{
44+
ApplicationProtocol = s_http3;
45+
}
46+
else
47+
{
48+
ApplicationProtocol = context.SelectedApplicationProtocol.Protocol.ToArray();
49+
}
50+
}
51+
}
52+
else
53+
{
54+
ApplicationProtocol = Interop.AppleCrypto.SslGetAlpnSelected(sslContext);
55+
}
3056

3157
MapCipherSuite(cipherSuite);
3258
}

src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,12 @@ private async ValueTask<ProtocolToken> ReceiveBlobAsync<TIOAdapter>(Cancellation
381381
TlsFrameHelper.ProcessingOptions options = NetEventSource.Log.IsEnabled() ?
382382
TlsFrameHelper.ProcessingOptions.All :
383383
TlsFrameHelper.ProcessingOptions.ServerName;
384+
if (OperatingSystem.IsMacOS() && _sslAuthenticationOptions.IsServer)
385+
{
386+
// macOS cannot process ALPN on server at the momennt.
387+
// We fallback to our own process similar to SNI bellow.
388+
options |= TlsFrameHelper.ProcessingOptions.RawApplicationProtocol;
389+
}
384390

385391
// Process SNI from Client Hello message
386392
if (!TlsFrameHelper.TryGetFrameInfo(_buffer.EncryptedReadOnlySpan, ref _lastFrame, options))

src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,24 @@ private SecurityStatusPal GenerateToken(ReadOnlySpan<byte> inputBuffer, ref byte
816816
inputBuffer,
817817
ref result,
818818
_sslAuthenticationOptions);
819+
if (status.ErrorCode == SecurityStatusPalErrorCode.HandshakeStarted)
820+
{
821+
status = SslStreamPal.SelectApplicationProtocol(
822+
_credentialsHandle!,
823+
_securityContext!,
824+
_sslAuthenticationOptions,
825+
_lastFrame.RawApplicationProtocols);
826+
827+
if (status.ErrorCode == SecurityStatusPalErrorCode.OK)
828+
{
829+
status = SslStreamPal.AcceptSecurityContext(
830+
ref _credentialsHandle!,
831+
ref _securityContext,
832+
ReadOnlySpan<byte>.Empty,
833+
ref result,
834+
_sslAuthenticationOptions);
835+
}
836+
}
819837
}
820838
else
821839
{

src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ public static void VerifyPackageInfo()
2626
{
2727
}
2828

29+
public static SecurityStatusPal SelectApplicationProtocol(
30+
SafeFreeCredentials? credentialsHandle,
31+
SafeDeleteSslContext? context,
32+
SslAuthenticationOptions sslAuthenticationOptions,
33+
ReadOnlySpan<byte> clientProtocols)
34+
{
35+
throw new PlatformNotSupportedException(nameof(SelectApplicationProtocol));
36+
}
37+
2938
public static SecurityStatusPal AcceptSecurityContext(
3039
ref SafeFreeCredentials credential,
3140
ref SafeDeleteSslContext? context,

src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Buffers;
5+
using System.Collections.Generic;
56
using System.ComponentModel;
67
using System.Diagnostics;
8+
using System.Net.Security;
79
using System.Security.Authentication;
810
using System.Security.Authentication.ExtendedProtection;
911
using System.Security.Cryptography.X509Certificates;
@@ -31,6 +33,50 @@ public static void VerifyPackageInfo()
3133
{
3234
}
3335

36+
public static SecurityStatusPal SelectApplicationProtocol(
37+
SafeFreeCredentials? _,
38+
SafeDeleteSslContext context,
39+
SslAuthenticationOptions sslAuthenticationOptions,
40+
ReadOnlySpan<byte> clientProtocols)
41+
{
42+
// Client did not provide ALPN or APLN is not needed
43+
if (clientProtocols.Length == 0 ||
44+
sslAuthenticationOptions.ApplicationProtocols == null || sslAuthenticationOptions.ApplicationProtocols.Count == 0)
45+
{
46+
return new SecurityStatusPal(SecurityStatusPalErrorCode.OK);
47+
}
48+
49+
// We do server side ALPN e.g. walk the intersect in server order
50+
foreach (SslApplicationProtocol applicationProtcol in sslAuthenticationOptions.ApplicationProtocols)
51+
{
52+
ReadOnlySpan<byte> protocols = clientProtocols;
53+
54+
while (protocols.Length > 0)
55+
{
56+
byte length = protocols[0];
57+
if (protocols.Length < length + 1)
58+
{
59+
break;
60+
}
61+
ReadOnlySpan<byte> protocol = protocols.Slice(1, length);
62+
if (protocol.SequenceCompareTo<byte>(applicationProtcol.Protocol.Span) == 0)
63+
{
64+
if (Interop.AppleCrypto.SslCtxSetAlpnProtocol(context.SslContext, applicationProtcol))
65+
{
66+
context.SelectedApplicationProtocol = applicationProtcol;
67+
}
68+
69+
// We ignore failure and we will move on with ALPN
70+
return new SecurityStatusPal(SecurityStatusPalErrorCode.OK);
71+
}
72+
73+
protocols = protocols.Slice(protocol.Length + 1);
74+
}
75+
}
76+
77+
return new SecurityStatusPal(SecurityStatusPalErrorCode.ApplicationProtocolMismatch);
78+
}
79+
3480
#pragma warning disable IDE0060
3581
public static SecurityStatusPal AcceptSecurityContext(
3682
ref SafeFreeCredentials credential,
@@ -216,7 +262,7 @@ public static void QueryContextConnectionInfo(
216262
SafeDeleteSslContext securityContext,
217263
ref SslConnectionInfo connectionInfo)
218264
{
219-
connectionInfo.UpdateSslConnectionInfo(securityContext.SslContext);
265+
connectionInfo.UpdateSslConnectionInfo(securityContext);
220266
}
221267

222268
private static SecurityStatusPal HandshakeInternal(
@@ -243,6 +289,7 @@ private static SecurityStatusPal HandshakeInternal(
243289

244290
SafeSslHandle sslHandle = sslContext!.SslContext;
245291
SecurityStatusPal status = PerformHandshake(sslHandle);
292+
246293
if (status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded && clientCertificateSelectionCallback != null)
247294
{
248295
X509Certificate2? clientCertificate = clientCertificateSelectionCallback(out bool _);
@@ -288,6 +335,8 @@ private static SecurityStatusPal PerformHandshake(SafeSslHandle sslHandle)
288335
break;
289336
case PAL_TlsHandshakeState.ClientCertRequested:
290337
return new SecurityStatusPal(SecurityStatusPalErrorCode.CredentialsNeeded);
338+
case PAL_TlsHandshakeState.ClientHelloReceived:
339+
return new SecurityStatusPal(SecurityStatusPalErrorCode.HandshakeStarted);
291340
default:
292341
return new SecurityStatusPal(
293342
SecurityStatusPalErrorCode.InternalError,

src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ public static void VerifyPackageInfo()
2424
{
2525
}
2626

27+
public static SecurityStatusPal SelectApplicationProtocol(
28+
SafeFreeCredentials? credentialsHandle,
29+
SafeDeleteSslContext? context,
30+
SslAuthenticationOptions sslAuthenticationOptions,
31+
ReadOnlySpan<byte> clientProtocols)
32+
{
33+
throw new PlatformNotSupportedException(nameof(SelectApplicationProtocol));
34+
}
35+
2736
#pragma warning disable IDE0060
2837
public static SecurityStatusPal AcceptSecurityContext(
2938
ref SafeFreeCredentials? credential,

0 commit comments

Comments
 (0)