Skip to content

Commit dd263ab

Browse files
rzikmliveansCopilotstephentoubam11
authored
Client-side TLS 1.3 support on OSX (#117428)
* Native Interop Layer * Native Layer Compilation fix for Mono + NativeAOT + templates * First shape of new native + interop * Newlines at the end of files * Default constructor ownsHandle to true * Delete couple of unsafe keyword in Interop * Update src/native/libs/System.Net.Security.Native.Apple/pal_networkframework.m Co-authored-by: Copilot <[email protected]> * Fix PlatformManifestFileEntry * Review feedback * Apply suggestions from code review Co-authored-by: Stephen Toub <[email protected]> * Update src/libraries/Common/src/Interop/OSX/Interop.Network.Tls.cs Co-authored-by: Radek Zikmund <[email protected]> * Review feedbacks * Further review feedback * Add new library name to nativeaot build target file * Merge System.Net.Security.Native.Apple with System.Security.Cryptography.Native.Apple * fixup! Merge System.Net.Security.Native.Apple with System.Security.Cryptography.Native.Apple * Shared OSStatus * Correctly release some handles * Remove printf * Add comments * Fix build * Copy of initial changes * Fix build * WIP * WIP * more WIP * Minimal example is working * Fix concurrent read/write calls * ALPN fix * Certificate validation * Report remote alerts * CipherSuitesPolicy support * Fix IDNA * Zero-bytes read support * fixup! ALPN fix * Attach correct cancellation token to exceptions * Fix framer lifetime * fixup! CipherSuitesPolicy support * Cleanup some unwanted changes * Some more cleanup * Fix ALPN reading * ClientCertificates + CertificateContext + CertSelectionDelegate implementation * Correctly pass remote certificate + acceptableIssuers to selection callback * Disable Ciphersuite tests for NW * Fix formatting * Fix some test scenarios * Delete unused ResettableTaskSource * Fix build * Unify certificate validation code * TARGET_OSX to TARGET_APPLE * Small changes * Fix build of other platforms * Disable known edge-case for now * Some test fixes * Disable EventSource order test for NW * Add TCS for completion on transportStream Write and propagate exceptions * Propagate exception for handshake + write tcs from transport read task * Missing write part of propagation exception for transport read task * App read optimization * fixup! App read optimization * Fix hanging pending read after read cancellation * Unify local cert selection * Improve thisHandle lifetime management * Introduce specific exception for NetworkFramework + properly propagate error messages and error domain * Refactor NetworkFramework error handling to use enum for error domains * Refactor error extraction in NetworkFramework to return CFStringRef for better memory management * Enhance cancellation support in SafeDeleteNwContext by throwing on cancellation requests and linking tokens for graceful shutdowns * Typo fix * Switch to Network.framework tests on CI * Fix memory leaks, introduce CancellationAction for ResettableValueTaskSource objects to avoid deadlock on disposal * Reverting back running nw tests on ci, as some apis requires at least 12.3 version * A bit cleanup * Use more appropriate names in nw shim functions * Centralized gchandle management in native code * Fix correct cancellation token when throwing * Fix hang, remove try-catches in completion callbacks * Revert unwanted changes * Fix comment * Logging improvements * Apply suggestion from @liveans Co-authored-by: Ahmet Ibrahim Aksoy <[email protected]> * Update src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteNwContext.cs Co-authored-by: Ahmet Ibrahim Aksoy <[email protected]> * Code review feecback * Remove duplicate void* state argument in native functions * Update src/native/libs/System.Security.Cryptography.Native.Apple/pal_networkframework.m Co-authored-by: Adeel Mujahid <[email protected]> --------- Co-authored-by: Ahmet İbrahim Aksoy <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Stephen Toub <[email protected]> Co-authored-by: Adeel Mujahid <[email protected]>
1 parent 3fb34b2 commit dd263ab

35 files changed

+2082
-62
lines changed

src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ The .NET Foundation licenses this file to you under the MIT license.
201201
<NativeFramework Include="CoreFoundation" />
202202
<NativeFramework Include="CryptoKit" />
203203
<NativeFramework Include="Foundation" />
204+
<NativeFramework Include="Network" />
204205
<NativeFramework Include="Security" />
205206
<!-- The library builds don't reference the GSS API on tvOS builds. -->
206207
<NativeFramework Condition="!$(_targetOS.StartsWith('tvos'))" Include="GSS" />

src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ internal static partial class Libraries
1414
internal const string OpenLdap = "libldap.dylib";
1515
internal const string SystemConfigurationLibrary = "/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration";
1616
internal const string AppleCryptoNative = "libSystem.Security.Cryptography.Native.Apple";
17+
internal const string NetworkFramework = "/System/Library/Frameworks/Network.framework/Network";
1718
internal const string MsQuic = "libmsquic.dylib";
1819
}
1920
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Collections.Generic;
7+
using System.Diagnostics;
8+
using System.Net;
9+
using System.Net.Security;
10+
using System.Runtime.InteropServices;
11+
using System.Security.Authentication;
12+
using Microsoft.Win32.SafeHandles;
13+
14+
internal static partial class Interop
15+
{
16+
// TLS 1.3 specific Network Framework implementation for macOS
17+
internal static partial class NetworkFramework
18+
{
19+
internal static partial class Tls
20+
{
21+
// Initialize internal shim for NetworkFramework integration
22+
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_Init")]
23+
[return: MarshalAs(UnmanagedType.I4)]
24+
internal static unsafe partial bool Init(
25+
delegate* unmanaged<IntPtr, StatusUpdates, IntPtr, IntPtr, NetworkFrameworkError*, void> statusCallback,
26+
delegate* unmanaged<IntPtr, byte*, ulong, void> writeCallback,
27+
delegate* unmanaged<IntPtr, IntPtr, IntPtr> challengeCallback);
28+
29+
// Create a new connection context
30+
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwConnectionCreate", StringMarshalling = StringMarshalling.Utf8)]
31+
internal static unsafe partial SafeNwHandle NwConnectionCreate([MarshalAs(UnmanagedType.I4)] bool isServer, IntPtr context, string targetName, byte* alpnBuffer, int alpnLength, SslProtocols minTlsProtocol, SslProtocols maxTlsProtocol, uint* cipherSuites, int cipherSuitesLength);
32+
33+
// Start the TLS handshake, notifications are received via the status callback (potentially from a different thread).
34+
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwConnectionStart")]
35+
internal static partial int NwConnectionStart(SafeNwHandle connection, IntPtr context);
36+
37+
// takes encrypted input from underlying stream and feed it to the connection.
38+
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwFramerDeliverInput")]
39+
internal static unsafe partial int NwFramerDeliverInput(SafeNwHandle framer, IntPtr context, byte* buffer, int bufferLength, delegate* unmanaged<IntPtr, NetworkFrameworkError*, void> completionCallback);
40+
41+
// sends plaintext data to the connection.
42+
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwConnectionSend")]
43+
internal static unsafe partial void NwConnectionSend(SafeNwHandle connection, IntPtr context, void* buffer, int bufferLength, delegate* unmanaged<IntPtr, NetworkFrameworkError*, void> completionCallback);
44+
45+
// read plaintext data from the connection.
46+
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwConnectionReceive")]
47+
internal static unsafe partial void NwConnectionReceive(SafeNwHandle connection, IntPtr context, int length, delegate* unmanaged<IntPtr, NetworkFrameworkError*, byte*, int, void> readCompletionCallback);
48+
49+
// starts connection cleanup
50+
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwConnectionCancel")]
51+
internal static partial void NwConnectionCancel(SafeNwHandle connection);
52+
53+
// gets TLS connection information
54+
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_GetConnectionInfo")]
55+
internal static unsafe partial int GetConnectionInfo(SafeNwHandle connection, IntPtr context, out SslProtocols pProtocol, out TlsCipherSuite pCipherSuiteOut, byte* negotiatedAlpn, ref int negotiatedAlpnLength);
56+
}
57+
58+
// Status enumeration for Network Framework TLS operations
59+
internal enum StatusUpdates
60+
{
61+
UnknownError = 0,
62+
FramerStart = 1,
63+
HandshakeFinished = 3,
64+
ConnectionFailed = 4,
65+
ConnectionCancelled = 103,
66+
CertificateAvailable = 104,
67+
DebugLog = 200,
68+
}
69+
}
70+
71+
// Safe handle classes for Network Framework TLS resources
72+
internal sealed class SafeNwHandle : SafeHandleZeroOrMinusOneIsInvalid
73+
{
74+
public SafeNwHandle() : base(ownsHandle: true) { }
75+
76+
public SafeNwHandle(IntPtr handle, bool ownsHandle) : base(ownsHandle)
77+
{
78+
SetHandle(handle);
79+
}
80+
81+
protected override bool ReleaseHandle()
82+
{
83+
NetworkFramework.Release(handle);
84+
SetHandle(IntPtr.Zero);
85+
return true;
86+
}
87+
}
88+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
using Microsoft.Win32.SafeHandles;
7+
8+
internal static partial class Interop
9+
{
10+
internal static partial class NetworkFramework
11+
{
12+
// Network Framework reference counting functions
13+
[LibraryImport(Libraries.NetworkFramework, EntryPoint = "nw_retain")]
14+
internal static partial IntPtr Retain(IntPtr obj);
15+
16+
[LibraryImport(Libraries.NetworkFramework, EntryPoint = "nw_release")]
17+
internal static partial void Release(IntPtr obj);
18+
19+
// Network Framework error domains
20+
internal enum NetworkFrameworkErrorDomain
21+
{
22+
Invalid = 0,
23+
POSIX = 1,
24+
DNS = 2,
25+
TLS = 3
26+
}
27+
28+
internal enum NWErrorDomainPOSIX
29+
{
30+
OperationCanceled = 89, // ECANCELED
31+
}
32+
33+
internal sealed class NetworkFrameworkException : Exception
34+
{
35+
public int ErrorCode { get; }
36+
public NetworkFrameworkErrorDomain ErrorDomain { get; }
37+
38+
internal NetworkFrameworkException()
39+
{
40+
}
41+
42+
internal NetworkFrameworkException(int errorCode, NetworkFrameworkErrorDomain errorDomain, string? message)
43+
: base(message ?? $"Network Framework error {errorCode} in domain {errorDomain}")
44+
{
45+
HResult = errorCode;
46+
ErrorCode = errorCode;
47+
ErrorDomain = errorDomain;
48+
}
49+
50+
internal NetworkFrameworkException(int errorCode, NetworkFrameworkErrorDomain errorDomain, string? message, Exception innerException)
51+
: base(message ?? $"Network Framework error {errorCode} in domain {errorDomain}", innerException)
52+
{
53+
HResult = errorCode;
54+
ErrorCode = errorCode;
55+
ErrorDomain = errorDomain;
56+
}
57+
58+
public override string ToString()
59+
{
60+
return $"{base.ToString()}, ErrorCode: {ErrorCode}, ErrorDomain: {ErrorDomain}";
61+
}
62+
}
63+
64+
[StructLayout(LayoutKind.Sequential)]
65+
internal struct NetworkFrameworkError
66+
{
67+
public int ErrorCode;
68+
public int ErrorDomain;
69+
public IntPtr ErrorMessage; // C string of NULL
70+
}
71+
72+
internal static Exception CreateExceptionForNetworkFrameworkError(in NetworkFrameworkError error)
73+
{
74+
string? message = null;
75+
NetworkFrameworkErrorDomain domain = (NetworkFrameworkErrorDomain)error.ErrorDomain;
76+
77+
if (error.ErrorMessage != IntPtr.Zero)
78+
{
79+
message = Marshal.PtrToStringUTF8(error.ErrorMessage);
80+
}
81+
82+
return new NetworkFrameworkException(error.ErrorCode, domain, message);
83+
}
84+
}
85+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
internal static partial class Interop
5+
{
6+
internal static partial class AppleCrypto
7+
{
8+
internal static class OSStatus
9+
{
10+
public const int NoErr = 0;
11+
public const int ReadErr = -19;
12+
public const int WritErr = -20;
13+
public const int EOFErr = -39;
14+
public const int SecUserCanceled = -128;
15+
public const int ErrSSLWouldBlock = -9803;
16+
}
17+
}
18+
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ internal interface IReadWriteAdapter
1414
static abstract ValueTask WriteAsync(Stream stream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken);
1515
static abstract Task FlushAsync(Stream stream, CancellationToken cancellationToken);
1616
static abstract Task WaitAsync(TaskCompletionSource<bool> waiter);
17+
static abstract Task WaitAsync(Task task);
18+
static abstract ValueTask<T> WaitAsync<T>(ValueTask<T> task);
1719
}
1820

1921
internal readonly struct AsyncReadWriteAdapter : IReadWriteAdapter
@@ -30,6 +32,8 @@ public static ValueTask WriteAsync(Stream stream, ReadOnlyMemory<byte> buffer, C
3032
public static Task FlushAsync(Stream stream, CancellationToken cancellationToken) => stream.FlushAsync(cancellationToken);
3133

3234
public static Task WaitAsync(TaskCompletionSource<bool> waiter) => waiter.Task;
35+
public static Task WaitAsync(Task task) => task;
36+
public static ValueTask<T> WaitAsync<T>(ValueTask<T> task) => task;
3337
}
3438

3539
internal readonly struct SyncReadWriteAdapter : IReadWriteAdapter
@@ -57,5 +61,16 @@ public static Task WaitAsync(TaskCompletionSource<bool> waiter)
5761
waiter.Task.GetAwaiter().GetResult();
5862
return Task.CompletedTask;
5963
}
64+
65+
public static Task WaitAsync(Task task)
66+
{
67+
task.GetAwaiter().GetResult();
68+
return Task.CompletedTask;
69+
}
70+
71+
public static ValueTask<T> WaitAsync<T>(ValueTask<T> task)
72+
{
73+
return ValueTask.FromResult(task.AsTask().GetAwaiter().GetResult());
74+
}
6075
}
6176
}

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

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ private static bool GetSsl3Support()
547547

