From 065e9bb57a5f8900dc2fc7cc8d94c71135cde9e5 Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Mon, 13 Nov 2017 15:54:16 -0800 Subject: [PATCH] Update "temporary" OpenSSL wrapper to support 1.1, and add HTTP/2 sample with docker (#2149) --- KestrelHttpServer.sln | 17 ++- samples/Http2SampleApp/Dockerfile | 14 +++ samples/Http2SampleApp/Http2SampleApp.csproj | 21 ++++ samples/Http2SampleApp/Program.cs | 50 ++++++++ samples/Http2SampleApp/Startup.cs | 29 +++++ .../Http2SampleApp/scripts/build-docker.ps1 | 3 + .../Http2SampleApp/scripts/build-docker.sh | 6 + samples/Http2SampleApp/scripts/run-docker.ps1 | 1 + samples/Http2SampleApp/scripts/run-docker.sh | 2 + samples/SampleApp/SampleApp.csproj | 8 +- samples/SampleApp/testCert.pfx | Bin 2483 -> 0 bytes .../Internal/Http2/Http2Frame.Data.cs | 6 + src/Kestrel.Core/Internal/Http2/Http2Frame.cs | 1 + src/Kestrel.Tls/ListenOptionsTlsExtensions.cs | 4 +- src/Kestrel.Tls/OpenSsl.cs | 115 +++++++++++++++++- src/Kestrel.Tls/TlsConnectionAdapter.cs | 6 +- .../TlsConnectionAdapterOptions.cs | 2 +- src/Kestrel.Tls/TlsStream.cs | 34 +++--- 18 files changed, 290 insertions(+), 29 deletions(-) create mode 100644 samples/Http2SampleApp/Dockerfile create mode 100644 samples/Http2SampleApp/Http2SampleApp.csproj create mode 100644 samples/Http2SampleApp/Program.cs create mode 100644 samples/Http2SampleApp/Startup.cs create mode 100644 samples/Http2SampleApp/scripts/build-docker.ps1 create mode 100755 samples/Http2SampleApp/scripts/build-docker.sh create mode 100644 samples/Http2SampleApp/scripts/run-docker.ps1 create mode 100755 samples/Http2SampleApp/scripts/run-docker.sh delete mode 100644 samples/SampleApp/testCert.pfx diff --git a/KestrelHttpServer.sln b/KestrelHttpServer.sln index 8ec3d0f3a6..6cdeaa1464 100644 --- a/KestrelHttpServer.sln +++ b/KestrelHttpServer.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.16 +VisualStudioVersion = 15.0.27101.0 MinimumVisualStudioVersion = 15.0.26730.03 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}" ProjectSection(SolutionItems) = preProject @@ -121,6 +121,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Libuv.Fun EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Sockets.FunctionalTests", "test\Kestrel.Transport.Sockets.FunctionalTests\Kestrel.Transport.Sockets.FunctionalTests.csproj", "{9C7B6B5F-088A-436E-834B-6373EA36DEEE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Http2SampleApp", "samples\Http2SampleApp\Http2SampleApp.csproj", "{7BC22A4A-15D2-44C2-AB45-049F0FB562FA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -335,6 +337,18 @@ Global {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Release|x64.Build.0 = Release|Any CPU {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Release|x86.ActiveCfg = Release|Any CPU {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Release|x86.Build.0 = Release|Any CPU + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Debug|x64.ActiveCfg = Debug|Any CPU + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Debug|x64.Build.0 = Debug|Any CPU + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Debug|x86.ActiveCfg = Debug|Any CPU + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Debug|x86.Build.0 = Debug|Any CPU + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|Any CPU.Build.0 = Release|Any CPU + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|x64.ActiveCfg = Release|Any CPU + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|x64.Build.0 = Release|Any CPU + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|x86.ActiveCfg = Release|Any CPU + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -359,6 +373,7 @@ Global {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} + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA} = {8A3D00B8-1CCF-4BE6-A060-11104CE2D9CE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2D10D020-6770-47CA-BB8D-2C23FE3AE071} diff --git a/samples/Http2SampleApp/Dockerfile b/samples/Http2SampleApp/Dockerfile new file mode 100644 index 0000000000..e93d563bde --- /dev/null +++ b/samples/Http2SampleApp/Dockerfile @@ -0,0 +1,14 @@ +FROM microsoft/aspnetcore:2.0.0-stretch + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + libssl-dev && \ + rm -rf /var/lib/apt/lists/* + +ARG CONFIGURATION=Debug + +WORKDIR /app + +COPY ./bin/${CONFIGURATION}/netcoreapp2.0/publish/ /app + +ENTRYPOINT [ "/usr/bin/dotnet", "/app/Http2SampleApp.dll" ] diff --git a/samples/Http2SampleApp/Http2SampleApp.csproj b/samples/Http2SampleApp/Http2SampleApp.csproj new file mode 100644 index 0000000000..0fd184ca8d --- /dev/null +++ b/samples/Http2SampleApp/Http2SampleApp.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp2.0 + false + + + + + + + + + + + + + + + + diff --git a/samples/Http2SampleApp/Program.cs b/samples/Http2SampleApp/Program.cs new file mode 100644 index 0000000000..249c41347c --- /dev/null +++ b/samples/Http2SampleApp/Program.cs @@ -0,0 +1,50 @@ +using System.Globalization; +using System.IO; +using System.Net; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Http2SampleApp +{ + public class Program + { + public static void Main(string[] args) + { + var configuration = new ConfigurationBuilder() + .AddEnvironmentVariables() + .Build(); + + if (!ushort.TryParse(configuration["BASE_PORT"], NumberStyles.None, CultureInfo.InvariantCulture, out var basePort)) + { + basePort = 5000; + } + + var hostBuilder = new WebHostBuilder() + .ConfigureLogging((_, factory) => + { + // Set logging to the MAX. + factory.SetMinimumLevel(LogLevel.Trace); + factory.AddConsole(); + }) + .UseKestrel(options => + { + // Run callbacks on the transport thread + options.ApplicationSchedulingMode = SchedulingMode.Inline; + + options.Listen(IPAddress.Any, basePort, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; + listenOptions.UseTls("testCert.pfx", "testPassword"); + listenOptions.UseConnectionLogging(); + }); + }) + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup(); + + hostBuilder.Build().Run(); + } + } +} diff --git a/samples/Http2SampleApp/Startup.cs b/samples/Http2SampleApp/Startup.cs new file mode 100644 index 0000000000..6dce6b8a19 --- /dev/null +++ b/samples/Http2SampleApp/Startup.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Http2SampleApp +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + app.Run(async (context) => + { + await context.Response.WriteAsync("Hello World!"); + }); + } + } +} diff --git a/samples/Http2SampleApp/scripts/build-docker.ps1 b/samples/Http2SampleApp/scripts/build-docker.ps1 new file mode 100644 index 0000000000..eda82ace6f --- /dev/null +++ b/samples/Http2SampleApp/scripts/build-docker.ps1 @@ -0,0 +1,3 @@ +dotnet publish --framework netcoreapp2.0 "$PSScriptRoot/../Http2SampleApp.csproj" + +docker build -t kestrel-http2-sample (Convert-Path "$PSScriptRoot/..") diff --git a/samples/Http2SampleApp/scripts/build-docker.sh b/samples/Http2SampleApp/scripts/build-docker.sh new file mode 100755 index 0000000000..ca226f0b53 --- /dev/null +++ b/samples/Http2SampleApp/scripts/build-docker.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +dotnet publish --framework netcoreapp2.0 "$DIR/../Http2SampleApp.csproj" + +docker build -t kestrel-http2-sample "$DIR/.." diff --git a/samples/Http2SampleApp/scripts/run-docker.ps1 b/samples/Http2SampleApp/scripts/run-docker.ps1 new file mode 100644 index 0000000000..7b371b6dde --- /dev/null +++ b/samples/Http2SampleApp/scripts/run-docker.ps1 @@ -0,0 +1 @@ +docker run -p 5000:5000 -it --rm kestrel-http2-sample diff --git a/samples/Http2SampleApp/scripts/run-docker.sh b/samples/Http2SampleApp/scripts/run-docker.sh new file mode 100755 index 0000000000..3039b34a98 --- /dev/null +++ b/samples/Http2SampleApp/scripts/run-docker.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +docker run -it -p 5000:5000 --rm kestrel-http2-sample diff --git a/samples/SampleApp/SampleApp.csproj b/samples/SampleApp/SampleApp.csproj index 7d62f70177..2225608125 100644 --- a/samples/SampleApp/SampleApp.csproj +++ b/samples/SampleApp/SampleApp.csproj @@ -6,10 +6,6 @@ true - - - - @@ -19,4 +15,8 @@ + + + + diff --git a/samples/SampleApp/testCert.pfx b/samples/SampleApp/testCert.pfx deleted file mode 100644 index 7118908c2d730670c16e9f8b2c532a262c951989..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2483 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 diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs index 9a0760fe6e..91f0edb72a 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { @@ -40,5 +41,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 Payload.Slice(Length - padLength.Value).Fill(0); } } + + private void DataTraceFrame(ILogger logger) + { + logger.LogTrace("'DATA' Frame. Flags = {DataFlags}, PadLength = {PadLength}, PayloadLength = {PayloadLength}", DataFlags, DataPadLength, DataPayload.Count); + } } } diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.cs index e0dbf8bd09..f983b632a3 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Frame.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { diff --git a/src/Kestrel.Tls/ListenOptionsTlsExtensions.cs b/src/Kestrel.Tls/ListenOptionsTlsExtensions.cs index cb2c459152..695297eb56 100644 --- a/src/Kestrel.Tls/ListenOptionsTlsExtensions.cs +++ b/src/Kestrel.Tls/ListenOptionsTlsExtensions.cs @@ -10,12 +10,12 @@ namespace Microsoft.AspNetCore.Hosting { public static class ListenOptionsTlsExtensions { - public static ListenOptions UseTls(this ListenOptions listenOptions, string certificatePath, string privateKeyPath) + public static ListenOptions UseTls(this ListenOptions listenOptions, string certificatePath, string password) { return listenOptions.UseTls(new TlsConnectionAdapterOptions { CertificatePath = certificatePath, - PrivateKeyPath = privateKeyPath, + Password = password, Protocols = listenOptions.Protocols }); } diff --git a/src/Kestrel.Tls/OpenSsl.cs b/src/Kestrel.Tls/OpenSsl.cs index 17568e4b9c..52af7a8857 100644 --- a/src/Kestrel.Tls/OpenSsl.cs +++ b/src/Kestrel.Tls/OpenSsl.cs @@ -15,23 +15,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls 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 int SSL_library_init() + public static void SSL_library_init() { - return NativeMethods.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() { - NativeMethods.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() { - NativeMethods.OPENSSL_add_all_algorithms_noconf(); + try + { + NativeMethods.OPENSSL_add_all_algorithms_noconf(); + } + catch (EntryPointNotFoundException) + { + // Also fine, OpenSSL 1.1 doesn't need it. + } } public static IntPtr TLSv1_2_method() @@ -49,6 +72,41 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls 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); @@ -109,6 +167,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls 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); @@ -263,6 +334,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls [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); } } } diff --git a/src/Kestrel.Tls/TlsConnectionAdapter.cs b/src/Kestrel.Tls/TlsConnectionAdapter.cs index 539c8404f3..8dee80eb68 100644 --- a/src/Kestrel.Tls/TlsConnectionAdapter.cs +++ b/src/Kestrel.Tls/TlsConnectionAdapter.cs @@ -40,9 +40,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls throw new ArgumentException("Certificate path must be non-null.", nameof(options)); } - if (options.PrivateKeyPath == null) + if (options.Password == null) { - throw new ArgumentException("Private key path must be non-null.", nameof(options)); + throw new ArgumentException("Password must be non-null.", nameof(options)); } _options = options; @@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls private async Task InnerOnConnectionAsync(ConnectionAdapterContext context) { - var tlsStream = new TlsStream(context.ConnectionStream, _options.CertificatePath, _options.PrivateKeyPath, _serverProtocols); + var tlsStream = new TlsStream(context.ConnectionStream, _options.CertificatePath, _options.Password, _serverProtocols); try { diff --git a/src/Kestrel.Tls/TlsConnectionAdapterOptions.cs b/src/Kestrel.Tls/TlsConnectionAdapterOptions.cs index 88d107ffdd..220bd47d9c 100644 --- a/src/Kestrel.Tls/TlsConnectionAdapterOptions.cs +++ b/src/Kestrel.Tls/TlsConnectionAdapterOptions.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls { public string CertificatePath { get; set; } = string.Empty; - public string PrivateKeyPath { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; public HttpProtocols Protocols { get; set; } } diff --git a/src/Kestrel.Tls/TlsStream.cs b/src/Kestrel.Tls/TlsStream.cs index 0b1b583167..497317bab7 100644 --- a/src/Kestrel.Tls/TlsStream.cs +++ b/src/Kestrel.Tls/TlsStream.cs @@ -9,11 +9,15 @@ 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; @@ -36,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls OpenSsl.OpenSSL_add_all_algorithms(); } - public TlsStream(Stream innerStream, string certificatePath, string privateKeyPath, IEnumerable protocols) + public TlsStream(Stream innerStream, string certificatePath, string password, IEnumerable protocols) { _innerStream = innerStream; _protocols = ToWireFormat(protocols); @@ -49,18 +53,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls 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); - - if (OpenSsl.SSL_CTX_use_certificate_file(_ctx, certificatePath, 1) != 1) - { - throw new Exception("Unable to load certificate file."); - } - - if (OpenSsl.SSL_CTX_use_PrivateKey_file(_ctx, privateKeyPath, 1) != 1) - { - throw new Exception("Unable to load private key file."); - } - + OpenSsl.SSL_CTX_set_alpn_select_cb(_ctx, _alpnSelectCallback, GCHandle.ToIntPtr(_protocolsHandle)); _ssl = OpenSsl.SSL_new(_ctx); @@ -181,9 +180,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls { var error = OpenSsl.SSL_get_error(_ssl, ret); - if (error != 2) + if (error == 1) { - throw new IOException($"TLS handshake failed: {nameof(OpenSsl.SSL_do_handshake)} error {error}."); + // 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}"); } }