Use ALPN support in SSLStream on .NET Core >= 2.1 (#2179)

- Always add the TlsConnectionFeature when the HttpsConnectionAdapter runs
- Implemented the ITlsApplicationProtocolsFeature on the existing TlsConnectionFeature
- Removed Kestrel.Tls
This commit is contained in:
David Fowler 2017-11-27 09:11:39 -08:00 committed by GitHub
parent 76de77746d
commit ce68427050
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 64 additions and 949 deletions

View File

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27101.0
VisualStudioVersion = 15.0.27110.0
MinimumVisualStudioVersion = 15.0.26730.03
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}"
ProjectSection(SolutionItems) = preProject
@ -102,8 +102,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Protocols.Abstractions", "src\Protocols.Abstractions\Protocols.Abstractions.csproj", "{6956CF5C-3163-4398-8628-4ECA569245B5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Tls", "src\Kestrel.Tls\Kestrel.Tls.csproj", "{924AE57C-1EBA-4A1D-A039-8C100B7507A5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{C2910A13-B2C2-46D8-81D8-7E166F4F5981}"
ProjectSection(SolutionItems) = preProject
build\repo.props = build\repo.props
@ -301,18 +299,6 @@ Global
{6956CF5C-3163-4398-8628-4ECA569245B5}.Release|x64.Build.0 = Release|Any CPU
{6956CF5C-3163-4398-8628-4ECA569245B5}.Release|x86.ActiveCfg = Release|Any CPU
{6956CF5C-3163-4398-8628-4ECA569245B5}.Release|x86.Build.0 = Release|Any CPU
{924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Debug|x64.ActiveCfg = Debug|Any CPU
{924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Debug|x64.Build.0 = Debug|Any CPU
{924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Debug|x86.ActiveCfg = Debug|Any CPU
{924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Debug|x86.Build.0 = Debug|Any CPU
{924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Release|Any CPU.Build.0 = Release|Any CPU
{924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Release|x64.ActiveCfg = Release|Any CPU
{924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Release|x64.Build.0 = Release|Any CPU
{924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Release|x86.ActiveCfg = Release|Any CPU
{924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Release|x86.Build.0 = Release|Any CPU
{74032D79-8EA7-4483-BD82-C38370420FFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{74032D79-8EA7-4483-BD82-C38370420FFF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{74032D79-8EA7-4483-BD82-C38370420FFF}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -369,7 +355,6 @@ Global
{D95A7EC3-48AC-4D03-B2E2-0DA3E13BD3A4} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
{4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
{6956CF5C-3163-4398-8628-4ECA569245B5} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
{924AE57C-1EBA-4A1D-A039-8C100B7507A5} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
{B7B0EA74-528F-46B8-9BC4-909D9A67C194} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
{74032D79-8EA7-4483-BD82-C38370420FFF} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
{9C7B6B5F-088A-436E-834B-6373EA36DEEE} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}

View File

@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>netcoreapp2.1</TargetFrameworks>
<IsPackable>false</IsPackable>
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Kestrel\Kestrel.csproj" />
<ProjectReference Include="..\..\src\Kestrel.Tls\Kestrel.Tls.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -30,7 +30,7 @@ namespace Http2SampleApp
options.Listen(IPAddress.Any, basePort, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseTls("testCert.pfx", "testPassword");
listenOptions.UseHttps("testCert.pfx", "testPassword");
listenOptions.UseConnectionLogging();
});
})

View File

@ -6,6 +6,7 @@ using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Microsoft.AspNetCore.Server.Kestrel.Core;
namespace Microsoft.AspNetCore.Server.Kestrel.Https
{
@ -51,6 +52,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
/// </summary>
public SslProtocols SslProtocols { get; set; }
/// <summary>
/// The protocols enabled on this endpoint.
/// </summary>
/// <remarks>Defaults to HTTP/1.x only.</remarks>
internal HttpProtocols HttpProtocols { get; set; }
/// <summary>
/// Specifies whether the certificate revocation list is checked during authentication.
/// </summary>

View File

@ -2,12 +2,15 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.Extensions.Logging;
@ -64,6 +67,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
{
SslStream sslStream;
bool certificateRequired;
var feature = new TlsConnectionFeature();
context.Features.Set<ITlsConnectionFeature>(feature);
if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate)
{
@ -114,8 +119,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
try
{
#if NETCOREAPP2_1
var sslOptions = new SslServerAuthenticationOptions()
{
ServerCertificate = _serverCertificate,
ClientCertificateRequired = certificateRequired,
EnabledSslProtocols = _options.SslProtocols,
CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
ApplicationProtocols = new List<SslApplicationProtocol>()
};
// This is order sensitive
if ((_options.HttpProtocols & HttpProtocols.Http2) != 0)
{
sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http2);
}
if ((_options.HttpProtocols & HttpProtocols.Http1) != 0)
{
sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http11);
}
await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None);
#else
await sslStream.AuthenticateAsServerAsync(_serverCertificate, certificateRequired,
_options.SslProtocols, _options.CheckCertificateRevocation);
#endif
}
catch (OperationCanceledException)
{
@ -134,11 +163,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
timeoutFeature.CancelTimeout();
}
// Always set the feature even though the cert might be null
context.Features.Set<ITlsConnectionFeature>(new TlsConnectionFeature
#if NETCOREAPP2_1
// Don't allocate in the common case, see https://github.com/dotnet/corefx/issues/25432
if (sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http11)
{
ClientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate)
});
feature.ApplicationProtocol = "http/1.1";
}
else if (sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2)
{
feature.ApplicationProtocol = "h2";
}
else
{
feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.ToString();
}
context.Features.Set<ITlsApplicationProtocolFeature>(feature);
#endif
feature.ClientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate);
return new HttpsAdaptedConnection(sslStream);
}

View File

@ -6,13 +6,16 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
{
internal class TlsConnectionFeature : ITlsConnectionFeature
internal class TlsConnectionFeature : ITlsConnectionFeature, ITlsApplicationProtocolFeature
{
public X509Certificate2 ClientCertificate { get; set; }
public string ApplicationProtocol { get; set; }
public Task<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken)
{
return Task.FromResult(ClientCertificate);

View File

@ -4,7 +4,7 @@
<AssemblyName>Microsoft.AspNetCore.Server.Kestrel.Https</AssemblyName>
<RootNamespace>Microsoft.AspNetCore.Server.Kestrel.Https</RootNamespace>
<Description>HTTPS support for the ASP.NET Core Kestrel cross-platform web server.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>netstandard2.0;netcoreapp2.1</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;kestrel</PackageTags>
<NoWarn>CS1591;$(NoWarn)</NoWarn>

View File

@ -87,6 +87,8 @@ namespace Microsoft.AspNetCore.Hosting
public static ListenOptions UseHttps(this ListenOptions listenOptions, HttpsConnectionAdapterOptions httpsOptions)
{
var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<ILoggerFactory>();
// Set the list of protocols from listen options
httpsOptions.HttpProtocols = listenOptions.Protocols;
listenOptions.ConnectionAdapters.Add(new HttpsConnectionAdapter(httpsOptions, loggerFactory));
return listenOptions;
}

View File

@ -1,68 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.Kestrel.Tls
{
internal class ClosedStream : Stream
{
private static readonly Task<int> ZeroResultTask = Task.FromResult(result: 0);
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length
{
get
{
throw new NotSupportedException();
}
}
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
public override void Flush()
{
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
return 0;
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return ZeroResultTask;
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
}
}

View File

@ -1,23 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Microsoft.AspNetCore.Server.Kestrel.Tls</AssemblyName>
<RootNamespace>Microsoft.AspNetCore.Server.Kestrel.Tls</RootNamespace>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;kestrel</PackageTags>
<NoWarn>CS1591;$(NoWarn)</NoWarn>
<EnableApiCheck>false</EnableApiCheck>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Kestrel.Core\Kestrel.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(MicrosoftAspNetCoreHttpAbstractionsPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -1,30 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Tls;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Hosting
{
public static class ListenOptionsTlsExtensions
{
public static ListenOptions UseTls(this ListenOptions listenOptions, string certificatePath, string password)
{
return listenOptions.UseTls(new TlsConnectionAdapterOptions
{
CertificatePath = certificatePath,
Password = password,
Protocols = listenOptions.Protocols
});
}
public static ListenOptions UseTls(this ListenOptions listenOptions, TlsConnectionAdapterOptions tlsOptions)
{
var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<ILoggerFactory>();
listenOptions.ConnectionAdapters.Add(new TlsConnectionAdapter(tlsOptions, loggerFactory));
return listenOptions;
}
}
}

View File

@ -1,375 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Microsoft.AspNetCore.Server.Kestrel.Tls
{
public static class OpenSsl
{
public const int OPENSSL_NPN_NEGOTIATED = 1;
public const int SSL_TLSEXT_ERR_OK = 0;
public const int SSL_TLSEXT_ERR_NOACK = 3;
public const int SSL_CTRL_CHAIN = 88;
private const int BIO_C_SET_BUF_MEM_EOF_RETURN = 130;
private const int SSL_CTRL_SET_ECDH_AUTO = 94;
public static void SSL_library_init()
{
try
{
// Try OpenSSL 1.0.2
NativeMethods.SSL_library_init();
}
catch (EntryPointNotFoundException)
{
// It's fine, OpenSSL 1.1 doesn't need initialization
}
}
public static void SSL_load_error_strings()
{
try
{
NativeMethods.SSL_load_error_strings();
}
catch (EntryPointNotFoundException)
{
// Also fine, OpenSSL 1.1 doesn't need it.
}
}
public static void OpenSSL_add_all_algorithms()
{
try
{
NativeMethods.OPENSSL_add_all_algorithms_noconf();
}
catch (EntryPointNotFoundException)
{
// Also fine, OpenSSL 1.1 doesn't need it.
}
}
public static IntPtr TLSv1_2_method()
{
return NativeMethods.TLSv1_2_method();
}
public static IntPtr SSL_CTX_new(IntPtr method)
{
return NativeMethods.SSL_CTX_new(method);
}
public static void SSL_CTX_free(IntPtr ctx)
{
NativeMethods.SSL_CTX_free(ctx);
}
public unsafe static int SSL_CTX_Set_Pfx(IntPtr ctx, string path, string password)
{
var pass = Marshal.StringToHGlobalAnsi(password);
var key = IntPtr.Zero;
var cert = IntPtr.Zero;
var ca = IntPtr.Zero;
try
{
var file = System.IO.File.ReadAllBytes(path);
fixed (void* f = file)
{
var buffer = (IntPtr)f;
var pkcs = NativeMethods.d2i_PKCS12(IntPtr.Zero, ref buffer, file.Length);
var result = NativeMethods.PKCS12_parse(pkcs, pass, ref key, ref cert, ref ca);
if (result != 1)
{
return -1;
}
if (NativeMethods.SSL_CTX_use_certificate(ctx, cert) != 1) return -1;
if (NativeMethods.SSL_CTX_use_PrivateKey(ctx, key) != 1) return -1;
if (NativeMethods.SSL_CTX_ctrl(ctx, SSL_CTRL_CHAIN, 1, ca) != 1) return -1;
return 1;
}
}
finally
{
Marshal.FreeHGlobal(pass);
if (key != IntPtr.Zero) NativeMethods.EVP_PKEY_free(key);
if (cert != IntPtr.Zero) NativeMethods.X509_free(cert);
if (ca != IntPtr.Zero) NativeMethods.sk_X509_pop_free(ca);
}
}
public static int SSL_CTX_set_ecdh_auto(IntPtr ctx, int onoff)
{
return (int)NativeMethods.SSL_CTX_ctrl(ctx, SSL_CTRL_SET_ECDH_AUTO, onoff, IntPtr.Zero);
}
public static int SSL_CTX_use_certificate_file(IntPtr ctx, string file, int type)
{
var ptr = Marshal.StringToHGlobalAnsi(file);
var error = NativeMethods.SSL_CTX_use_certificate_file(ctx, ptr, type);
Marshal.FreeHGlobal(ptr);
return error;
}
public static int SSL_CTX_use_PrivateKey_file(IntPtr ctx, string file, int type)
{
var ptr = Marshal.StringToHGlobalAnsi(file);
var error = NativeMethods.SSL_CTX_use_PrivateKey_file(ctx, ptr, type);
Marshal.FreeHGlobal(ptr);
return error;
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate int alpn_select_cb_t(IntPtr ssl, out byte* @out, out byte outlen, byte* @in, uint inlen, IntPtr arg);
public unsafe static void SSL_CTX_set_alpn_select_cb(IntPtr ctx, alpn_select_cb_t cb, IntPtr arg)
{
NativeMethods.SSL_CTX_set_alpn_select_cb(ctx, cb, arg);
}
public static unsafe int SSL_select_next_proto(out byte* @out, out byte outlen, byte* server, uint server_len, byte* client, uint client_len)
{
return NativeMethods.SSL_select_next_proto(out @out, out outlen, server, server_len, client, client_len);
}
public static unsafe void SSL_get0_alpn_selected(IntPtr ssl, out string protocol)
{
NativeMethods.SSL_get0_alpn_selected(ssl, out var data, out var length);
protocol = data != null
? Marshal.PtrToStringAnsi((IntPtr)data, length)
: null;
}
public static IntPtr SSL_new(IntPtr ctx)
{
return NativeMethods.SSL_new(ctx);
}
public static void SSL_free(IntPtr ssl)
{
NativeMethods.SSL_free(ssl);
}
public static int SSL_get_error(IntPtr ssl, int ret)
{
return NativeMethods.SSL_get_error(ssl, ret);
}
public static int ERR_get_error()
{
return NativeMethods.ERR_get_error();
}
public static string ERR_error_string(int error)
{
var buf = NativeMethods.ERR_error_string(error, IntPtr.Zero);
// Don't free the buffer! It's a static buffer
return Marshal.PtrToStringAnsi(buf);
}
public static void SSL_set_accept_state(IntPtr ssl)
{
NativeMethods.SSL_set_accept_state(ssl);
}
public static int SSL_do_handshake(IntPtr ssl)
{
return NativeMethods.SSL_do_handshake(ssl);
}
public static unsafe int SSL_read(IntPtr ssl, byte[] buffer, int offset, int count)
{
fixed (byte* ptr = buffer)
{
return NativeMethods.SSL_read(ssl, (IntPtr)(ptr + offset), count);
}
}
public static unsafe int SSL_write(IntPtr ssl, byte[] buffer, int offset, int count)
{
fixed (byte* ptr = buffer)
{
return NativeMethods.SSL_write(ssl, (IntPtr)(ptr + offset), count);
}
}
public static void SSL_set_bio(IntPtr ssl, IntPtr rbio, IntPtr wbio)
{
NativeMethods.SSL_set_bio(ssl, rbio, wbio);
}
public static IntPtr BIO_new(IntPtr type)
{
return NativeMethods.BIO_new(type);
}
public static unsafe int BIO_read(IntPtr b, byte[] buffer, int offset, int count)
{
fixed (byte* ptr = buffer)
{
return NativeMethods.BIO_read(b, (IntPtr)(ptr + offset), count);
}
}
public static unsafe int BIO_write(IntPtr b, byte[] buffer, int offset, int count)
{
fixed (byte* ptr = buffer)
{
return NativeMethods.BIO_write(b, (IntPtr)(ptr + offset), count);
}
}
public static long BIO_ctrl_pending(IntPtr b)
{
return NativeMethods.BIO_ctrl_pending(b);
}
public static long BIO_set_mem_eof_return(IntPtr b, int v)
{
return NativeMethods.BIO_ctrl(b, BIO_C_SET_BUF_MEM_EOF_RETURN, v, IntPtr.Zero);
}
public static IntPtr BIO_s_mem()
{
return NativeMethods.BIO_s_mem();
}
public static void ERR_load_BIO_strings()
{
NativeMethods.ERR_load_BIO_strings();
}
private class NativeMethods
{
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int SSL_library_init();
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern void SSL_load_error_strings();
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern void OPENSSL_add_all_algorithms_noconf();
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr TLSv1_2_method();
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr SSL_CTX_new(IntPtr method);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr SSL_CTX_free(IntPtr ctx);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern long SSL_CTX_ctrl(IntPtr ctx, int cmd, long larg, IntPtr parg);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int SSL_CTX_use_certificate_file(IntPtr ctx, IntPtr file, int type);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int SSL_CTX_use_PrivateKey_file(IntPtr ctx, IntPtr file, int type);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern void SSL_CTX_set_alpn_select_cb(IntPtr ctx, alpn_select_cb_t cb, IntPtr arg);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe int SSL_select_next_proto(out byte* @out, out byte outlen, byte* server, uint server_len, byte* client, uint client_len);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe void SSL_get0_alpn_selected(IntPtr ssl, out byte* data, out int len);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr SSL_new(IntPtr ctx);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr SSL_free(IntPtr ssl);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int SSL_get_error(IntPtr ssl, int ret);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern void SSL_set_accept_state(IntPtr ssl);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int SSL_do_handshake(IntPtr ssl);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int SSL_read(IntPtr ssl, IntPtr buf, int len);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int SSL_write(IntPtr ssl, IntPtr buf, int len);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern void SSL_set_bio(IntPtr ssl, IntPtr rbio, IntPtr wbio);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr BIO_new(IntPtr type);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int BIO_read(IntPtr b, IntPtr buf, int len);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int BIO_write(IntPtr b, IntPtr buf, int len);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern long BIO_ctrl(IntPtr bp, int cmd, long larg, IntPtr parg);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern long BIO_ctrl_pending(IntPtr bp);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr BIO_s_mem();
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern void ERR_load_BIO_strings();
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int ERR_get_error();
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr ERR_error_string(int error, IntPtr buf);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr d2i_PKCS12(IntPtr unsused, ref IntPtr bufferPointer, long length);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int PKCS12_parse(IntPtr p12, IntPtr pass, ref IntPtr pkey, ref IntPtr cert, ref IntPtr ca);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern void PKCS12_free(IntPtr p12);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern void EVP_PKEY_free(IntPtr pkey);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern void X509_free(IntPtr a);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern void sk_X509_pop_free(IntPtr ca);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int SSL_CTX_ctrl(IntPtr ctx, int cmd, int larg, IntPtr parg);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int SSL_CTX_set1_chain(IntPtr ctx, IntPtr sk);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int SSL_CTX_use_certificate(IntPtr ctx, IntPtr x509);
[DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
public static extern int SSL_CTX_use_PrivateKey(IntPtr ctx, IntPtr pkey);
}
}
}

View File

@ -1,6 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -1,5 +0,0 @@
# NOT FOR PRODUCTION USE
The code in this package contains the bare minimum to make Kestrel work with TLS 1.2 with ALPN support. It has not been audited nor hardened in any way. DO NOT USE THIS IN PRODUCTION.
This package is temporary and will be removed once `SslStream` supports ALPN.

View File

@ -1,17 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Tls
{
internal class TlsApplicationProtocolFeature : ITlsApplicationProtocolFeature
{
public TlsApplicationProtocolFeature(string applicationProtocol)
{
ApplicationProtocol = applicationProtocol;
}
public string ApplicationProtocol { get; }
}
}

View File

@ -1,120 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Tls
{
public class TlsConnectionAdapter : IConnectionAdapter
{
private static readonly ClosedAdaptedConnection _closedAdaptedConnection = new ClosedAdaptedConnection();
private static readonly List<string> _serverProtocols = new List<string>();
private readonly TlsConnectionAdapterOptions _options;
private readonly ILogger _logger;
private string _applicationProtocol;
public TlsConnectionAdapter(TlsConnectionAdapterOptions options)
: this(options, loggerFactory: null)
{
}
public TlsConnectionAdapter(TlsConnectionAdapterOptions options, ILoggerFactory loggerFactory)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (options.CertificatePath == null)
{
throw new ArgumentException("Certificate path must be non-null.", nameof(options));
}
if (options.Password == null)
{
throw new ArgumentException("Password must be non-null.", nameof(options));
}
_options = options;
_logger = loggerFactory?.CreateLogger(nameof(TlsConnectionAdapter));
// Order is important. If HTTP/2 is enabled, we prefer it over HTTP/1.1. So add it first.
if ((options.Protocols & HttpProtocols.Http2) == HttpProtocols.Http2)
{
_serverProtocols.Add("h2");
}
if ((options.Protocols & HttpProtocols.Http1) == HttpProtocols.Http1)
{
_serverProtocols.Add("http/1.1");
}
}
public bool IsHttps => true;
public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
{
// Don't trust TlsStream not to block.
return Task.Run(() => InnerOnConnectionAsync(context));
}
private async Task<IAdaptedConnection> InnerOnConnectionAsync(ConnectionAdapterContext context)
{
var tlsStream = new TlsStream(context.ConnectionStream, _options.CertificatePath, _options.Password, _serverProtocols);
try
{
await tlsStream.DoHandshakeAsync();
_applicationProtocol = tlsStream.GetNegotiatedApplicationProtocol();
}
catch (IOException ex)
{
_logger?.LogInformation(1, ex, "Authentication failed.");
tlsStream.Dispose();
return _closedAdaptedConnection;
}
// Always set the feature even though the cert might be null
context.Features.Set<ITlsConnectionFeature>(new TlsConnectionFeature());
context.Features.Set<ITlsApplicationProtocolFeature>(new TlsApplicationProtocolFeature(_applicationProtocol));
return new TlsAdaptedConnection(tlsStream);
}
private class TlsAdaptedConnection : IAdaptedConnection
{
private readonly TlsStream _tlsStream;
public TlsAdaptedConnection(TlsStream tlsStream)
{
_tlsStream = tlsStream;
}
public Stream ConnectionStream => _tlsStream;
public void Dispose()
{
_tlsStream.Dispose();
}
}
private class ClosedAdaptedConnection : IAdaptedConnection
{
public Stream ConnectionStream { get; } = new ClosedStream();
public void Dispose()
{
}
}
}
}

View File

@ -1,16 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Server.Kestrel.Core;
namespace Microsoft.AspNetCore.Server.Kestrel.Tls
{
public class TlsConnectionAdapterOptions
{
public string CertificatePath { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public HttpProtocols Protocols { get; set; }
}
}

View File

@ -1,21 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Tls
{
internal class TlsConnectionFeature : ITlsConnectionFeature
{
public X509Certificate2 ClientCertificate { get; set; }
public Task<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken)
{
return Task.FromResult(ClientCertificate);
}
}
}

View File

@ -1,243 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Tls
{
public class TlsStream : Stream
{
// Error code that indicates that a handshake failed because unencrypted HTTP was sent
private const int SSL_ERROR_HTTP_REQUEST = 336130204;
private static unsafe OpenSsl.alpn_select_cb_t _alpnSelectCallback = AlpnSelectCallback;
private readonly Stream _innerStream;
private readonly byte[] _protocols;
private readonly GCHandle _protocolsHandle;
private IntPtr _ctx;
private IntPtr _ssl;
private IntPtr _inputBio;
private IntPtr _outputBio;
private readonly byte[] _inputBuffer = new byte[1024 * 1024];
private readonly byte[] _outputBuffer = new byte[1024 * 1024];
static TlsStream()
{
OpenSsl.SSL_library_init();
OpenSsl.SSL_load_error_strings();
OpenSsl.ERR_load_BIO_strings();
OpenSsl.OpenSSL_add_all_algorithms();
}
public TlsStream(Stream innerStream, string certificatePath, string password, IEnumerable<string> protocols)
{
_innerStream = innerStream;
_protocols = ToWireFormat(protocols);
_protocolsHandle = GCHandle.Alloc(_protocols);
_ctx = OpenSsl.SSL_CTX_new(OpenSsl.TLSv1_2_method());
if (_ctx == IntPtr.Zero)
{
throw new Exception("Unable to create SSL context.");
}
if(OpenSsl.SSL_CTX_Set_Pfx(_ctx, certificatePath, password) != 1)
{
throw new InvalidOperationException("Unable to load PFX");
}
OpenSsl.SSL_CTX_set_ecdh_auto(_ctx, 1);
OpenSsl.SSL_CTX_set_alpn_select_cb(_ctx, _alpnSelectCallback, GCHandle.ToIntPtr(_protocolsHandle));
_ssl = OpenSsl.SSL_new(_ctx);
_inputBio = OpenSsl.BIO_new(OpenSsl.BIO_s_mem());
OpenSsl.BIO_set_mem_eof_return(_inputBio, -1);
_outputBio = OpenSsl.BIO_new(OpenSsl.BIO_s_mem());
OpenSsl.BIO_set_mem_eof_return(_outputBio, -1);
OpenSsl.SSL_set_bio(_ssl, _inputBio, _outputBio);
}
~TlsStream()
{
if (_ssl != IntPtr.Zero)
{
OpenSsl.SSL_free(_ssl);
}
if (_ctx != IntPtr.Zero)
{
// This frees the BIOs.
OpenSsl.SSL_CTX_free(_ctx);
}
if (_protocolsHandle.IsAllocated)
{
_protocolsHandle.Free();
}
}
public override bool CanRead => true;
public override bool CanWrite => true;
public override bool CanSeek => false;
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void Flush()
{
FlushAsync(default(CancellationToken)).GetAwaiter().GetResult();
}
public override int Read(byte[] buffer, int offset, int count)
{
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override async Task FlushAsync(CancellationToken cancellationToken)
{
var pending = OpenSsl.BIO_ctrl_pending(_outputBio);
while (pending > 0)
{
var count = OpenSsl.BIO_read(_outputBio, _outputBuffer, 0, _outputBuffer.Length);
await _innerStream.WriteAsync(_outputBuffer, 0, count, cancellationToken);
pending = OpenSsl.BIO_ctrl_pending(_outputBio);
}
}
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (OpenSsl.BIO_ctrl_pending(_inputBio) == 0)
{
var bytesRead = await _innerStream.ReadAsync(_inputBuffer, 0, _inputBuffer.Length, cancellationToken);
if (bytesRead == 0)
{
return 0;
}
OpenSsl.BIO_write(_inputBio, _inputBuffer, 0, bytesRead);
}
return OpenSsl.SSL_read(_ssl, buffer, offset, count);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
OpenSsl.SSL_write(_ssl, buffer, offset, count);
return FlushAsync(cancellationToken);
}
public async Task DoHandshakeAsync(CancellationToken cancellationToken = default(CancellationToken))
{
OpenSsl.SSL_set_accept_state(_ssl);
var count = 0;
try
{
while ((count = await _innerStream.ReadAsync(_inputBuffer, 0, _inputBuffer.Length, cancellationToken)) > 0)
{
if (count == 0)
{
throw new IOException("TLS handshake failed: the inner stream was closed.");
}
OpenSsl.BIO_write(_inputBio, _inputBuffer, 0, count);
var ret = OpenSsl.SSL_do_handshake(_ssl);
if (ret != 1)
{
var error = OpenSsl.SSL_get_error(_ssl, ret);
if (error == 1)
{
// SSL error, get it from the OpenSSL error queue
error = OpenSsl.ERR_get_error();
if (error == SSL_ERROR_HTTP_REQUEST)
{
throw new InvalidOperationException("Unencrypted HTTP traffic was sent to an HTTPS endpoint");
}
var errorString = OpenSsl.ERR_error_string(error);
throw new IOException($"TLS handshake failed: {nameof(OpenSsl.SSL_do_handshake)} error {error}. {errorString}");
}
}
await FlushAsync(cancellationToken);
if (ret == 1)
{
return;
}
}
}
finally
{
_protocolsHandle.Free();
}
}
public string GetNegotiatedApplicationProtocol()
{
OpenSsl.SSL_get0_alpn_selected(_ssl, out var protocol);
return protocol;
}
private static unsafe int AlpnSelectCallback(IntPtr ssl, out byte* @out, out byte outlen, byte* @in, uint inlen, IntPtr arg)
{
var protocols = GCHandle.FromIntPtr(arg);
var server = (byte[])protocols.Target;
fixed (byte* serverPtr = server)
{
return OpenSsl.SSL_select_next_proto(out @out, out outlen, serverPtr, (uint)server.Length, @in, (uint)inlen) == OpenSsl.OPENSSL_NPN_NEGOTIATED
? OpenSsl.SSL_TLSEXT_ERR_OK
: OpenSsl.SSL_TLSEXT_ERR_NOACK;
}
}
private static byte[] ToWireFormat(IEnumerable<string> protocols)
{
var buffer = new byte[protocols.Count() + protocols.Sum(protocol => protocol.Length)];
var offset = 0;
foreach (var protocol in protocols)
{
buffer[offset++] = (byte)protocol.Length;
offset += Encoding.ASCII.GetBytes(protocol, 0, protocol.Length, buffer, offset);
}
return buffer;
}
}
}