548548
}
549549

550-
return (IsOSX || (IsLinux && OpenSslVersion < new Version(1, 0, 2) && !IsDebian));
550+
return ((IsOSX && !IsNetworkFrameworkEnabled()) || (IsLinux && OpenSslVersion < new Version(1, 0, 2) && !IsDebian));
551551
}
552552

553553
private static bool OpenSslGetTlsSupport(SslProtocols protocol)
@@ -569,7 +569,7 @@ private static bool AndroidGetSslProtocolSupport(SslProtocols protocol)
569569
private static bool GetTls10Support()
570570
{
571571
// on macOS and Android TLS 1.0 is supported.
572-
if (IsApplePlatform || IsAndroid)
572+
if ((IsApplePlatform && !IsNetworkFrameworkEnabled()) || IsAndroid)
573573
{
574574
return true;
575575
}
@@ -580,7 +580,7 @@ private static bool GetTls10Support()
580580
return GetProtocolSupportFromWindowsRegistry(SslProtocols.Tls, defaultProtocolSupport: true) && !IsWindows10Version20348OrGreater;
581581
}
582582

583-
return OpenSslGetTlsSupport(SslProtocols.Tls);
583+
return IsOpenSslSupported && OpenSslGetTlsSupport(SslProtocols.Tls);
584584
}
585585

