Skip to content

[WIP] Server-side TLS 1.3 support on OSX PoC #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: osx-tls13
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,7 @@ private static bool GetTls13Support()
else if (IsOSX || IsMacCatalyst || IsiOS || IstvOS)
{
// [ActiveIssue("https://github.com/dotnet/runtime/issues/1979")]
return false;
return true;
}
else if (IsAndroid)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,22 @@ internal static SslPolicyErrors VerifyCertificateProperties(

X509Certificate2? result = null;

SafeX509ChainHandle chainHandle = securityContext switch
SafeX509ChainHandle? chainHandle = securityContext switch
{
SafeDeleteNwContext nwContext => nwContext.PeerX509ChainHandle!,
SafeDeleteSslContext sslContext => Interop.AppleCrypto.SslCopyCertChain(sslContext.SslContext),
_ => throw new ArgumentException("Invalid context type", nameof(securityContext))
};

if (chainHandle == null || chainHandle.IsInvalid)
{
if (NetEventSource.Log.IsEnabled())
{
NetEventSource.Error(securityContext, "Failed to retrieve remote certificate: chain handle is invalid.");
}
return null;
}

try
{
long chainSize = Interop.AppleCrypto.X509ChainGetChainSize(chainHandle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,12 @@ private static bool CheckNetworkFrameworkAvailability()
{
// Call Init with null callbacks to check if Network Framework is available
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, "Checking Network Framework availability...");
return !Interop.NetworkFramework.Tls.Init(&StatusUpdateCallback, &WriteOutboundWireData, &ChallengeCallback);
bool isAvailable = !Interop.NetworkFramework.Tls.Init(&StatusUpdateCallback, &WriteOutboundWireData, &ChallengeCallback);
if (NetEventSource.Log.IsEnabled())
{
NetEventSource.Info(null, $"Network Framework availability check completed: {isAvailable}");
}
return isAvailable;
}
}
catch
Expand Down Expand Up @@ -599,6 +604,11 @@ private static IntPtr ChallengeCallback(IntPtr thisHandle, IntPtr acceptableIssu
return nwContext._selectedClientCertificate;
}

if (nwContext.SslAuthenticationOptions.IsServer)
{
return nwContext._selectedClientCertificate = nwContext.SslAuthenticationOptions.CertificateContext?.TargetCertificate.Handle ?? IntPtr.Zero;
}

nwContext._acceptableIssuers = ExtractAcceptableIssuers(acceptableIssuersHandle);
Debug.Assert(nwContext._peerCertChainHandle != null, "Peer certificate chain handle should be set before challenge callback");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,12 @@ private async Task ForceAuthenticationAsync<TIOAdapter>(bool receiveFirst, byte[
#if TARGET_APPLE
if (SslStreamPal.ShouldUseAsyncSecurityContext(_sslAuthenticationOptions))
{
Debug.Assert(_sslAuthenticationOptions.IsClient);
// Debug.Assert(_sslAuthenticationOptions.IsClient);
byte[]? dummy = null;
AcquireClientCredentials(ref dummy, true);
if (_sslAuthenticationOptions.IsClient)
{
AcquireClientCredentials(ref dummy, true);
}

Task<Exception?> handshakeTask = SslStreamPal.AsyncHandshakeAsync(ref _securityContext, this, cancellationToken);
await TIOAdapter.WaitAsync(handshakeTask).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,6 @@ private static bool ShouldUseNetworkFramework(
SslAuthenticationOptions sslAuthenticationOptions)
{
return
sslAuthenticationOptions.IsClient &&
SafeDeleteNwContext.IsNetworkFrameworkAvailable &&
(sslAuthenticationOptions.EnabledSslProtocols == SslProtocols.None ||
sslAuthenticationOptions.EnabledSslProtocols == SslProtocols.Tls13 ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,6 @@ static CFStringRef ExtractNetworkFrameworkError(nw_error_t error, PAL_NetworkFra

PALEXPORT nw_connection_t AppleCryptoNative_NwConnectionCreate(int32_t isServer, void* state, char* targetName, const uint8_t * alpnBuffer, int alpnLength, PAL_SslProtocol minTlsProtocol, PAL_SslProtocol maxTlsProtocol, uint32_t* cipherSuites, int cipherSuitesLength)
{
if (isServer != 0) // the current implementation only supports client
return NULL;

nw_parameters_t parameters = nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION);

#pragma clang diagnostic push
Expand Down Expand Up @@ -310,6 +307,122 @@ PALEXPORT nw_connection_t AppleCryptoNative_NwConnectionCreate(int32_t isServer,
nw_release(protocol_stack);
nw_release(tls_options);

if (isServer != 0)
{
// Server-side implementation: create a listener, wait for connection, return it
__block nw_connection_t serverConnection = NULL;
__block dispatch_semaphore_t connectionSemaphore = dispatch_semaphore_create(0);

// Create listener on random port
nw_listener_t listener = nw_listener_create(parameters);
if (listener == NULL)
{
LOG(state, "Failed to create listener");
nw_release(parameters);
dispatch_release(connectionSemaphore);
return NULL;
}

// Set up new connection handler to capture incoming connection
nw_listener_set_new_connection_handler(listener, ^(nw_connection_t connection) {
LOG(state, "New connection received on listener");
serverConnection = nw_retain(connection);
dispatch_semaphore_signal(connectionSemaphore);
});

// Start listener
nw_listener_set_queue(listener, _tlsQueue);
// Wait for listener to be ready
dispatch_semaphore_t listenerReadySemaphore = dispatch_semaphore_create(0);
nw_listener_set_state_changed_handler(listener, ^(nw_listener_state_t listenerState, nw_error_t error) {
(void)error;
if (listenerState == nw_listener_state_ready)
{
LOG(state, "Listener is ready");
dispatch_semaphore_signal(listenerReadySemaphore);
}
else if (listenerState == nw_listener_state_failed)
{
LOG(state, "Listener failed to start");
dispatch_semaphore_signal(listenerReadySemaphore);
}
});

nw_listener_start(listener);

// Wait for listener to be ready (timeout after 5 seconds)
if (dispatch_semaphore_wait(listenerReadySemaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)) != 0)
{
LOG(state, "Timeout waiting for listener to be ready");
nw_listener_cancel(listener);
nw_release(listener);
nw_release(parameters);
dispatch_release(connectionSemaphore);
dispatch_release(listenerReadySemaphore);
return NULL;
}
dispatch_release(listenerReadySemaphore);

// Get the actual port the listener is using
uint16_t listenerPort = nw_listener_get_port(listener);
LOG(state, "Listener bound to port %d", listenerPort);

if (listenerPort == 0)
{
LOG(state, "Failed to get listener port");
nw_listener_cancel(listener);
nw_release(listener);
nw_release(parameters);
dispatch_release(connectionSemaphore);
return NULL;
}

// Create dummy client connection to trigger the listener
char portStr[16];
snprintf(portStr, sizeof(portStr), "%d", listenerPort);
nw_endpoint_t clientEndpoint = nw_endpoint_create_host("127.0.0.1", portStr);
nw_parameters_t clientParams = nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION);
nw_connection_t dummyClient = nw_connection_create(clientEndpoint, clientParams);

nw_connection_set_queue(dummyClient, _tlsQueue);
nw_connection_start(dummyClient);

// Send a 0-byte packet to trigger the connection
nw_connection_send(dummyClient, NULL, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, ^(nw_error_t error) {
if (error != NULL) {
LOG(state, "Failed to send dummy packet");
}
});

// Wait for server connection (timeout after 5 seconds)
if (dispatch_semaphore_wait(connectionSemaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)) != 0)
{
LOG(state, "Timeout waiting for server connection");
nw_connection_cancel(dummyClient);
nw_release(dummyClient);
nw_listener_cancel(listener);
nw_release(listener);
nw_release(clientEndpoint);
nw_release(clientParams);
nw_release(parameters);
dispatch_release(connectionSemaphore);
return NULL;
}

// Clean up
nw_connection_cancel(dummyClient);
nw_release(dummyClient);
nw_listener_cancel(listener);
nw_release(listener);
nw_release(clientEndpoint);
nw_release(clientParams);
nw_release(parameters);
dispatch_release(connectionSemaphore);

LOG(state, "Server connection created successfully");
return serverConnection;
}

nw_connection_t connection = nw_connection_create(_endpoint, parameters);

nw_release(parameters);
Expand Down