diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs index 043308097e5cc..e58c41c04e3a5 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs @@ -47,6 +47,7 @@ internal enum PAL_TlsHandshakeState ServerAuthCompleted, ClientAuthCompleted, ClientCertRequested, + ClientHelloReceived, } internal enum PAL_TlsIo @@ -100,6 +101,12 @@ private static partial int AppleCryptoNative_SslSetBreakOnClientAuth( int setBreak, out int pOSStatus); + [LibraryImport(Interop.Libraries.AppleCryptoNative)] + private static partial int AppleCryptoNative_SslSetBreakOnClientHello( + SafeSslHandle sslHandle, + int setBreak, + out int pOSStatus); + [LibraryImport(Interop.Libraries.AppleCryptoNative)] private static partial int AppleCryptoNative_SslSetBreakOnCertRequested( SafeSslHandle sslHandle, @@ -121,6 +128,9 @@ private static partial int AppleCryptoNative_SslSetTargetName( [LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SSLSetALPNProtocols")] internal static partial int SSLSetALPNProtocols(SafeSslHandle ctx, SafeCreateHandle cfProtocolsRefs, out int osStatus); + [LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SSLSetALPNProtocol")] + internal static unsafe partial int SSLSetALPNProtocol(SafeSslHandle ctx, void* protocol, int length, out int osStatus); + [LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SslGetAlpnSelected")] internal static partial int SslGetAlpnSelected(SafeSslHandle ssl, out SafeCFDataHandle protocol); @@ -289,6 +299,25 @@ internal static void SslBreakOnClientAuth(SafeSslHandle sslHandle, bool setBreak throw new SslException(); } + internal static void SslBreakOnClientHello(SafeSslHandle sslHandle, bool setBreak) + { + int osStatus; + int result = AppleCryptoNative_SslSetBreakOnClientHello(sslHandle, setBreak ? 1 : 0, out osStatus); + + if (result == 1) + { + return; + } + + if (result == 0) + { + throw CreateExceptionForOSStatus(osStatus); + } + + Debug.Fail($"AppleCryptoNative_SslSetBreakOnClientHello returned {result}"); + throw new SslException(); + } + internal static void SslBreakOnCertRequested(SafeSslHandle sslHandle, bool setBreak) { int osStatus; @@ -398,6 +427,22 @@ internal static unsafe void SslCtxSetAlpnProtos(SafeSslHandle ctx, List s_supportsAlpn = new Lazy(GetAlpnSupport); private static bool GetAlpnSupport() @@ -281,6 +280,11 @@ private static bool GetAlpnSupport() return Interop.AndroidCrypto.SSLSupportsApplicationProtocolsConfiguration(); } + if (IsOSX) + { + return true; + } + return false; } 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 fd86353ed1fa5..34b8b957b0b73 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 @@ -24,6 +24,8 @@ internal sealed class SafeDeleteSslContext : SafeDeleteContext private ArrayBuffer _outputBuffer = new ArrayBuffer(InitialBufferSize); public SafeSslHandle SslContext => _sslContext; + public SslApplicationProtocol SelectedApplicationProtocol; + public bool IsServer; public SafeDeleteSslContext(SslAuthenticationOptions sslAuthenticationOptions) : base(IntPtr.Zero) @@ -74,11 +76,16 @@ public SafeDeleteSslContext(SslAuthenticationOptions sslAuthenticationOptions) if (sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0) { - // On OSX coretls supports only client side. For server, we will silently ignore the option. - if (!sslAuthenticationOptions.IsServer) + if (sslAuthenticationOptions.IsClient) { + // On macOS coreTls supports only client side. Interop.AppleCrypto.SslCtxSetAlpnProtos(_sslContext, sslAuthenticationOptions.ApplicationProtocols); } + else + { + // For Server, we do the selection in SslStream and we set it later + Interop.AppleCrypto.SslBreakOnClientHello(_sslContext, true); + } } } catch (Exception ex) @@ -102,6 +109,8 @@ public SafeDeleteSslContext(SslAuthenticationOptions sslAuthenticationOptions) if (sslAuthenticationOptions.IsServer) { + IsServer = true; + if (sslAuthenticationOptions.RemoteCertRequired) { Interop.AppleCrypto.SslSetAcceptClientCert(_sslContext); diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslConnectionInfo.OSX.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslConnectionInfo.OSX.cs index ba883b7a16972..0654658320583 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslConnectionInfo.OSX.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslConnectionInfo.OSX.cs @@ -9,8 +9,9 @@ namespace System.Net.Security { internal partial struct SslConnectionInfo { - public void UpdateSslConnectionInfo(SafeSslHandle sslContext) + public void UpdateSslConnectionInfo(SafeDeleteSslContext context) { + SafeSslHandle sslContext = context.SslContext; SslProtocols protocol; TlsCipherSuite cipherSuite; @@ -26,7 +27,32 @@ public void UpdateSslConnectionInfo(SafeSslHandle sslContext) Protocol = (int)protocol; TlsCipherSuite = cipherSuite; - ApplicationProtocol = Interop.AppleCrypto.SslGetAlpnSelected(sslContext); + if (context.IsServer) + { + if (context.SelectedApplicationProtocol.Protocol.Length > 0) + { + if (context.SelectedApplicationProtocol.Equals(SslApplicationProtocol.Http11.Protocol)) + { + ApplicationProtocol = s_http1; + } + else if (context.SelectedApplicationProtocol.Equals(SslApplicationProtocol.Http2.Protocol)) + { + ApplicationProtocol = s_http2; + } + else if (context.SelectedApplicationProtocol.Equals(SslApplicationProtocol.Http3.Protocol)) + { + ApplicationProtocol = s_http3; + } + else + { + ApplicationProtocol = context.SelectedApplicationProtocol.Protocol.ToArray(); + } + } + } + else + { + ApplicationProtocol = Interop.AppleCrypto.SslGetAlpnSelected(sslContext); + } MapCipherSuite(cipherSuite); } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs index 38b097ebd961b..ca937f2475b74 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs @@ -381,6 +381,12 @@ private async ValueTask ReceiveBlobAsync(Cancellation TlsFrameHelper.ProcessingOptions options = NetEventSource.Log.IsEnabled() ? TlsFrameHelper.ProcessingOptions.All : TlsFrameHelper.ProcessingOptions.ServerName; + if (OperatingSystem.IsMacOS() && _sslAuthenticationOptions.IsServer) + { + // macOS cannot process ALPN on server at the momennt. + // We fallback to our own process similar to SNI bellow. + options |= TlsFrameHelper.ProcessingOptions.RawApplicationProtocol; + } // Process SNI from Client Hello message if (!TlsFrameHelper.TryGetFrameInfo(_buffer.EncryptedReadOnlySpan, ref _lastFrame, options)) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs index 18c5a7e824573..64bfed4d38bb7 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs @@ -817,6 +817,24 @@ private SecurityStatusPal GenerateToken(ReadOnlySpan inputBuffer, ref byte inputBuffer, ref result, _sslAuthenticationOptions); + if (status.ErrorCode == SecurityStatusPalErrorCode.HandshakeStarted) + { + status = SslStreamPal.SelectApplicationProtocol( + _credentialsHandle!, + _securityContext!, + _sslAuthenticationOptions, + _lastFrame.RawApplicationProtocols); + + if (status.ErrorCode == SecurityStatusPalErrorCode.OK) + { + status = SslStreamPal.AcceptSecurityContext( + ref _credentialsHandle!, + ref _securityContext, + ReadOnlySpan.Empty, + ref result, + _sslAuthenticationOptions); + } + } } else { diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs index 32aaa30530cf8..fe3a22c02b3b5 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs @@ -26,6 +26,15 @@ public static void VerifyPackageInfo() { } + public static SecurityStatusPal SelectApplicationProtocol( + SafeFreeCredentials? credentialsHandle, + SafeDeleteSslContext? context, + SslAuthenticationOptions sslAuthenticationOptions, + ReadOnlySpan clientProtocols) + { + throw new PlatformNotSupportedException(nameof(SelectApplicationProtocol)); + } + public static SecurityStatusPal AcceptSecurityContext( ref SafeFreeCredentials credential, ref SafeDeleteSslContext? context, diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs index 80d7590ea7d39..dc990676733c5 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs @@ -2,8 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Net.Security; using System.Security.Authentication; using System.Security.Authentication.ExtendedProtection; using System.Security.Cryptography.X509Certificates; @@ -31,6 +33,50 @@ public static void VerifyPackageInfo() { } + public static SecurityStatusPal SelectApplicationProtocol( + SafeFreeCredentials? _, + SafeDeleteSslContext context, + SslAuthenticationOptions sslAuthenticationOptions, + ReadOnlySpan clientProtocols) + { + // Client did not provide ALPN or APLN is not needed + if (clientProtocols.Length == 0 || + sslAuthenticationOptions.ApplicationProtocols == null || sslAuthenticationOptions.ApplicationProtocols.Count == 0) + { + return new SecurityStatusPal(SecurityStatusPalErrorCode.OK); + } + + // We do server side ALPN e.g. walk the intersect in server order + foreach (SslApplicationProtocol applicationProtcol in sslAuthenticationOptions.ApplicationProtocols) + { + ReadOnlySpan protocols = clientProtocols; + + while (protocols.Length > 0) + { + byte length = protocols[0]; + if (protocols.Length < length + 1) + { + break; + } + ReadOnlySpan protocol = protocols.Slice(1, length); + if (protocol.SequenceCompareTo(applicationProtcol.Protocol.Span) == 0) + { + if (Interop.AppleCrypto.SslCtxSetAlpnProtocol(context.SslContext, applicationProtcol)) + { + context.SelectedApplicationProtocol = applicationProtcol; + } + + // We ignore failure and we will move on with ALPN + return new SecurityStatusPal(SecurityStatusPalErrorCode.OK); + } + + protocols = protocols.Slice(protocol.Length + 1); + } + } + + return new SecurityStatusPal(SecurityStatusPalErrorCode.ApplicationProtocolMismatch); + } + #pragma warning disable IDE0060 public static SecurityStatusPal AcceptSecurityContext( ref SafeFreeCredentials credential, @@ -216,7 +262,7 @@ public static void QueryContextConnectionInfo( SafeDeleteSslContext securityContext, ref SslConnectionInfo connectionInfo) { - connectionInfo.UpdateSslConnectionInfo(securityContext.SslContext); + connectionInfo.UpdateSslConnectionInfo(securityContext); } private static SecurityStatusPal HandshakeInternal( @@ -243,6 +289,7 @@ private static SecurityStatusPal HandshakeInternal( SafeSslHandle sslHandle = sslContext!.SslContext; SecurityStatusPal status = PerformHandshake(sslHandle); + if (status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded && clientCertificateSelectionCallback != null) { X509Certificate2? clientCertificate = clientCertificateSelectionCallback(out bool _); @@ -288,6 +335,8 @@ private static SecurityStatusPal PerformHandshake(SafeSslHandle sslHandle) break; case PAL_TlsHandshakeState.ClientCertRequested: return new SecurityStatusPal(SecurityStatusPalErrorCode.CredentialsNeeded); + case PAL_TlsHandshakeState.ClientHelloReceived: + return new SecurityStatusPal(SecurityStatusPalErrorCode.HandshakeStarted); default: return new SecurityStatusPal( SecurityStatusPalErrorCode.InternalError, diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs index 35da4502293ae..383168b9b0050 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs @@ -24,6 +24,15 @@ public static void VerifyPackageInfo() { } + public static SecurityStatusPal SelectApplicationProtocol( + SafeFreeCredentials? credentialsHandle, + SafeDeleteSslContext? context, + SslAuthenticationOptions sslAuthenticationOptions, + ReadOnlySpan clientProtocols) + { + throw new PlatformNotSupportedException(nameof(SelectApplicationProtocol)); + } + #pragma warning disable IDE0060 public static SecurityStatusPal AcceptSecurityContext( ref SafeFreeCredentials? credential, diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs index 035a19d02cf3c..2a1a3d3174409 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs @@ -79,6 +79,15 @@ private static unsafe void SetAlpn(ref InputSecurityBuffers inputBuffers, List clientProtocols) + { + throw new PlatformNotSupportedException(nameof(SelectApplicationProtocol)); + } + public static unsafe SecurityStatusPal AcceptSecurityContext( ref SafeFreeCredentials? credentialsHandle, ref SafeDeleteSslContext? context, diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/TlsFrameHelper.cs b/src/libraries/System.Net.Security/src/System/Net/Security/TlsFrameHelper.cs index 9fccacf845f8b..09953bf56f961 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/TlsFrameHelper.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/TlsFrameHelper.cs @@ -103,6 +103,7 @@ public enum ProcessingOptions ServerName = 0x1, ApplicationProtocol = 0x2, Versions = 0x4, + RawApplicationProtocol = 0x8, } [Flags] @@ -122,6 +123,7 @@ public struct TlsFrameInfo public string TargetName; public ApplicationProtocolInfo ApplicationProtocols; public TlsAlertDescription AlertDescription; + public byte[]? RawApplicationProtocols; public override string ToString() { @@ -524,7 +526,7 @@ private static bool TryParseHelloExtensions(ReadOnlySpan extensions, ref T info.SupportedVersions |= versions; } else if (extensionType == ExtensionType.ApplicationProtocols && (options == ProcessingOptions.All || - (options & ProcessingOptions.ApplicationProtocol) == ProcessingOptions.ApplicationProtocol)) + (options.HasFlag(ProcessingOptions.ApplicationProtocol) || options.HasFlag(ProcessingOptions.RawApplicationProtocol)))) { if (!TryGetApplicationProtocolsFromExtension(extensionData, out ApplicationProtocolInfo alpn)) { @@ -532,6 +534,13 @@ private static bool TryParseHelloExtensions(ReadOnlySpan extensions, ref T } info.ApplicationProtocols |= alpn; + + // Process RAW options only if explicitly set since that will allocate.... + if (options.HasFlag(ProcessingOptions.RawApplicationProtocol)) + { + // Skip ALPN extension Length. We have that in span. + info.RawApplicationProtocols = extensionData.Slice(sizeof(short)).ToArray(); + } } callback?.Invoke(ref info, extensionType, extensionData); diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAlpnTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAlpnTests.cs index 8b0e9ab6165ff..a4882469fccc7 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAlpnTests.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAlpnTests.cs @@ -3,13 +3,10 @@ using System.Collections.Generic; using System.IO; -using System.Linq; using System.Net.Sockets; using System.Net.Test.Common; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading; using System.Threading.Tasks; using Xunit; @@ -205,33 +202,16 @@ public async Task SslStream_Http2_Alpn_Success(Uri server) public static IEnumerable Alpn_TestData() { - if (OperatingSystem.IsMacOS()) - { - yield return new object[] { new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List { SslApplicationProtocol.Http2 }, null }; - yield return new object[] { new List { SslApplicationProtocol.Http11 }, new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, null }; - yield return new object[] { new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, null }; - yield return new object[] { null, new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, null }; - yield return new object[] { new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List(), null }; - yield return new object[] { new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, null, null }; - yield return new object[] { new List { SslApplicationProtocol.Http11 }, new List { SslApplicationProtocol.Http2 }, null }; - yield return new object[] { new List(), new List(), null }; - yield return new object[] { null, new List(), null }; - yield return new object[] { new List(), null, null }; - yield return new object[] { null, null, null }; - } - else - { - yield return new object[] { new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List { SslApplicationProtocol.Http2 }, BackendSupportsAlpn ? SslApplicationProtocol.Http2 : default }; - yield return new object[] { new List { SslApplicationProtocol.Http11 }, new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, BackendSupportsAlpn ? SslApplicationProtocol.Http11 : default }; - yield return new object[] { new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, BackendSupportsAlpn ? SslApplicationProtocol.Http11 : default }; - yield return new object[] { null, new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, default(SslApplicationProtocol) }; - yield return new object[] { new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List(), default(SslApplicationProtocol) }; - yield return new object[] { new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, null, default(SslApplicationProtocol) }; - yield return new object[] { new List(), new List(), default(SslApplicationProtocol) }; - yield return new object[] { null, new List(), default(SslApplicationProtocol) }; - yield return new object[] { new List(), null, default(SslApplicationProtocol) }; - yield return new object[] { null, null, default(SslApplicationProtocol) }; - } + yield return new object[] { new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List { SslApplicationProtocol.Http2 }, BackendSupportsAlpn ? SslApplicationProtocol.Http2 : default }; + yield return new object[] { new List { SslApplicationProtocol.Http11 }, new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, BackendSupportsAlpn ? SslApplicationProtocol.Http11 : default }; + yield return new object[] { new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, BackendSupportsAlpn ? SslApplicationProtocol.Http11 : default }; + yield return new object[] { null, new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, default(SslApplicationProtocol) }; + yield return new object[] { new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List(), default(SslApplicationProtocol) }; + yield return new object[] { new List { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, null, default(SslApplicationProtocol) }; + yield return new object[] { new List(), new List(), default(SslApplicationProtocol) }; + yield return new object[] { null, new List(), default(SslApplicationProtocol) }; + yield return new object[] { new List(), null, default(SslApplicationProtocol) }; + yield return new object[] { null, null, default(SslApplicationProtocol) }; } } diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/coretls_structs.h b/src/native/libs/System.Security.Cryptography.Native.Apple/coretls_structs.h new file mode 100644 index 0000000000000..b0e8a9c20b170 --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/coretls_structs.h @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Definitions of structures from coreTLS +// https://github.com/apple-oss-distributions/coreTLS/ + +typedef struct +{ size_t length; + uint8_t *data; +} tls_buffer; + +struct _tls_handshake_s +{ + uint8_t _ignore0[968]; + /* ALPN */ + bool alpn_enabled; /* Client: alpn is enabled */ + bool alpn_announced; /* Client: alpn extension was sent, Server: alpn extension was received */ + bool alpn_confirmed; /* Client: alpn extension was received, Server: alpn extension was sent */ + bool alpn_received; /* Server: alpn message was received */ + tls_buffer alpnOwnData; /* Client: supported protocols sent, Server: selected protocol sent */ + tls_buffer alpnPeerData; /* Client: select protocol received, Server: list of supported protocol received */ +}; + +typedef struct _tls_handshake_s *tls_handshake_t; + +struct SSLContext +{ + uint8_t _ignored0[56]; + tls_handshake_t hdsk; +}; + 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 08fdb438e8849..9f91b6d2488fe 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c @@ -63,6 +63,7 @@ static const Entry s_cryptoAppleNative[] = DllImportEntry(AppleCryptoNative_SslRead) DllImportEntry(AppleCryptoNative_SslSetBreakOnCertRequested) DllImportEntry(AppleCryptoNative_SslSetBreakOnClientAuth) + DllImportEntry(AppleCryptoNative_SslSetBreakOnClientHello) DllImportEntry(AppleCryptoNative_SslSetBreakOnServerAuth) DllImportEntry(AppleCryptoNative_SslSetIoCallbacks) DllImportEntry(AppleCryptoNative_SslWrite) @@ -91,6 +92,7 @@ static const Entry s_cryptoAppleNative[] = DllImportEntry(AppleCryptoNative_SslSetCertificate) DllImportEntry(AppleCryptoNative_SslSetCertificateAuthorities) DllImportEntry(AppleCryptoNative_SslSetTargetName) + DllImportEntry(AppleCryptoNative_SSLSetALPNProtocol) DllImportEntry(AppleCryptoNative_SSLSetALPNProtocols) DllImportEntry(AppleCryptoNative_SslGetAlpnSelected) DllImportEntry(AppleCryptoNative_SslHandshake) diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.c b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.c index 3f43c5c1b782f..4e916693814ae 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.c +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.c @@ -4,6 +4,8 @@ #include "pal_ssl.h" #include +#include "coretls_structs.h" + // 10.13.4 introduced public API but linking would fail on all prior versions. // For that reason we use function pointers instead of direct call. // This can be revisited after we drop support for 10.12 and iOS 10 @@ -172,6 +174,14 @@ int32_t AppleCryptoNative_SslSetBreakOnClientAuth(SSLContextRef sslContext, int3 #pragma clang diagnostic pop } +int32_t AppleCryptoNative_SslSetBreakOnClientHello(SSLContextRef sslContext, int32_t setBreak, int32_t* pOSStatus) +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return SslSetSessionOption(sslContext, kSSLSessionOptionBreakOnClientHello, setBreak, pOSStatus); +#pragma clang diagnostic pop +} + int32_t AppleCryptoNative_SslSetCertificate(SSLContextRef sslContext, CFArrayRef certRefs) { #pragma clang diagnostic push @@ -230,6 +240,58 @@ int32_t AppleCryptoNative_SSLSetALPNProtocols(SSLContextRef sslContext, return *pOSStatus == noErr; } +int32_t AppleCryptoNative_SSLSetALPNProtocol(SSLContextRef sslContext, void* protocol, int length, int32_t* pOSStatus) +{ + if (sslContext == NULL || protocol == NULL || length <= 0 || pOSStatus == NULL) + return -1; + + if (!SSLSetALPNProtocolsPtr) + { + // not available. + *pOSStatus = errSecNotAvailable; + return 1; + } + + CFStringRef value = CFStringCreateWithBytes(NULL, protocol, length, kCFStringEncodingASCII, 0); + if (!value) + { + *pOSStatus = errSecMemoryError; + return -2; + } + + CFArrayRef protocolList = CFArrayCreate(kCFAllocatorDefault, (const void **)&value, 1, &kCFTypeArrayCallBacks); + if (!protocolList) + { + CFRelease(value); + *pOSStatus = errSecMemoryError; + return -2; + } + + + *pOSStatus = (*SSLSetALPNProtocolsPtr)(sslContext, protocolList); + if (*pOSStatus == 0) + { + struct SSLContext* ctx = (struct SSLContext*)sslContext; + tls_handshake_t tls = ctx->hdsk; + + // This is extra consistency check to verify that the ALPN data appeared where we expect them + // before dereferencing sslContext + if (tls != NULL && tls->alpnOwnData.length == length + 1) + { + tls->alpn_announced = 1; + tls->alpn_received = 1 ; + } + else + { + *pOSStatus = errSecNotAvailable; + } + } + + CFRelease(value); + CFRelease(protocolList); + return 1; +} + int32_t AppleCryptoNative_SslGetAlpnSelected(SSLContextRef sslContext, CFDataRef* protocol) { if (sslContext == NULL || protocol == NULL) @@ -285,6 +347,8 @@ PAL_TlsHandshakeState AppleCryptoNative_SslHandshake(SSLContextRef sslContext) return PAL_TlsHandshakeState_ServerAuthCompleted; case errSSLClientCertRequested: return PAL_TlsHandshakeState_ClientCertRequested; + case errSSLClientHelloReceived: + return PAL_TlsHandshakeState_ClientHelloReceived; default: return osStatus; } diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.h b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.h index 312dacb9e7c21..14b8d790970e8 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.h +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.h @@ -16,6 +16,7 @@ enum PAL_TlsHandshakeState_ServerAuthCompleted = 3, PAL_TlsHandshakeState_ClientAuthCompleted = 4, PAL_TlsHandshakeState_ClientCertRequested = 5, + PAL_TlsHandshakeState_ClientHelloReceived = 6, }; typedef int32_t PAL_TlsHandshakeState; @@ -123,6 +124,17 @@ pOSStatus: Receives the value returned by SSLSetSessionOption PALEXPORT int32_t AppleCryptoNative_SslSetBreakOnClientAuth(SSLContextRef sslContext, int32_t setBreak, int32_t* pOSStatus); +/* +Sets the policy of whether or not to break when server receives ClientHello. + +Returns 1 on success, 0 on failure, other values on invalid state. + +Output: +pOSStatus: Receives the value returned by SSLSetSessionOption +*/ +PALEXPORT int32_t +AppleCryptoNative_SslSetBreakOnClientHello(SSLContextRef sslContext, int32_t setBreak, int32_t* pOSStatus); + /* Set the certificate chain for the ServerHello or ClientHello message. @@ -157,6 +169,16 @@ pOSStatus: Receives the value from SSLSetALPNData() */ PALEXPORT int32_t AppleCryptoNative_SSLSetALPNProtocols(SSLContextRef sslContext, CFArrayRef protocols, int32_t* pOSStatus); +/* +Set selected protocol on server side. + +Returns 1 on success, 0 on failure, other values for invalid state. + +Output: +pOSStatus: Receives the value from SSLSetALPNData() +*/ +PALEXPORT int32_t AppleCryptoNative_SSLSetALPNProtocol(SSLContextRef sslContext, void* protocol, int length, int32_t* pOSStatus); + /* Get negotiated protocol value from ServerHello. */