586586
private static bool GetTls11Support()
@@ -597,12 +597,12 @@ private static bool GetTls11Support()
597597
return GetProtocolSupportFromWindowsRegistry(SslProtocols.Tls11, defaultProtocolSupport: true) && !IsWindows10Version20348OrGreater;
598598
}
599599
// on macOS and Android TLS 1.1 is supported.
600-
else if (IsApplePlatform || IsAndroid)
600+
else if ((IsApplePlatform && !IsNetworkFrameworkEnabled()) || IsAndroid)
601601
{
602602
return true;
603603
}
604604

605-
return OpenSslGetTlsSupport(SslProtocols.Tls11);
605+
return IsOpenSslSupported && OpenSslGetTlsSupport(SslProtocols.Tls11);
606606
}
607607
#pragma warning restore SYSLIB0039
608608

@@ -665,6 +665,31 @@ private static bool GetTls13Support()
665665
return false;
666666
}
667667

668+
/// <summary>
669+
/// Determines if Network.framework is enabled for SSL/TLS operations on Apple platforms.
670+
/// This can be controlled via AppContext switch or environment variable.
671+
/// </summary>
672+
/// <returns>True if Network.framework is enabled, false otherwise.</returns>
673+
public static bool IsNetworkFrameworkEnabled()
674+
{
675+
// Check AppContext switch first (highest priority)
676+
if (AppContext.TryGetSwitch("System.Net.Security.UseNetworkFramework", out bool isEnabled))
677+
{
678+
return isEnabled;
679+
}
680+
681+
// Fall back to environment variable
682+
string? envVar = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SECURITY_USENETWORKFRAMEWORK");
683+
if (!string.IsNullOrEmpty(envVar))
684+
{
685+
return envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase);
686+
}
687+
688+
// Default is disabled
689+
return false;
690+
}
691+
692+
668693
private static bool GetSendsCAListByDefault()
669694
{
670695
if (IsWindows)

src/libraries/Common/tests/TestUtilities/TestEventListener.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData)
129129
}
130130
}
131131
#endif
132-
sb.Append($"[{eventData.EventName}] ");
132+
sb.Append($"[{eventData.EventName}] ");
133133

