support server ALPN on macOS (#79434)
authorTomas Weinfurt <tweinfurt@yahoo.com>
Tue, 13 Dec 2022 18:25:16 +0000 (10:25 -0800)
committerGitHub <noreply@github.com>
Tue, 13 Dec 2022 18:25:16 +0000 (10:25 -0800)
* support server ALPN on macOS

* reset status

17 files changed:
src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs
src/libraries/Common/src/System/Net/SecurityStatusPal.cs
src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs
src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs
src/libraries/System.Net.Security/src/System/Net/Security/SslConnectionInfo.OSX.cs
src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs
src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs
src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs
src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs
src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs
src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs
src/libraries/System.Net.Security/src/System/Net/Security/TlsFrameHelper.cs
src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAlpnTests.cs
src/native/libs/System.Security.Cryptography.Native.Apple/coretls_structs.h [new file with mode: 0644]
src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c
src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.c
src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.h

index 0433080..e58c41c 100644 (file)
@@ -47,6 +47,7 @@ internal static partial class Interop
             ServerAuthCompleted,
             ClientAuthCompleted,
             ClientCertRequested,
+            ClientHelloReceived,
         }
 
         internal enum PAL_TlsIo
@@ -101,6 +102,12 @@ internal static partial class Interop
             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,
             int setBreak,
@@ -121,6 +128,9 @@ internal static partial class Interop
         [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 partial class Interop
             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 partial class Interop
             }
         }
 
+        internal static unsafe bool SslCtxSetAlpnProtocol(SafeSslHandle ctx, SslApplicationProtocol protocol)
+        {
+            int osStatus;
+
+            fixed (void* ptr = &MemoryMarshal.GetReference(protocol.Protocol.Span))
+            {
+                int result = SSLSetALPNProtocol(ctx, ptr, protocol.Protocol.Length, out osStatus);
+                if (result != 1)
+                {
+                    throw CreateExceptionForOSStatus(osStatus);
+                }
+            }
+
+            return osStatus == 0;
+        }
+
         internal static byte[]? SslGetAlpnSelected(SafeSslHandle ssl)
         {
             SafeCFDataHandle protocol;
index f3847b8..f4d79d5 100644 (file)
@@ -33,6 +33,7 @@ namespace System.Net
         CredentialsNeeded,
         Renegotiate,
         TryAgain,
+        HandshakeStarted,
 
         // Errors
         OutOfMemory,
index 245ff9c..33cec4f 100644 (file)
@@ -256,7 +256,6 @@ namespace System
 
         // Windows - Schannel supports alpn from win8.1/2012 R2 and higher.
         // Linux - OpenSsl supports alpn from openssl 1.0.2 and higher.
-        // OSX - SecureTransport doesn't expose alpn APIs. TODO https://github.com/dotnet/runtime/issues/27727
         // Android - Platform supports alpn from API level 29 and higher
         private static readonly Lazy<bool> s_supportsAlpn = new Lazy<bool>(GetAlpnSupport);
         private static bool GetAlpnSupport()
@@ -281,6 +280,11 @@ namespace System
                 return Interop.AndroidCrypto.SSLSupportsApplicationProtocolsConfiguration();
             }
 
+            if (IsOSX)
+            {
+                return true;
+            }
+
             return false;
         }
 
index fd86353..34b8b95 100644 (file)
@@ -24,6 +24,8 @@ namespace System.Net
         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 @@ namespace System.Net
 
                 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 @@ namespace System.Net
 
             if (sslAuthenticationOptions.IsServer)
             {
+                IsServer = true;
+
                 if (sslAuthenticationOptions.RemoteCertRequired)
                 {
                     Interop.AppleCrypto.SslSetAcceptClientCert(_sslContext);
index ba883b7..0654658 100644 (file)
@@ -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 @@ namespace System.Net.Security
 
             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);
         }
index 38b097e..ca937f2 100644 (file)
@@ -381,6 +381,12 @@ namespace System.Net.Security
                         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))
index 6b4a445..3498550 100644 (file)
@@ -816,6 +816,24 @@ namespace System.Net.Security
                                       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<byte>.Empty,
