Skip to content

Commit

Permalink
Add alpn support on Linux.
Browse files Browse the repository at this point in the history
  • Loading branch information
Lakshmi Priya Sekar committed Aug 29, 2017
1 parent 0758534 commit 76548cc
Show file tree
Hide file tree
Showing 17 changed files with 330 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal static partial class Interop
internal static partial class OpenSsl
{
private static Ssl.SslCtxSetVerifyCallback s_verifyClientCertificate = VerifyClientCertificate;
private static Ssl.SslCtxSetAplnCallback s_alpnServerCallback = AplnServerSelectCallback;

#region internal methods

Expand All @@ -46,7 +47,7 @@ internal static SafeChannelBindingHandle QueryChannelBinding(SafeSslHandle conte
return bindingHandle;
}

internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX509Handle certHandle, SafeEvpPKeyHandle certKeyHandle, EncryptionPolicy policy, bool isServer, bool remoteCertRequired)
internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX509Handle certHandle, SafeEvpPKeyHandle certKeyHandle, EncryptionPolicy policy, bool isServer, bool remoteCertRequired, SslAuthenticationOptions authOptions)
{
SafeSslHandle context = null;

Expand Down Expand Up @@ -97,6 +98,23 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50
UpdateCAListFromRootStore(innerContext);
}

if (authOptions.ApplicationProtocols != null)
{
if (isServer)
{
byte[] protos = Ssl.AlpnStringListToByteArray(authOptions.ApplicationProtocols);
authOptions._alpnProtocolsHandle = GCHandle.Alloc(protos);
Interop.Ssl.SslCtxSetAplnSelectCb(innerContext, s_alpnServerCallback, GCHandle.ToIntPtr(authOptions._alpnProtocolsHandle));
}
else
{
if (Interop.Ssl.SslCtxSetAplnProtos(innerContext, authOptions.ApplicationProtocols) != 0)
{
throw CreateSslException(SR.net_alpn_notsupported);
}
}
}

context = SafeSslHandle.Create(innerContext, isServer);
Debug.Assert(context != null, "Expected non-null return value from SafeSslHandle.Create");
if (context.IsInvalid)
Expand Down Expand Up @@ -320,6 +338,18 @@ private static int VerifyClientCertificate(int preverify_ok, IntPtr x509_ctx_ptr
return OpenSslSuccess;
}

private static unsafe int AplnServerSelectCallback(IntPtr ssl, out IntPtr outp, out byte outlen, IntPtr inp, uint inlen, IntPtr arg)
{
GCHandle protocols = GCHandle.FromIntPtr(arg);
byte[] server = (byte[])protocols.Target;

This comment has been minimized.

Copy link
@Drawaes

Drawaes Aug 29, 2017

There should either be a check here that GCHandle.IsAllocated after you rehydrate and that protocols.Target is a valid value or at minimum a debug.assert. If for whatever reason it is null you will throw a null ref exception from within a native callback which is never nice. Ideally

GCHandle protocols = GCHandle.FromIntPtr(arg);
if(protocols.IsAllocated && protocols.Target as byte[] server)
{
    // Do your lookup logic and return
 
}
return [-1,01]; // depending on what an error is for this call
fixed (byte* sp = server)
{
return Interop.Ssl.SslSelectNextProto(out outp, out outlen, (IntPtr)sp, (uint)server.Length, inp, inlen) == Interop.Ssl.OPENSSL_NPN_NEGOTIATED ?
Interop.Ssl.SSL_TLSEXT_ERR_OK : Interop.Ssl.SSL_TLSEXT_ERR_NOACK;
}
}