134134
for (int i = 0; i < eventData.Payload?.Count; i++)
135135
{

src/libraries/System.Net.Security/src/System.Net.Security.csproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<ApiExclusionListPath Condition="'$(TargetPlatformIdentifier)' == ''">ExcludeApiList.PNSE.txt</ApiExclusionListPath>
1616
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'windows'">$(DefineConstants);TARGET_WINDOWS</DefineConstants>
1717
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'android'">$(DefineConstants);TARGET_ANDROID</DefineConstants>
18+
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'osx' or '$(TargetPlatformIdentifier)' == 'ios' or '$(TargetPlatformIdentifier)' == 'tvos'">$(DefineConstants);TARGET_APPLE</DefineConstants>
1819
<UseAndroidCrypto Condition="'$(TargetPlatformIdentifier)' == 'android'">true</UseAndroidCrypto>
1920
<UseAppleCrypto Condition="'$(TargetPlatformIdentifier)' == 'osx' or '$(TargetPlatformIdentifier)' == 'ios' or '$(TargetPlatformIdentifier)' == 'tvos'">true</UseAppleCrypto>
2021
<UseManagedNtlm Condition="'$(TargetPlatformIdentifier)' == 'android' or '$(TargetPlatformIdentifier)' == 'tvos'">true</UseManagedNtlm>
@@ -440,13 +441,22 @@
440441
Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Ssl.cs" />
441442
<Compile Include="$(CommonPath)Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.X509Chain.cs"
442443
Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.X509Chain.cs" />
444+
<Compile Include="$(CommonPath)Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.OSStatus.cs"
445+
Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.OSStatus.cs" />
446+
<Compile Include="$(CommonPath)Interop\OSX\Interop.NetworkFramework.cs"
447+
Link="Common\Interop\OSX\Interop.NetworkFramework.cs" />
448+
<Compile Include="$(CommonPath)Interop\OSX\Interop.NetworkFramework.Tls.cs"
449+
Link="Common\Interop\OSX\Interop.NetworkFramework.Tls.cs" />
443450
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeCreateHandle.OSX.cs"
444451
Link="Common\Microsoft\Win32\SafeHandles\SafeCreateHandle.OSX.cs" />
445452
<Compile Include="$(CommonPath)System\Net\Security\CertificateValidation.OSX.cs"
446453
Link="Common\System\Net\Security\CertificateValidation.OSX.cs" />
447454
<Compile Include="System\Net\CertificateValidationPal.OSX.cs" />
448455
<Compile Include="System\Net\Security\Pal.Managed\SslProtocolsValidation.cs" />
449456
<Compile Include="System\Net\Security\Pal.OSX\SafeDeleteSslContext.cs" />
457+
<Compile Include="System\Net\Security\Pal.OSX\SafeDeleteNwContext.cs" />
458+
<!-- TODO: move to shared code -->
459+
<Compile Include="../../System.Net.Quic/src/System/Net/Quic/Internal/ResettableValueTaskSource.cs" />
450460
<Compile Include="System\Net\Security\SslConnectionInfo.OSX.cs" />
451461
<Compile Include="System\Net\Security\SslStreamCertificateContext.OSX.cs" />
452462
<Compile Include="System\Net\Security\SslStreamPal.OSX.cs" />

0 commit comments

Comments
 (0)