diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets
index 4e5b53a938b85a..349a1a4fd46c47 100644
--- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets
+++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets
@@ -201,6 +201,7 @@ The .NET Foundation licenses this file to you under the MIT license.
+
diff --git a/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs b/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs
index b82c030e4232d4..f3951931e03910 100644
--- a/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs
+++ b/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs
@@ -14,6 +14,7 @@ internal static partial class Libraries
internal const string OpenLdap = "libldap.dylib";
internal const string SystemConfigurationLibrary = "/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration";
internal const string AppleCryptoNative = "libSystem.Security.Cryptography.Native.Apple";
+ internal const string NetworkFramework = "/System/Library/Frameworks/Network.framework/Network";
internal const string MsQuic = "libmsquic.dylib";
}
}
diff --git a/src/libraries/Common/src/Interop/OSX/Interop.NetworkFramework.Tls.cs b/src/libraries/Common/src/Interop/OSX/Interop.NetworkFramework.Tls.cs
new file mode 100644
index 00000000000000..a73f0c78f7d440
--- /dev/null
+++ b/src/libraries/Common/src/Interop/OSX/Interop.NetworkFramework.Tls.cs
@@ -0,0 +1,142 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// 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;
+using System.Security.Authentication;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ // TLS 1.3 specific Network Framework implementation for macOS
+ internal static partial class NetworkFramework
+ {
+ internal static partial class Tls
+ {
+ // Initialize internal shim for NetworkFramework integration
+ [LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwInit")]
+ [return: MarshalAs(UnmanagedType.I4)]
+ internal static unsafe partial bool Init(
+ delegate* unmanaged statusCallback,
+ delegate* unmanaged readCallback,
+ delegate* unmanaged writeCallback);
+
+ // Create a new connection context
+ [LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwCreateContext")]
+ internal static partial SafeNwHandle CreateContext([MarshalAs(UnmanagedType.I4)] bool isServer);
+
+ // Set TLS options for a connection
+ [LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwSetTlsOptions", StringMarshalling = StringMarshalling.Utf8)]
+ private static partial void SetTlsOptions(SafeNwHandle connection, IntPtr state,
+ string targetName, Span alpnBuffer, int alpnLength, SslProtocols minTlsProtocol, SslProtocols maxTlsProtocol);
+
+ internal static void SetTlsOptions(SafeNwHandle nwHandle, IntPtr state, string targetName, List? applicationProtocols, SslProtocols minTlsVersion, SslProtocols maxTlsVersion)
+ {
+ int alpnLength = GetAlpnProtocolListSerializedLength(applicationProtocols);
+ Span alpn = stackalloc byte[256];
+ SerializeAlpnProtocolList(applicationProtocols, alpn);
+
+ SetTlsOptions(nwHandle, state, targetName, alpn, alpnLength, minTlsVersion, maxTlsVersion);
+ }
+
+ // Start the TLS handshake, notifications are received via the status callback (potentially from a different thread).
+ [LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwStartTlsHandshake")]
+ internal static partial int StartTlsHandshake(SafeNwHandle connection, IntPtr state);
+
+ // takes encrypted input from underlying stream and feed it to the connection.
+ [LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwProcessInputData")]
+ internal static unsafe partial int ProcessInputData(SafeNwHandle connection, SafeNwHandle framer, byte* buffer, int bufferLength);
+
+ // sends plaintext data to the connection.
+ [LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwSendToConnection")]
+ internal static unsafe partial void SendToConnection(SafeNwHandle connection, IntPtr state, void* buffer, int bufferLength);
+
+ // read plaintext data from the connection.
+ [LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwReadFromConnection")]
+ internal static partial void ReadFromConnection(SafeNwHandle connection, IntPtr state);
+
+ // starts connection cleanup
+ [LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwCancelConnection")]
+ internal static partial void CancelConnection(SafeNwHandle connection);
+
+ // gets TLS connection information
+ [LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwGetConnectionInfo")]
+ internal static unsafe partial int GetConnectionInfo(SafeNwHandle connection, out SslProtocols pProtocol, out TlsCipherSuite pCipherSuiteOut, ref byte* negotiatedAlpn, out int negotiatedAlpnLength);
+
+ // copies the certificate chain from the connection
+ [LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwCopyCertChain")]
+ internal static partial void CopyCertChain(SafeNwHandle connection, out SafeCFArrayHandle certificates, out int count);
+
+ internal static int GetAlpnProtocolListSerializedLength(List? applicationProtocols)
+ {
+ if (applicationProtocols is null)
+ {
+ return 0;
+ }
+
+ int protocolSize = 0;
+
+ foreach (SslApplicationProtocol protocol in applicationProtocols)
+ {
+ protocolSize += protocol.Protocol.Length + 2;
+ }
+
+ return protocolSize;
+ }
+
+ private static void SerializeAlpnProtocolList(List? applicationProtocols, Span buffer)
+ {
+ if (applicationProtocols is null)
+ {
+ return;
+ }
+
+ Debug.Assert(GetAlpnProtocolListSerializedLength(applicationProtocols) == buffer.Length);
+
+ int offset = 0;
+ foreach (SslApplicationProtocol protocol in applicationProtocols)
+ {
+ buffer[offset++] = (byte)protocol.Protocol.Length;
+ protocol.Protocol.Span.CopyTo(buffer.Slice(offset));
+ offset += protocol.Protocol.Length;
+ buffer[offset++] = 0;
+ }
+ }
+ }
+
+ // Status enumeration for Network Framework TLS operations
+ internal enum StatusUpdates
+ {
+ UnknownError = 0,
+ FramerStart = 1,
+ FramerStop = 2,
+ HandshakeFinished = 3,
+ HandshakeFailed = 4,
+ ConnectionReadFinished = 100,
+ ConnectionWriteFinished = 101,
+ ConnectionWriteFailed = 102,
+ ConnectionCancelled = 103,
+ }
+ }
+
+ // Safe handle classes for Network Framework TLS resources
+ internal sealed class SafeNwHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ public SafeNwHandle() : base(ownsHandle: true) { }
+
+ public SafeNwHandle(IntPtr handle, bool ownsHandle) : base(ownsHandle)
+ {
+ SetHandle(handle);
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ NetworkFramework.Release(handle);
+ SetHandle(IntPtr.Zero);
+ return true;
+ }
+ }
+}
diff --git a/src/libraries/Common/src/Interop/OSX/Interop.NetworkFramework.cs b/src/libraries/Common/src/Interop/OSX/Interop.NetworkFramework.cs
new file mode 100644
index 00000000000000..1229645e360863
--- /dev/null
+++ b/src/libraries/Common/src/Interop/OSX/Interop.NetworkFramework.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class NetworkFramework
+ {
+ // Network Framework reference counting functions
+ [LibraryImport(Libraries.NetworkFramework, EntryPoint = "nw_retain")]
+ internal static partial IntPtr Retain(IntPtr obj);
+
+ [LibraryImport(Libraries.NetworkFramework, EntryPoint = "nw_release")]
+ internal static partial void Release(IntPtr obj);
+ }
+}
diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.OSStatus.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.OSStatus.cs
new file mode 100644
index 00000000000000..49936936419ccd
--- /dev/null
+++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.OSStatus.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+internal static partial class Interop
+{
+ internal static partial class AppleCrypto
+ {
+ internal static class OSStatus
+ {
+ public const int NoErr = 0;
+ public const int ReadErr = -19;
+ public const int WritErr = -20;
+ public const int ErrSSLWouldBlock = -9803;
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj
index 5ab3577f673e11..f296312bf7ac99 100644
--- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj
+++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj
@@ -440,6 +440,12 @@
Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Ssl.cs" />
+
+
+
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs
index f1945c428363cd..9b672d7d6c3a6d 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs
@@ -8,16 +8,13 @@
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Win32.SafeHandles;
+using OSStatus = Interop.AppleCrypto.OSStatus;
namespace System.Net
{
internal sealed class SafeDeleteSslContext : SafeDeleteContext
{
// mapped from OSX error codes
- private const int OSStatus_writErr = -20;
- private const int OSStatus_readErr = -19;
- private const int OSStatus_noErr = 0;
- private const int OSStatus_errSSLWouldBlock = -9803;
private const int InitialBufferSize = 2048;
private readonly SafeSslHandle _sslContext;
private ArrayBuffer _inputBuffer = new ArrayBuffer(InitialBufferSize);
@@ -241,14 +238,14 @@ private static unsafe int WriteToConnection(IntPtr connection, byte* data, void*
context._outputBuffer.Commit(toWrite);
// Since we can enqueue everything, no need to re-assign *dataLength.
- return OSStatus_noErr;
+ return OSStatus.NoErr;
}
}
catch (Exception e)
{
if (NetEventSource.Log.IsEnabled())
NetEventSource.Error(context, $"WritingToConnection failed: {e.Message}");
- return OSStatus_writErr;
+ return OSStatus.WritErr;
}
}
@@ -266,7 +263,7 @@ private static unsafe int ReadFromConnection(IntPtr connection, byte* data, void
if (toRead == 0)
{
- return OSStatus_noErr;
+ return OSStatus.NoErr;
}
uint transferred = 0;
@@ -274,7 +271,7 @@ private static unsafe int ReadFromConnection(IntPtr connection, byte* data, void
if (context._inputBuffer.ActiveLength == 0)
{
*dataLength = (void*)0;
- return OSStatus_errSSLWouldBlock;
+ return OSStatus.ErrSSLWouldBlock;
}
int limit = Math.Min((int)toRead, context._inputBuffer.ActiveLength);
@@ -284,14 +281,14 @@ private static unsafe int ReadFromConnection(IntPtr connection, byte* data, void
transferred = (uint)limit;
*dataLength = (void*)transferred;
- return OSStatus_noErr;
+ return OSStatus.NoErr;
}
}
catch (Exception e)
{
if (NetEventSource.Log.IsEnabled())
NetEventSource.Error(context, $"ReadFromConnectionfailed: {e.Message}");
- return OSStatus_readErr;
+ return OSStatus.ReadErr;
}
}
diff --git a/src/mono/msbuild/apple/build/AppleBuild.targets b/src/mono/msbuild/apple/build/AppleBuild.targets
index c686768b27621a..8127cc0c86a686 100644
--- a/src/mono/msbuild/apple/build/AppleBuild.targets
+++ b/src/mono/msbuild/apple/build/AppleBuild.targets
@@ -84,6 +84,7 @@
<_CommonLinkerArgs Include="-lswiftCore" />
<_CommonLinkerArgs Include="-lswiftFoundation" />
<_CommonLinkerArgs Include="-framework Foundation" />
+ <_CommonLinkerArgs Include="-framework Network" />
<_CommonLinkerArgs Include="-framework Security" />
<_CommonLinkerArgs Include="-framework CryptoKit" />
<_CommonLinkerArgs Include="-framework UIKit" />
diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/CMakeLists.txt b/src/native/libs/System.Security.Cryptography.Native.Apple/CMakeLists.txt
index bc333326b9837e..876a5c47e845ad 100644
--- a/src/native/libs/System.Security.Cryptography.Native.Apple/CMakeLists.txt
+++ b/src/native/libs/System.Security.Cryptography.Native.Apple/CMakeLists.txt
@@ -21,6 +21,7 @@ set(NATIVECRYPTO_SOURCES
pal_x509.c
pal_x509chain.c
pal_swiftbindings.o
+ pal_networkframework.m
)
if (CLR_CMAKE_TARGET_MACCATALYST OR CLR_CMAKE_TARGET_IOS OR CLR_CMAKE_TARGET_TVOS)
diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c
index 5163aa5b121236..79bdea330a2b48 100644
--- a/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c
+++ b/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c
@@ -23,6 +23,7 @@
#include "pal_x509.h"
#include "pal_x509_macos.h"
#include "pal_x509chain.h"
+#include "pal_networkframework.h"
static const Entry s_cryptoAppleNative[] =
{
@@ -137,6 +138,16 @@ static const Entry s_cryptoAppleNative[] =
DllImportEntry(AppleCryptoNative_X509StoreRemoveCertificate)
DllImportEntry(AppleCryptoNative_Pbkdf2)
DllImportEntry(AppleCryptoNative_X509GetSubjectSummary)
+ DllImportEntry(AppleCryptoNative_NwInit)
+ DllImportEntry(AppleCryptoNative_NwCreateContext)
+ DllImportEntry(AppleCryptoNative_NwSetTlsOptions)
+ DllImportEntry(AppleCryptoNative_NwStartTlsHandshake)
+ DllImportEntry(AppleCryptoNative_NwProcessInputData)
+ DllImportEntry(AppleCryptoNative_NwSendToConnection)
+ DllImportEntry(AppleCryptoNative_NwReadFromConnection)
+ DllImportEntry(AppleCryptoNative_NwCancelConnection)
+ DllImportEntry(AppleCryptoNative_NwGetConnectionInfo)
+ DllImportEntry(AppleCryptoNative_NwCopyCertChain)
};
EXTERN_C const void* CryptoAppleResolveDllImport(const char* name);
diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/extra_libs.cmake b/src/native/libs/System.Security.Cryptography.Native.Apple/extra_libs.cmake
index d220db67479ac5..adeb619b299171 100644
--- a/src/native/libs/System.Security.Cryptography.Native.Apple/extra_libs.cmake
+++ b/src/native/libs/System.Security.Cryptography.Native.Apple/extra_libs.cmake
@@ -2,7 +2,8 @@
macro(append_extra_cryptography_apple_libs NativeLibsExtra)
find_library(COREFOUNDATION_LIBRARY CoreFoundation)
find_library(SECURITY_LIBRARY Security)
+ find_library(NETWORK_LIBRARY Network)
find_library(CRYPTOKIT_LIBRARY CryptoKit)
- list(APPEND ${NativeLibsExtra} ${COREFOUNDATION_LIBRARY} ${SECURITY_LIBRARY} ${CRYPTOKIT_LIBRARY} -L/usr/lib/swift -lobjc -lswiftCore -lswiftFoundation)
+ list(APPEND ${NativeLibsExtra} ${COREFOUNDATION_LIBRARY} ${SECURITY_LIBRARY} ${NETWORK_LIBRARY} ${CRYPTOKIT_LIBRARY} -L/usr/lib/swift -lobjc -lswiftCore -lswiftFoundation)
endmacro()
diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_networkframework.h b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_networkframework.h
new file mode 100644
index 00000000000000..9cafebd6eadfe0
--- /dev/null
+++ b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_networkframework.h
@@ -0,0 +1,56 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma once
+
+#include "pal_compiler.h"
+#include
+#include
+#include
+#include // for intptr_t
+
+#ifdef __OBJC__
+#import
+#else
+#include
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Status update enumeration for TLS operations
+typedef enum
+{
+ PAL_NwStatusUpdates_UnknownError = 0,
+ PAL_NwStatusUpdates_FramerStart = 1,
+ PAL_NwStatusUpdates_FramerStop = 2,
+ PAL_NwStatusUpdates_HandshakeFinished = 3,
+ PAL_NwStatusUpdates_HandshakeFailed = 4,
+
+ PAL_NwStatusUpdates_ConnectionReadFinished = 100,
+ PAL_NwStatusUpdates_ConnectionWriteFinished = 101,
+ PAL_NwStatusUpdates_ConnectionWriteFailed = 102,
+ PAL_NwStatusUpdates_ConnectionCancelled = 103,
+} PAL_NwStatusUpdates;
+
+// Callback type definitions that match the implementation usage
+typedef void (*StatusUpdateCallback)(size_t context, PAL_NwStatusUpdates status, size_t data1, size_t data2);
+typedef int32_t (*ReadCallback)(void* context, uint8_t* buffer, size_t* length);
+typedef int32_t (*WriteCallback)(void* context, uint8_t* buffer, size_t length);
+
+// Only TLS-specific Network Framework functions are exported
+PALEXPORT nw_connection_t AppleCryptoNative_NwCreateContext(int32_t isServer);
+PALEXPORT int32_t AppleCryptoNative_NwStartTlsHandshake(nw_connection_t connection, size_t gcHandle);
+PALEXPORT int32_t AppleCryptoNative_NwInit(StatusUpdateCallback statusFunc, ReadCallback readFunc, WriteCallback writeFunc);
+PALEXPORT void AppleCryptoNative_NwSendToConnection(nw_connection_t connection, size_t gcHandle, uint8_t* buffer, int length);
+PALEXPORT void AppleCryptoNative_NwReadFromConnection(nw_connection_t connection, size_t gcHandle);
+PALEXPORT int32_t AppleCryptoNative_NwProcessInputData(nw_connection_t connection, nw_framer_t framer, const uint8_t * data, int dataLength);
+PALEXPORT void AppleCryptoNative_NwSetTlsOptions(nw_connection_t connection, size_t gcHandle, char* targetName, const uint8_t* alpnBuffer, int alpnLength, PAL_SslProtocol minTlsProtocol, PAL_SslProtocol maxTlsProtocol);
+PALEXPORT int32_t AppleCryptoNative_NwGetConnectionInfo(nw_connection_t connection, PAL_SslProtocol* pProtocol, uint16_t* pCipherSuiteOut, const char** negotiatedAlpn, int32_t* negotiatedAlpnLength);
+PALEXPORT void AppleCryptoNative_NwCopyCertChain(nw_connection_t connection, CFArrayRef* certificates, int* count);
+PALEXPORT void AppleCryptoNative_NwCancelConnection(nw_connection_t connection);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_networkframework.m b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_networkframework.m
new file mode 100644
index 00000000000000..0cceaae8cbcc8a
--- /dev/null
+++ b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_networkframework.m
@@ -0,0 +1,473 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#include "pal_networkframework.h"
+#include
+#include
+
+static WriteCallback _writeFunc;
+static ReadCallback _readFunc;
+static StatusUpdateCallback _statusFunc;
+static nw_protocol_definition_t _framerDefinition;
+static nw_protocol_definition_t _tlsDefinition;
+static dispatch_queue_t _tlsQueue;
+static dispatch_queue_t _inputQueue;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability-new"
+
+PALEXPORT nw_connection_t AppleCryptoNative_NwCreateContext(int32_t isServer)
+{
+ if (isServer != 0) // the current implementation only supports client
+ return NULL;
+
+ // Network.framework requires an underlying network connection for TLS operations,
+ // but we need to perform TLS without an actual connection. This implementation
+ // uses a workaround: we create a dummy UDP connection that will never be used.
+ //
+ // The trick works by layering a custom framer on top of this dummy connection,
+ // then adding TLS on top of the framer. The framer intercepts the raw TLS data
+ // and exposes it to SslStream, preventing it from ever reaching the underlying
+ // connection.
+ //
+ // The endpoint values (127.0.0.1:42) are arbitrary - they just need to be
+ // syntactically and semantically valid since the connection is never established.
+ //
+ // TODO: reuse parameters and endpoint across connections
+ nw_parameters_t parameters = nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION);
+ nw_endpoint_t endpoint = nw_endpoint_create_host("127.0.0.1", "42");
+
+ nw_connection_t connection = nw_connection_create(endpoint, parameters);
+
+ // connection retains its own reference to the endpoint and parameters
+ nw_release(endpoint);
+ nw_release(parameters);
+
+ return connection;
+}
+
+// This writes encrypted TLS frames to the safe handle. It is executed on NW Thread pool
+static nw_framer_output_handler_t framer_output_handler = ^(nw_framer_t framer, nw_framer_message_t message, size_t message_length, bool is_complete)
+{
+
+ if (__builtin_available(macOS 12.3, iOS 15.4, tvOS 15.4, watchOS 2.0, *))
+ {
+ nw_protocol_options_t framer_options = nw_framer_copy_options(framer);
+
+ NSNumber* num = nw_framer_options_copy_object_value(framer_options, "GCHANDLE");
+ assert(num != NULL);
+
+ void * ptr;
+ [num getValue:&ptr];
+ size_t size = message_length;
+
+ nw_framer_parse_output(framer, 1, message_length, NULL, ^size_t(uint8_t *buffer, size_t buffer_length, bool is_complete2) {
+ size_t length = buffer_length;
+ (_writeFunc)(ptr, buffer, length);
+ (void)is_complete2;
+ (void)message;
+ return buffer_length;
+ });
+
+ nw_release(framer_options);
+ }
+ else
+ {
+ assert(0);
+ }
+ (void)is_complete;
+};
+
+static nw_framer_stop_handler_t framer_stop_handler = ^bool(nw_framer_t framer) {
+ if (__builtin_available(macOS 12.3, iOS 15.4, tvOS 15.4, watchOS 8.4, *))
+ {
+ size_t state = 0;
+ nw_protocol_options_t framer_options = nw_framer_copy_options(framer);
+ NSNumber* num = nw_framer_options_copy_object_value(framer_options, "GCHANDLE");
+ if (num != NULL)
+ {
+ [num getValue:&state];
+ (_statusFunc)(state, PAL_NwStatusUpdates_FramerStop, 0, 0);
+ }
+
+ nw_release(framer_options);
+ }
+
+ return TRUE;
+};
+
+static nw_framer_cleanup_handler_t framer_cleanup_handler = ^(nw_framer_t framer) {
+ (void)framer;
+};
+
+
+// This is called when connection start to set up framer
+static nw_framer_start_handler_t framer_start = ^nw_framer_start_result_t(nw_framer_t framer)
+{
+ assert(_statusFunc != NULL);
+ size_t state = 0;
+
+ nw_protocol_options_t framer_options = nw_framer_copy_options(framer);
+ NSNumber* num = nw_framer_options_copy_object_value(framer_options, "GCHANDLE");
+ assert(num != NULL);
+
+ [num getValue:&state];
+
+ // Notify SafeHandle with framer instance so we can submit to it directly.
+ (_statusFunc)(state, PAL_NwStatusUpdates_FramerStart, (size_t)framer, 0);
+
+ nw_framer_set_output_handler(framer, framer_output_handler);
+
+ nw_framer_set_stop_handler(framer, framer_stop_handler);
+ nw_framer_set_cleanup_handler(framer, framer_cleanup_handler);
+
+ nw_release(framer_options);
+
+ return nw_framer_start_result_ready;
+};
+
+
+// this takes encrypted input from underlying stream and feeds it to nw_connection.
+PALEXPORT int32_t AppleCryptoNative_NwProcessInputData(nw_connection_t connection, nw_framer_t framer, const uint8_t * buffer, int bufferLength)
+{
+ if (connection == NULL || framer == NULL)
+ {
+ return -1;
+ }
+
+ nw_framer_message_t message = nw_framer_message_create(framer);
+
+ // There is race condition when connection can fail or be canceled and if it does we fail to create the message here.
+ if (message == NULL)
+ {
+ return -1;
+ }
+
+ uint8_t * copy = NULL;
+ if (bufferLength > 0)
+ {
+ copy = malloc((size_t)bufferLength);
+ if (copy == NULL)
+ {
+ return -1;
+ }
+ memcpy(copy, buffer, bufferLength);
+ nw_framer_message_set_value(message, "DATA", copy, ^(void* ptr) {
+ free(ptr);
+ });
+ }
+
+ nw_framer_async(framer, ^(void)
+ {
+ nw_framer_deliver_input(framer, copy, (size_t)bufferLength, message, bufferLength > 0 ? FALSE : TRUE);
+ nw_release(message);
+ });
+
+ (void)connection;
+
+ return 0;
+}
+
+// This starts TLS handshake. For client, it will produce ClientHello and call output handler (on thread pool)
+// important part here is the state handler that will get asynchronous n=notifications about progress.
+PALEXPORT int AppleCryptoNative_NwStartTlsHandshake(nw_connection_t connection, size_t state)
+{
+ if (connection == NULL)
+ return -1;
+
+ nw_retain(connection); // hold a reference until canceled
+ nw_connection_set_state_changed_handler(connection, ^(nw_connection_state_t status, nw_error_t error) {
+ int errorCode = error ? nw_error_get_error_code(error) : 0;
+ switch (status)
+ {
+ case nw_connection_state_preparing:
+ case nw_connection_state_waiting:
+ case nw_connection_state_failed:
+ {
+ if (errorCode != 0 || status == nw_connection_state_failed)
+ {
+ (_statusFunc)(state, PAL_NwStatusUpdates_HandshakeFailed, (size_t)errorCode, 0);
+ }
+ }
+ break;
+ case nw_connection_state_ready:
+ {
+ (_statusFunc)(state, PAL_NwStatusUpdates_HandshakeFinished, 0, 0);
+ }
+ break;
+ case nw_connection_state_cancelled:
+ {
+ (_statusFunc)(state, PAL_NwStatusUpdates_ConnectionCancelled, 0, 0);
+ nw_release(connection); // release the reference we held
+ }
+ break;
+ case nw_connection_state_invalid:
+ {
+ (_statusFunc)(state, PAL_NwStatusUpdates_UnknownError, 0, 0);
+ }
+ break;
+ }
+ });
+
+ nw_connection_set_queue(connection, _tlsQueue);
+ nw_connection_start(connection);
+
+ return 0;
+}
+
+// This will start connection cleanup
+PALEXPORT void AppleCryptoNative_NwCancelConnection(nw_connection_t connection)
+{
+ nw_connection_cancel(connection);
+}
+
+// this is used by encrypt. We write plain text to the connection and it will be handound out encrypted via output handler
+PALEXPORT void AppleCryptoNative_NwSendToConnection(nw_connection_t connection, size_t state, uint8_t* buffer, int length)
+{
+ dispatch_data_t data = dispatch_data_create(buffer, (size_t)length, _inputQueue, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+
+ nw_connection_send(connection, data, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, FALSE, ^(nw_error_t error) {
+ if (error != NULL)
+ {
+ int errorCode = nw_error_get_error_code(error);
+ (_statusFunc)(state, PAL_NwStatusUpdates_ConnectionWriteFailed, (size_t)errorCode, 0);
+ }
+ else
+ {
+ (_statusFunc)(state, PAL_NwStatusUpdates_ConnectionWriteFinished, 0, 0);
+ }
+ });
+}
+
+// This is used by decrypt. We feed data in via AppleCryptoNative_NwProcessInputData and we try to read from the connection.
+PALEXPORT void AppleCryptoNative_NwReadFromConnection(nw_connection_t connection, size_t state)
+{
+ nw_connection_receive(connection, 1, 65536, ^(dispatch_data_t content, nw_content_context_t context, bool is_complete, nw_error_t error) {
+ int errorCode = error ? nw_error_get_error_code(error) : 0;
+
+ if (error != NULL)
+ {
+ errorCode = nw_error_get_error_code(error);
+ return;
+ }
+
+ if (content != NULL)
+ {
+ const void *buffer;
+ size_t bufferLength;
+ dispatch_data_t tmp = dispatch_data_create_map(content, &buffer, &bufferLength);
+ (_statusFunc)(state, PAL_NwStatusUpdates_ConnectionReadFinished, bufferLength, (size_t)buffer);
+ dispatch_release(tmp);
+ }
+
+ if (is_complete || content == NULL)
+ {
+ (_statusFunc)(state, PAL_NwStatusUpdates_ConnectionReadFinished, 0, 0);
+ }
+ (void)context;
+ });
+}
+
+static tls_protocol_version_t PalSslProtocolToTlsProtocolVersion(PAL_SslProtocol palProtocolId)
+{
+ switch (palProtocolId)
+ {
+ case PAL_SslProtocol_Tls13:
+ return tls_protocol_version_TLSv13;
+ case PAL_SslProtocol_Tls12:
+ return tls_protocol_version_TLSv12;
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ case PAL_SslProtocol_Tls11:
+ return tls_protocol_version_TLSv11;
+ case PAL_SslProtocol_Tls10:
+ return tls_protocol_version_TLSv10;
+ case tls_protocol_version_DTLSv10:
+ default:
+ break;
+#pragma clang diagnostic pop
+ }
+
+ return (tls_protocol_version_t)0;
+}
+
+// This configures TLS properties
+PALEXPORT void AppleCryptoNative_NwSetTlsOptions(nw_connection_t connection, size_t state, char* targetName, const uint8_t * alpnBuffer, int alpnLength, PAL_SslProtocol minTlsProtocol, PAL_SslProtocol maxTlsProtocol)
+{
+ nw_protocol_options_t tls_options = nw_tls_create_options();
+ sec_protocol_options_t sec_options = nw_tls_copy_sec_protocol_options(tls_options);
+ if (targetName != NULL)
+ {
+ sec_protocol_options_set_tls_server_name(sec_options, targetName);
+ }
+
+ tls_protocol_version_t version = PalSslProtocolToTlsProtocolVersion(minTlsProtocol);
+ if ((int)version != 0)
+ {
+ sec_protocol_options_set_min_tls_protocol_version(sec_options, version);
+ }
+ version = PalSslProtocolToTlsProtocolVersion(maxTlsProtocol);
+ if ((int)version != 0)
+ {
+ sec_protocol_options_set_max_tls_protocol_version(sec_options, version);
+ }
+
+ if (alpnBuffer != NULL)
+ {
+ int offset = 0;
+ while (offset < alpnLength)
+ {
+ uint8_t length = alpnBuffer[offset];
+ sec_protocol_options_add_tls_application_protocol(sec_options, (const char*) &alpnBuffer[offset + 1]);
+ offset += length + 2;
+ }
+ }
+
+ // we accept all certificates here and we will do validation later
+ sec_protocol_options_set_verify_block(sec_options, ^(sec_protocol_metadata_t metadata, sec_trust_t trust_ref, sec_protocol_verify_complete_t complete) {
+ (void)metadata;
+ (void)trust_ref;
+ complete(true);
+ }, _tlsQueue);
+
+ nw_release(sec_options);
+
+ nw_protocol_options_t framer_options = nw_framer_create_options(_framerDefinition);
+ if (__builtin_available(macOS 12.3, iOS 15.4, tvOS 15.4.0, watchOS 8.4, *))
+ {
+ NSNumber *ref = [NSNumber numberWithLong:(long)state];
+ nw_framer_options_set_object_value(framer_options, "GCHANDLE", ref);
+ }
+
+ nw_parameters_t parameters = nw_connection_copy_parameters(connection);
+ nw_protocol_stack_t protocol_stack = nw_parameters_copy_default_protocol_stack(parameters);
+ nw_protocol_stack_prepend_application_protocol(protocol_stack, framer_options);
+ nw_protocol_stack_prepend_application_protocol(protocol_stack, tls_options);
+
+ nw_release(framer_options);
+ nw_release(protocol_stack);
+ nw_release(tls_options);
+}
+
+// This wil get TLS details after handshake is finished
+PALEXPORT int32_t AppleCryptoNative_NwGetConnectionInfo(nw_connection_t connection, PAL_SslProtocol* protocol, uint16_t* pCipherSuiteOut, const char** negotiatedAlpn, int32_t* negotiatedAlpnLength)
+{
+ nw_protocol_metadata_t meta = nw_connection_copy_protocol_metadata(connection, _tlsDefinition);
+ if (meta != NULL)
+ {
+ sec_protocol_metadata_t secMeta = nw_tls_copy_sec_protocol_metadata(meta);
+ const char* alpn = sec_protocol_metadata_get_negotiated_protocol(secMeta);
+ if (alpn != NULL)
+ {
+ *negotiatedAlpn = alpn;
+ *negotiatedAlpnLength = (int32_t)strlen(alpn);
+ }
+ else
+ {
+ *negotiatedAlpn = NULL;
+ *negotiatedAlpnLength = 0;
+ }
+
+ tls_protocol_version_t version = sec_protocol_metadata_get_negotiated_tls_protocol_version(secMeta);
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ switch (version)
+ {
+ case tls_protocol_version_TLSv10:
+ *protocol = PAL_SslProtocol_Tls10;
+ break;
+ case tls_protocol_version_TLSv11:
+ *protocol = PAL_SslProtocol_Tls11;
+ break;
+ case tls_protocol_version_TLSv12:
+ *protocol = PAL_SslProtocol_Tls12;
+ break;
+ case tls_protocol_version_TLSv13:
+ *protocol = PAL_SslProtocol_Tls13;
+ break;
+ case tls_protocol_version_DTLSv10:
+ case tls_protocol_version_DTLSv12:
+ default:
+ *protocol = PAL_SslProtocol_None;
+ break;
+ }
+#pragma clang diagnostic pop
+
+ *pCipherSuiteOut = sec_protocol_metadata_get_negotiated_tls_ciphersuite(secMeta);
+
+ nw_release(meta);
+ nw_release(secMeta);
+
+ return 0;
+ }
+
+ return -1;
+}
+
+PALEXPORT void AppleCryptoNative_NwCopyCertChain(nw_connection_t connection, CFArrayRef* certificates, int* certificateCount)
+{
+ CFMutableArrayRef certs = NULL;
+ __block int count = 0;
+
+ nw_protocol_metadata_t meta = nw_connection_copy_protocol_metadata(connection, _tlsDefinition);
+ if (meta != NULL)
+ {
+ sec_protocol_metadata_t secMeta = nw_tls_copy_sec_protocol_metadata(meta);
+ if (secMeta != NULL)
+ {
+ sec_protocol_metadata_access_peer_certificate_chain(secMeta, ^(sec_certificate_t certificate) {
+ count++;
+ (void*)certificate;
+ });
+
+ if (count > 0)
+ {
+ certs = CFArrayCreateMutable(NULL, count, &kCFTypeArrayCallBacks);
+
+ sec_protocol_metadata_access_peer_certificate_chain(secMeta, ^(sec_certificate_t certificate) {
+ CFArrayAppendValue(certs, sec_certificate_copy_ref(certificate));
+ });
+ }
+
+ sec_release(secMeta);
+ }
+
+ nw_release(meta);
+ }
+
+ if (certs != NULL)
+ {
+ *certificateCount = (int)CFArrayGetCount(certs);
+ *certificates = (CFArrayRef)certs;
+ }
+ else
+ {
+ *certificateCount = 0;
+ *certificates = NULL;
+ }
+}
+#pragma clang diagnostic pop
+
+// this is called once to set everything up
+PALEXPORT int32_t AppleCryptoNative_NwInit(StatusUpdateCallback statusFunc, ReadCallback readFunc, WriteCallback writeFunc)
+{
+ assert(statusFunc != NULL);
+ assert(writeFunc != NULL);
+ assert(readFunc != NULL);
+
+ if (__builtin_available(macOS 12.3, iOS 15.4, tvOS 15.4.0, watchOS 8.4, *))
+ {
+ _writeFunc = writeFunc;
+ _readFunc = readFunc;
+ _statusFunc = statusFunc;
+ _framerDefinition = nw_framer_create_definition("com.dotnet.networkframework.tlsframer",
+ NW_FRAMER_CREATE_FLAGS_DEFAULT, framer_start);
+ _tlsDefinition = nw_protocol_copy_tls_definition();
+ _tlsQueue = dispatch_queue_create("com.dotnet.networkframework.tlsqueue", NULL);
+ _inputQueue = dispatch_queue_create("com.dotnet.networkframework.inputqueue", NULL);
+
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/src/tasks/AppleAppBuilder/Templates/CMakeLists-librarymode.txt.template b/src/tasks/AppleAppBuilder/Templates/CMakeLists-librarymode.txt.template
index bda7c7fb3a30be..c2d4e9c004cde4 100644
--- a/src/tasks/AppleAppBuilder/Templates/CMakeLists-librarymode.txt.template
+++ b/src/tasks/AppleAppBuilder/Templates/CMakeLists-librarymode.txt.template
@@ -61,6 +61,7 @@ target_link_libraries(
%ProjectName%
PRIVATE
"-framework Foundation"
+ "-framework Network"
"-framework Security"
"-framework CryptoKit"
"-framework UIKit"
diff --git a/src/tasks/AppleAppBuilder/Templates/CMakeLists.txt.template b/src/tasks/AppleAppBuilder/Templates/CMakeLists.txt.template
index 300f2fc5414f4b..850825f2039e0b 100644
--- a/src/tasks/AppleAppBuilder/Templates/CMakeLists.txt.template
+++ b/src/tasks/AppleAppBuilder/Templates/CMakeLists.txt.template
@@ -70,6 +70,7 @@ target_link_libraries(
%ProjectName%
PRIVATE
"-framework Foundation"
+ "-framework Network"
"-framework Security"
"-framework CryptoKit"
"-framework UIKit"
diff --git a/src/tasks/LibraryBuilder/Templates/CMakeLists.txt.template b/src/tasks/LibraryBuilder/Templates/CMakeLists.txt.template
index 2af12ae8cf85a0..d7d0406de4fe83 100644
--- a/src/tasks/LibraryBuilder/Templates/CMakeLists.txt.template
+++ b/src/tasks/LibraryBuilder/Templates/CMakeLists.txt.template
@@ -42,6 +42,7 @@ if(TARGETS_ANDROID)
elseif(TARGETS_APPLE_MOBILE)
set(MOBILE_SYSTEM_LIBS
"-framework Foundation"
+ "-framework Network"
"-framework Security"
"-framework CryptoKit"
"-framework UIKit"