Skip to content
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

add support of TLS 1.3 on macOS POC #104835

Closed
wants to merge 31 commits into from
Closed

add support of TLS 1.3 on macOS POC #104835

wants to merge 31 commits into from

Conversation

wfurt
Copy link
Member

@wfurt wfurt commented Jul 12, 2024

This contributes to #1979 that is long due. The API we currently use is deprecated since 10.5 Catalina and can possibly go at any time. Also with recent security push there may be more TLS 1.3 only servers and there really no good workaround for macOS and iOS so those platforms will simply fail to connect.

There are conceptually two challenges.

The new API does not provide capability to work on top of buffers like SslStream expects. To over come it we use Framer approach pretending we are implementing custom protocol. Something @filipnavara demonstrated here: https://gist.github.com/filipnavara/d5fb55bdb5edcceb1981f73078b855c4

The second is that there is no synchronize API. We basically call void function and we provide lambda that would be called once in the future. While that may be generally handy and similar to our 'async` it creates significant complication and platform difference.

To do that both handshake and decrypt have new Task that deliver events. I tried to structure it in a way that it has no impact on other platform. There can perhaps be improvements here. Certainly the "decrypt in place is a lieand we can possibly avoid some copies and memory use by refactoring. Original naive implementation was hitting assets for synchronous API - something I fixed by tracking sync vs async now and using synchronousWhenAny` when needed.

This PR is trying to close the gap by addressing both. It is still in rough shape but I would like to solicit early feedback before spending too much time polishing. I can do basic operations and pass most of the test locally:

=== TEST EXECUTION SUMMARY ===
     System.Net.Security.Tests  Total: 577, Errors: 0, Failed: 9, Skipped: 16, Time: 405.069s

Since the implementation is not complete I keep old one around as well and I use new one only when seems appropriate.
I also envision some possible compat switch so user can opt-in or opt-out as needed. The focus is on client for developers talking to server they cannot control. For simple HTTP(s) it should just work.

From failing tests, the new API is more picky about non ASCIS in SNI name. Something we saw on Android, but it should not matter for HTTP as we puny-code IDN.

Some tests (particularly around cipher suites) are confused as we cannot handshake TLS 1.3 in tests but the client side is capable of doing it.

And there are still some loose ends around closing, disposing and cleanup.

it would be great to get feedback, particularly from
@jkotas for inter
@stephentoub for async
@filipnavara for objective-C and macOS API use
@rzikm for integration with SslStream

and @dotnet/ncl for all the above. Note that the Network Framework can also do Quic so this may be option for use in the distant future.

Copy link
Contributor

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

@@ -0,0 +1,429 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

Copy link
Member

@liveans liveans Jul 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file looks like C with some AppleClang extensions, rather than objective C

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is Objective-C e.g. .m vs .c file type.

Copy link
Member

@rzikm rzikm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks interesting so far, but it feels like a substantial change which we are unlikely to get into 9.0

Comment on lines 77 to +82