private static void UpdateCAListFromRootStore(SafeSslContextHandle context)
{
using (SafeX509NameStackHandle nameStack = Crypto.NewX509NameStack())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ internal static partial class Interop
{
internal static partial class Ssl
{
internal const int OPENSSL_NPN_NEGOTIATED = 1;
internal const int SSL_TLSEXT_ERR_OK = 0;
internal const int SSL_TLSEXT_ERR_NOACK = 3;

internal delegate int SslCtxSetVerifyCallback(int preverify_ok, IntPtr x509_ctx);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EnsureLibSslInitialized")]
Expand Down Expand Up @@ -44,6 +48,21 @@ internal static partial class Ssl
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslGetVersion")]
private static extern IntPtr SslGetVersion(SafeSslHandle ssl);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSelectNextProto")]
internal static extern int SslSelectNextProto(out IntPtr outp, out byte outlen, IntPtr server, uint serverlen, IntPtr client, uint clientlen);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslGet0AlpnSelected")]
internal static extern void SslGetAlpnSelected(SafeSslHandle ssl, out IntPtr protocol, out int len);

internal static unsafe string SslGetAlpnSelected(SafeSslHandle ssl)
{
IntPtr protocol;
int len;
SslGetAlpnSelected(ssl, out protocol, out len);

return len == 0 ? null : Marshal.PtrToStringAnsi(protocol, len);
}

internal static string GetProtocolVersion(SafeSslHandle ssl)
{
return Marshal.PtrToStringAnsi(SslGetVersion(ssl));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;

internal static partial class Interop
Expand All @@ -12,6 +14,7 @@ internal static partial class Ssl
{
internal delegate int AppVerifyCallback(IntPtr storeCtx, IntPtr arg);
internal delegate int ClientCertCallback(IntPtr ssl, out IntPtr x509, out IntPtr pkey);
internal delegate int SslCtxSetAplnCallback(IntPtr ssl, out IntPtr outp, out byte outlen, IntPtr inp, uint inlen, IntPtr arg);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxCreate")]
internal static extern SafeSslContextHandle SslCtxCreate(IntPtr method);
Expand All @@ -24,6 +27,45 @@ internal static partial class Ssl

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetClientCertCallback")]
internal static extern void SslCtxSetClientCertCallback(IntPtr ctx, ClientCertCallback callback);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetAlpnProtos")]
internal static extern int SslCtxSetAlpnProtos(SafeSslContextHandle ctx, IntPtr protos, int len);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetAplnSelectCb")]
internal static unsafe extern void SslCtxSetAplnSelectCb(SafeSslContextHandle ctx, SslCtxSetAplnCallback callback, IntPtr arg);

internal static unsafe int SslCtxSetAplnProtos(SafeSslContextHandle ctx, IList<string> protocols)
{
byte[] buffer = AlpnStringListToByteArray(protocols);
fixed (byte* b = buffer)
{
return SslCtxSetAlpnProtos(ctx, (IntPtr)b, buffer.Length);
}
}

internal static byte[] AlpnStringListToByteArray(IList<string> protocols)
{
int protocolSize = 0;
foreach (string protocol in protocols)
{
if (string.IsNullOrEmpty(protocol) || protocol.Length > byte.MaxValue)
{
throw new ArgumentException(SR.net_ssl_app_protocols_invalid, nameof(protocols));
}

protocolSize += protocol.Length + 1;
}

byte[] buffer = new byte[protocolSize];
var offset = 0;
foreach (string protocol in protocols)
{
buffer[offset++] = (byte)(protocol.Length);
offset += Encoding.ASCII.GetBytes(protocol, 0, protocol.Length, buffer, offset);

This comment has been minimized.

Copy link
@Tratcher

Tratcher Aug 29, 2017

UTF-8? The spec just says opaque bytes.

This comment has been minimized.

Copy link
@Priya91

Priya91 Aug 29, 2017

Owner

I didn't understand the opaque bytes correctly. Any idea what that means?

}

return buffer;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.Win32.SafeHandles;

using System.Diagnostics;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Authentication.ExtendedProtection;
Expand All @@ -25,7 +26,7 @@ public SafeSslHandle SslContext
}
}

public SafeDeleteSslContext(SafeFreeSslCredentials credential, bool isServer, bool remoteCertRequired)
public SafeDeleteSslContext(SafeFreeSslCredentials credential, bool isServer, bool remoteCertRequired, SslAuthenticationOptions sslAuthenticationOptions)
: base(credential)
{
Debug.Assert((null != credential) && !credential.IsInvalid, "Invalid credential used in SafeDeleteSslContext");
Expand All @@ -38,7 +39,8 @@ public SafeDeleteSslContext(SafeFreeSslCredentials credential, bool isServer, bo
credential.CertKeyHandle,
credential.Policy,
isServer,
remoteCertRequired);
remoteCertRequired,
sslAuthenticationOptions);
}
catch(Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ int EC_POINT_set_affine_coordinates_GF2m(const EC_GROUP *group, EC_POINT *p,
PER_FUNCTION_BLOCK(SSL_CTX_ctrl, true) \
PER_FUNCTION_BLOCK(SSL_CTX_free, true) \
PER_FUNCTION_BLOCK(SSL_CTX_new, true) \
PER_FUNCTION_BLOCK(SSL_CTX_set_alpn_protos, true) \
PER_FUNCTION_BLOCK(SSL_CTX_set_alpn_select_cb, true) \
PER_FUNCTION_BLOCK(SSL_CTX_set_cert_verify_callback, true) \
PER_FUNCTION_BLOCK(SSL_CTX_set_cipher_list, true) \
PER_FUNCTION_BLOCK(SSL_CTX_set_client_CA_list, true) \
Expand All @@ -270,11 +272,13 @@ int EC_POINT_set_affine_coordinates_GF2m(const EC_GROUP *group, EC_POINT *p,
PER_FUNCTION_BLOCK(SSL_get_peer_finished, true) \
PER_FUNCTION_BLOCK(SSL_get_SSL_CTX, true) \
PER_FUNCTION_BLOCK(SSL_get_version, true) \
PER_FUNCTION_BLOCK(SSL_get0_alpn_selected, true) \
PER_FUNCTION_BLOCK(SSL_library_init, true) \
PER_FUNCTION_BLOCK(SSL_load_error_strings, true) \
PER_FUNCTION_BLOCK(SSL_new, true) \
PER_FUNCTION_BLOCK(SSL_read, true) \
PER_FUNCTION_BLOCK(SSL_renegotiate_pending, true) \
PER_FUNCTION_BLOCK(SSL_select_next_proto, true) \
PER_FUNCTION_BLOCK(SSL_set_accept_state, true) \
PER_FUNCTION_BLOCK(SSL_set_bio, true) \
PER_FUNCTION_BLOCK(SSL_set_connect_state, true) \
Expand Down Expand Up @@ -542,6 +546,8 @@ FOR_ALL_OPENSSL_FUNCTIONS
#define SSL_CTX_ctrl SSL_CTX_ctrl_ptr
#define SSL_CTX_free SSL_CTX_free_ptr
#define SSL_CTX_new SSL_CTX_new_ptr
#define SSL_CTX_set_alpn_protos SSL_CTX_set_alpn_protos_ptr
#define SSL_CTX_set_alpn_select_cb SSL_CTX_set_alpn_select_cb_ptr
#define SSL_CTX_set_cert_verify_callback SSL_CTX_set_cert_verify_callback_ptr
#define SSL_CTX_set_cipher_list SSL_CTX_set_cipher_list_ptr
#define SSL_CTX_set_client_CA_list SSL_CTX_set_client_CA_list_ptr
Expand All @@ -561,11 +567,13 @@ FOR_ALL_OPENSSL_FUNCTIONS
#define SSL_get_peer_finished SSL_get_peer_finished_ptr
#define SSL_get_SSL_CTX SSL_get_SSL_CTX_ptr
#define SSL_get_version SSL_get_version_ptr
#define SSL_get0_alpn_selected SSL_get0_alpn_selected_ptr
#define SSL_library_init SSL_library_init_ptr
#define SSL_load_error_strings SSL_load_error_strings_ptr
#define SSL_new SSL_new_ptr
#define SSL_read SSL_read_ptr
#define SSL_renegotiate_pending SSL_renegotiate_pending_ptr
#define SSL_select_next_proto SSL_select_next_proto_ptr
#define SSL_set_accept_state SSL_set_accept_state_ptr
#define SSL_set_bio SSL_set_bio_ptr
#define SSL_set_connect_state SSL_set_connect_state_ptr
Expand Down
20 changes: 20 additions & 0 deletions src/Native/Unix/System.Security.Cryptography.Native/pal_ssl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -524,3 +524,23 @@ extern "C" int32_t CryptoNative_SslAddExtraChainCert(SSL* ssl, X509* x509)

return 0;
}

extern "C" int32_t CryptoNative_SslSelectNextProto(unsigned char** out, unsigned char* outlen, const unsigned char* server, unsigned int server_len, const unsigned char* client, unsigned int client_len)
{
return SSL_select_next_proto(out, outlen, server, server_len, client, client_len);
}

extern "C" void CryptoNative_SslCtxSetAplnSelectCb(SSL_CTX* ctx, SslCtxSetAplnCallback cb, void* arg)
{
SSL_CTX_set_alpn_select_cb(ctx, cb, arg);
}

extern "C" int32_t CryptoNative_SslCtxSetAlpnProtos(SSL_CTX* ctx, const unsigned char* protos, unsigned protos_len)
{
return SSL_CTX_set_alpn_protos(ctx, protos, protos_len);
}

extern "C" void CryptoNative_SslGet0AlpnSelected(SSL* ssl, const unsigned char** protocol, unsigned int* len)
{
SSL_get0_alpn_selected(ssl, protocol, len);
}
30 changes: 30 additions & 0 deletions src/Native/Unix/System.Security.Cryptography.Native/pal_ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ typedef int32_t (*SslCtxSetCertVerifyCallbackCallback)(X509_STORE_CTX*, void* ar

// the function pointer definition for the callback used in SslCtxSetClientCertCallback
typedef int32_t (*SslClientCertCallback)(SSL* ssl, X509** x509, EVP_PKEY** pkey);

// the function pointer definition for the callback used in SslCtxSetAplnSelectCb
typedef int32_t (*SslCtxSetAplnCallback)(SSL* ssl,
const unsigned char** out,
unsigned char* outlen,
const unsigned char* in,
unsigned int inlen,
void* arg);
/*
Ensures that libssl is correctly initialized and ready to use.
*/
Expand Down Expand Up @@ -365,3 +373,25 @@ libssl frees the x509 object.
Returns 1 if success and 0 in case of failure
*/
extern "C" int32_t CryptoNative_SslAddExtraChainCert(SSL* ssl, X509* x509);

/*
Shims the SSL_select_next_proto method.
Returns 1 on success, 0 on failure.
*/
extern "C" int32_t CryptoNative_SslSelectNextProto(unsigned char** out, unsigned char* outlen, const unsigned char* server, unsigned int server_len, const unsigned char* client, unsigned int client_len);

/*
Shims the ssl_ctx_set_alpn_select_cb method.
*/
extern "C" void CryptoNative_SslCtxSetAplnSelectCb(SSL_CTX* ctx, SslCtxSetAplnCallback cb, void *arg);

/*
Shims the ssl_ctx_set_alpn_protos method.
Returns 0 on success, non-zero on failure.
*/
extern "C" int32_t CryptoNative_SslCtxSetAlpnProtos(SSL_CTX* ctx, const unsigned char *protos, unsigned protos_len);

/*
Shims the ssl_get0_alpn_selected method.
*/
extern "C" void CryptoNative_SslGet0AlpnSelected(SSL* ssl, const unsigned char** protocol, unsigned int* len);
3 changes: 3 additions & 0 deletions src/System.Net.Http/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,7 @@
<data name="net_http_ssl_connection_failed" xml:space="preserve">
<value>The SSL connection could not be established, see inner exception.</value>
</data>
<data name="net_ssl_app_protocols_invalid" xml:space="preserve">
<value>The application protocol list is invalid.</value>
</data>
</root>
11 changes: 11 additions & 0 deletions src/System.Net.Security/ref/System.Net.Security.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Changes to this file must follow the http://aka.ms/api-review process.
// ------------------------------------------------------------------------------

using System.Collections.Generic;

namespace System.Net.Security
{
Expand Down Expand Up @@ -94,13 +95,23 @@ public enum ProtectionLevel
EncryptAndSign = 2
}
public delegate bool RemoteCertificateValidationCallback(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors);
public partial class SslAuthenticationOptions
{
public IList<string> ApplicationProtocols { get { throw null; } set { } }
public Dictionary<string, System.Security.Cryptography.X509Certificates.X509Certificate2> ServerCertificates { get { throw null; } set { } }
public RemoteCertificateValidationCallback UserCertificateValidationCallback { get { throw null; } set { } }
public LocalCertificateSelectionCallback UserCertificateSelectionCallback { get { throw null; } set { } }
public EncryptionPolicy EncryptionOption { get { throw null; } set { } }
}
public partial class SslStream : AuthenticatedStream
{
public SslStream(System.IO.Stream innerStream) : base(innerStream, false) { }
public SslStream(System.IO.Stream innerStream, bool leaveInnerStreamOpen, SslAuthenticationOptions sslauthenticationOptions) : base(innerStream, leaveInnerStreamOpen) { }
public SslStream(System.IO.Stream innerStream, bool leaveInnerStreamOpen) : base(innerStream, leaveInnerStreamOpen) { }
public SslStream(System.IO.Stream innerStream, bool leaveInnerStreamOpen, System.Net.Security.RemoteCertificateValidationCallback userCertificateValidationCallback) : base(innerStream, leaveInnerStreamOpen) { }
public SslStream(System.IO.Stream innerStream, bool leaveInnerStreamOpen, System.Net.Security.RemoteCertificateValidationCallback userCertificateValidationCallback, System.Net.Security.LocalCertificateSelectionCallback userCertificateSelectionCallback) : base(innerStream, leaveInnerStreamOpen) { }
public SslStream(System.IO.Stream innerStream, bool leaveInnerStreamOpen, System.Net.Security.RemoteCertificateValidationCallback userCertificateValidationCallback, System.Net.Security.LocalCertificateSelectionCallback userCertificateSelectionCallback, System.Net.Security.EncryptionPolicy encryptionPolicy) : base(innerStream, leaveInnerStreamOpen) { }
public string NegotiatedApplicationProtocol { get { throw null; } }
public override bool CanRead { get { throw null; } }
public override bool CanSeek { get { throw null; } }
public override bool CanTimeout { get { throw null; } }
Expand Down
1 change: 1 addition & 0 deletions src/System.Net.Security/ref/System.Net.Security.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\System.Collections.NonGeneric\ref\System.Collections.NonGeneric.csproj" />
<ProjectReference Include="..\..\System.Collections\ref\System.Collections.csproj" />
<ProjectReference Include="..\..\System.IO\ref\System.IO.csproj" />
<ProjectReference Include="..\..\System.Net.Primitives\ref\System.Net.Primitives.csproj" />
<ProjectReference Include="..\..\System.Runtime\ref\System.Runtime.csproj" />
Expand Down
6 changes: 6 additions & 0 deletions src/System.Net.Security/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -361,4 +361,10 @@
<data name="net_encryptionpolicy_notsupported" xml:space="preserve">
<value>The '{0}' encryption policy is not supported on this platform.</value>
</data>
<data name="net_alpn_notsupported" xml:space="preserve">
<value>ALPN is not supported on the current platform.</value>
</data>
<data name="net_ssl_app_protocols_invalid" xml:space="preserve">
<value>The application protocol list is invalid.</value>
</data>
</root>

0 comments on commit 76548cc

Please sign in to comment.