+                                        ref result,
+                                        _sslAuthenticationOptions);
+                            }
+                        }
                     }
                     else
                     {
index 32aaa30..fe3a22c 100644 (file)
@@ -26,6 +26,15 @@ namespace System.Net.Security
         {
         }
 
+        public static SecurityStatusPal SelectApplicationProtocol(
+            SafeFreeCredentials? credentialsHandle,
+            SafeDeleteSslContext? context,
+            SslAuthenticationOptions sslAuthenticationOptions,
+            ReadOnlySpan<byte> clientProtocols)
+        {
+            throw new PlatformNotSupportedException(nameof(SelectApplicationProtocol));
+        }
+
         public static SecurityStatusPal AcceptSecurityContext(
             ref SafeFreeCredentials credential,
             ref SafeDeleteSslContext? context,
index 80d7590..dc99067 100644 (file)
@@ -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 @@ namespace System.Net.Security
         {
         }
 
+        public static SecurityStatusPal SelectApplicationProtocol(
+            SafeFreeCredentials? _,
+            SafeDeleteSslContext context,
+            SslAuthenticationOptions sslAuthenticationOptions,
+            ReadOnlySpan<byte> 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<byte> protocols = clientProtocols;
+
+                while (protocols.Length > 0)
+                {
+                    byte length = protocols[0];
+                    if (protocols.Length < length + 1)
+                    {
+                        break;
+                    }
+                    ReadOnlySpan<byte> protocol = protocols.Slice(1, length);
+                    if (protocol.SequenceCompareTo<byte>(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 @@ namespace System.Net.Security
             SafeDeleteSslContext securityContext,
             ref SslConnectionInfo connectionInfo)
         {
-            connectionInfo.UpdateSslConnectionInfo(securityContext.SslContext);
+            connectionInfo.UpdateSslConnectionInfo(securityContext);
         }
 
         private static SecurityStatusPal HandshakeInternal(
@@ -243,6 +289,7 @@ namespace System.Net.Security
 
                 SafeSslHandle sslHandle = sslContext!.SslContext;
                 SecurityStatusPal status = PerformHandshake(sslHandle);
+
                 if (status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded && clientCertificateSelectionCallback != null)
                 {
                     X509Certificate2? clientCertificate = clientCertificateSelectionCallback(out bool _);
@@ -288,6 +335,8 @@ namespace System.Net.Security
                         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,
index 35da450..383168b 100644 (file)
@@ -24,6 +24,15 @@ namespace System.Net.Security
         {
         }
 
+        public static SecurityStatusPal SelectApplicationProtocol(
+            SafeFreeCredentials? credentialsHandle,
+            SafeDeleteSslContext? context,
+            SslAuthenticationOptions sslAuthenticationOptions,
+            ReadOnlySpan<byte> clientProtocols)
+        {
+            throw new PlatformNotSupportedException(nameof(SelectApplicationProtocol));
+        }
+
 #pragma warning disable IDE0060
         public static SecurityStatusPal AcceptSecurityContext(
             ref SafeFreeCredentials? credential,
index 035a19d..2a1a3d3 100644 (file)
@@ -79,6 +79,15 @@ namespace System.Net.Security
             }
         }
 
+        public static SecurityStatusPal SelectApplicationProtocol(
+            SafeFreeCredentials? credentialsHandle,
+            SafeDeleteSslContext? context,
+            SslAuthenticationOptions sslAuthenticationOptions,
+            ReadOnlySpan<byte> clientProtocols)
+        {
+            throw new PlatformNotSupportedException(nameof(SelectApplicationProtocol));
+        }
+
         public static unsafe SecurityStatusPal AcceptSecurityContext(
             ref SafeFreeCredentials? credentialsHandle,
             ref SafeDeleteSslContext? context,
index 9fccacf..09953bf 100644 (file)
@@ -103,6 +103,7 @@ namespace System.Net.Security
             ServerName = 0x1,
             ApplicationProtocol = 0x2,
             Versions = 0x4,
+            RawApplicationProtocol = 0x8,
         }
 
         [Flags]
@@ -122,6 +123,7 @@ namespace System.Net.Security
             public string TargetName;
             public ApplicationProtocolInfo ApplicationProtocols;
             public TlsAlertDescription AlertDescription;
+            public byte[]? RawApplicationProtocols;
 
             public override string ToString()
             {
@@ -524,7 +526,7 @@ namespace System.Net.Security
                     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 @@ namespace System.Net.Security
                     }
 
                     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);
index 8b0e9ab..a488246 100644 (file)
@@ -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 @@ namespace System.Net.Security.Tests
 
         public static IEnumerable<object[]> Alpn_TestData()
         {
-            if (OperatingSystem.IsMacOS())
-            {
-                yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List<SslApplicationProtocol> { SslApplicationProtocol.Http2 }, null };
-                yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11 }, new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, null };
-                yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, null };
-                yield return new object[] { null, new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, null };
-                yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List<SslApplicationProtocol>(), null };
-                yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, null, null };
-                yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11 }, new List<SslApplicationProtocol> { SslApplicationProtocol.Http2 }, null };
-                yield return new object[] { new List<SslApplicationProtocol>(), new List<SslApplicationProtocol>(), null };
-                yield return new object[] { null, new List<SslApplicationProtocol>(), null };
-                yield return new object[] { new List<SslApplicationProtocol>(), null, null };
-                yield return new object[] { null, null, null };
-            }
-            else
-            {
-                yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List<SslApplicationProtocol> { SslApplicationProtocol.Http2 }, BackendSupportsAlpn ? SslApplicationProtocol.Http2 : default };
-                yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11 }, new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, BackendSupportsAlpn ? SslApplicationProtocol.Http11 : default };
-                yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, BackendSupportsAlpn ? SslApplicationProtocol.Http11 : default };
-                yield return new object[] { null, new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, default(SslApplicationProtocol) };
-                yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List<SslApplicationProtocol>(), default(SslApplicationProtocol) };
-                yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, null, default(SslApplicationProtocol) };
-                yield return new object[] { new List<SslApplicationProtocol>(), new List<SslApplicationProtocol>(), default(SslApplicationProtocol) };
-                yield return new object[] { null, new List<SslApplicationProtocol>(), default(SslApplicationProtocol) };
-                yield return new object[] { new List<SslApplicationProtocol>(), null, default(SslApplicationProtocol) };
-                yield return new object[] { null, null, default(SslApplicationProtocol) };
-            }
+            yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List<SslApplicationProtocol> { SslApplicationProtocol.Http2 }, BackendSupportsAlpn ? SslApplicationProtocol.Http2 : default };
+            yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11 }, new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, BackendSupportsAlpn ? SslApplicationProtocol.Http11 : default };
+            yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, BackendSupportsAlpn ? SslApplicationProtocol.Http11 : default };
+            yield return new object[] { null, new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, default(SslApplicationProtocol) };
+            yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, new List<SslApplicationProtocol>(), default(SslApplicationProtocol) };
+            yield return new object[] { new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }, null, default(SslApplicationProtocol) };
+            yield return new object[] { new List<SslApplicationProtocol>(), new List<SslApplicationProtocol>(), default(SslApplicationProtocol) };
+            yield return new object[] { null, new List<SslApplicationProtocol>(), default(SslApplicationProtocol) };
+            yield return new object[] { new List<SslApplicationProtocol>(), 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 (file)
index 0000000..b0e8a9c
--- /dev/null
@@ -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;
+};
+
index 08fdb43..9f91b6d 100644 (file)
@@ -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)
index 3f43c5c..4e91669 100644 (file)
@@ -4,6 +4,8 @@
 #include "pal_ssl.h"
 #include <dlfcn.h>
 
+#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;
     }
index 312dacb..14b8d79 100644 (file)
@@ -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;
 
@@ -124,6 +125,17 @@ 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.
 
 certRefs should be an array of [ SecIdentityRef, SecCertificateRef* ], the 0 element being the
@@ -158,6 +170,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.
 */
 PALEXPORT int32_t AppleCryptoNative_SslGetAlpnSelected(SSLContextRef sslContext, CFDataRef *protocol);