[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwCreateContext")]
internal static partial System.Net.SafeSslHandle NwCreateContext(int isServer);

[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwInit")]
internal static unsafe partial int NwInit(delegate* unmanaged<IntPtr, PAL_NwStatusUpdates, IntPtr, IntPtr, int> statusCallback, delegate* unmanaged<IntPtr, byte*, void**, int> readCallback, delegate* unmanaged<IntPtr, byte*, void**, int> writeCallback);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume Nw* and Ssl* functions can't be used interchangeably, would it make sense to move it to its own class like Interop.NetworkFramework?

Copy link
Member

@liveans liveans Jul 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be helpful, especially for the possible future implementations/experiments. (QUIC API is there and can be used with couple of more interop additions to this. (there are some bugs))

@@ -251,11 +253,13 @@ private async Task RenegotiateAsync<TIOAdapter>(CancellationToken cancellationTo
}

// reAuthenticationData is only used on Windows in case of renegotiation.
private async Task ForceAuthenticationAsync<TIOAdapter>(bool receiveFirst, byte[]? reAuthenticationData, CancellationToken cancellationToken)
private async Task ForceAuthenticationAsync<TIOAdapter>(bool receiveFirst, byte[]? reAuthenticationData, CancellationToken cancellationToken, bool isAsync = true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extra isAsync parameter is a bit weird, since the information should already be apparent from the TIOAdapter. Is this expected to stay? So far the usages of this inside the function seem debug-only or WIP code.

int toWrite = (int)length;
var inputBuffer = new ReadOnlySpan<byte>(data, toWrite);

if (context.UseNwFramework)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are now lots of such branches all over the codebase, It might be a lot of work but reshuffling the code s.t code-paths which uses NetworkFramework is in it's own class (SafeDeleteNwSslContext maybe?) would help readability IMO.

@@ -34,6 +34,7 @@ internal enum SecurityStatusPalErrorCode
Renegotiate,
TryAgain,
HandshakeStarted,
ContinuePendig,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ContinuePendig,
ContinuePending,

@wfurt
Copy link
Member Author

wfurt commented Jul 15, 2024

Looks interesting so far, but it feels like a substantial change which we are unlikely to get into 9.0

My goal would be to make it op-in feature like we did for managed NTLM. e.g. make minimal impact by default but provide something for users who would be stuck otherwise.


internal static unsafe int NwSetTlsOptions(SafeSslHandle sslHandle, nint gcHandle, string targetName, List<SslApplicationProtocol>? applicationProtocols, SslProtocols minTlsVersion, SslProtocols maxTlsVersion)
{
int alpnLength = GetAlpnProtocolListSerializedLength(applicationProtocols);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: indentation

[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwCancelConnection")]
internal static partial int NwCancelConnection(SafeSslHandle sslHandle);


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

return;
}

Debug.Assert(GetAlpnProtocolListSerializedLength(applicationProtocols) == buffer.Length,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, you no longer need this message. The exact same message will now be injected for you,

@@ -53,6 +54,7 @@ public ArrayBuffer(byte[] buffer)

public void Dispose()
{
_disposed = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: indenting

@@ -2864,7 +2865,7 @@ public virtual async Task UseWrappedAfterClose_Success()
readable.ReadByte();
}
}

*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

@@ -355,12 +620,32 @@ internal int ReadPendingWrites(byte[] buf, int offset, int count)
SslProtocols.Tls12
};

private static void SetProtocols(SafeSslHandle sslContext, SslProtocols protocols)
private static readonly SslProtocols[] s_orderedSslProtocols13 = new SslProtocols[6]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be:

private static ReadOnlySpan<SslProtocols> OrderedSslProtocols13 =>
[
#pragma warning disable 0618
    SslProtocols.Ssl2,
    SslProtocols.Ssl3,
#pragma warning restore 0618
#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
    SslProtocols.Tls,
    SslProtocols.Tls11,
#pragma warning restore SYSLIB0039
    SslProtocols.Tls12,
    SslProtocols.Tls13
];

?

// if we have background task eat any exceptions
_frameTask?.ContinueWith(t => {
_ = t.Exception;
_buffer.ReturnBuffer();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What prevents the buffer from being returned twice?

// This is optimization to consume and report alters insteads of throwing IO excceoption as
// the peer would typically close connection afterwards.
// We don't want to thorw here, taht would be done later if needed
Task.WaitAny(new Task[] { handshakeTask }, cancellationToken);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this always synchronous regardless of whether isSync?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the cancellation token here will cancel the waiting, but it won't affect handshakeTask... that's ok? Will handshakeTask be canceled separately in response to cancellationToken?

if (isSync)
{
Task[] tasks = new Task[] { handshakeTask, _frameTask };
int index = Task.WaitAny(tasks, cancellationToken);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is synchronous waiting being canceled with the token but asynchronous waiting isn't?

// We grab reference to prevent disposal while handshake is pending.
this.DangerousAddRef(ref add);
ObjectDisposedException.ThrowIf(_disposed, this);
Interop.AppleCrypto.NwStartHandshake(SslContext, GCHandle.ToIntPtr(gcHandle));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like you might need some GC.KeepAlives in various places. This gcHandle is a weak handle, which means it's not keeping this alive, which means if nothing after the call to this instance method is guaranteed to be keeping the instance alive, it's possible it could be collected at a bad time around here.

Copy link
Contributor

Draft Pull Request was automatically closed for 30 days of inactivity. Please let us know if you'd like to reopen it.

@wfurt wfurt reopened this Aug 19, 2024
Copy link
Contributor

Draft Pull Request was automatically closed for 30 days of inactivity. Please let us know if you'd like to reopen it.

@filipnavara filipnavara reopened this Sep 18, 2024
Copy link
Contributor

Draft Pull Request was automatically closed for 30 days of inactivity. Please let us know if you'd like to reopen it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants