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"