KestrelHttpServer 789 B

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. commit 065e9bb57a5f8900dc2fc7cc8d94c71135cde9e5
  2. Author: Andrew Stanton-Nurse <[email protected]>
  3. Date: Mon Nov 13 15:54:16 2017 -0800
  4. Update "temporary" OpenSSL wrapper to support 1.1, and add HTTP/2 sample with docker (#2149)
  5. diff --git a/KestrelHttpServer.sln b/KestrelHttpServer.sln
  6. index 8ec3d0f3a65..6cdeaa1464e 100644
  7. --- a/KestrelHttpServer.sln
  8. +++ b/KestrelHttpServer.sln
  9. @@ -1,6 +1,6 @@
  10. Microsoft Visual Studio Solution File, Format Version 12.00
  11. # Visual Studio 15
  12. -VisualStudioVersion = 15.0.26730.16
  13. +VisualStudioVersion = 15.0.27101.0
  14. MinimumVisualStudioVersion = 15.0.26730.03
  15. Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}"
  16. ProjectSection(SolutionItems) = preProject
  17. @@ -121,6 +121,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Libuv.Fun
  18. EndProject
  19. 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}"
  20. EndProject
  21. +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Http2SampleApp", "samples\Http2SampleApp\Http2SampleApp.csproj", "{7BC22A4A-15D2-44C2-AB45-049F0FB562FA}"
  22. +EndProject
  23. Global
  24. GlobalSection(SolutionConfigurationPlatforms) = preSolution
  25. Debug|Any CPU = Debug|Any CPU
  26. @@ -335,6 +337,18 @@ Global
  27. {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Release|x64.Build.0 = Release|Any CPU
  28. {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Release|x86.ActiveCfg = Release|Any CPU
  29. {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Release|x86.Build.0 = Release|Any CPU
  30. + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
  31. + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
  32. + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Debug|x64.ActiveCfg = Debug|Any CPU
  33. + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Debug|x64.Build.0 = Debug|Any CPU
  34. + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Debug|x86.ActiveCfg = Debug|Any CPU
  35. + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Debug|x86.Build.0 = Debug|Any CPU
  36. + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
  37. + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|Any CPU.Build.0 = Release|Any CPU
  38. + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|x64.ActiveCfg = Release|Any CPU
  39. + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|x64.Build.0 = Release|Any CPU
  40. + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|x86.ActiveCfg = Release|Any CPU
  41. + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|x86.Build.0 = Release|Any CPU
  42. EndGlobalSection
  43. GlobalSection(SolutionProperties) = preSolution
  44. HideSolutionNode = FALSE
  45. @@ -359,6 +373,7 @@ Global
  46. {B7B0EA74-528F-46B8-9BC4-909D9A67C194} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
  47. {74032D79-8EA7-4483-BD82-C38370420FFF} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
  48. {9C7B6B5F-088A-436E-834B-6373EA36DEEE} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
  49. + {7BC22A4A-15D2-44C2-AB45-049F0FB562FA} = {8A3D00B8-1CCF-4BE6-A060-11104CE2D9CE}
  50. EndGlobalSection
  51. GlobalSection(ExtensibilityGlobals) = postSolution
  52. SolutionGuid = {2D10D020-6770-47CA-BB8D-2C23FE3AE071}
  53. diff --git a/samples/Http2SampleApp/Dockerfile b/samples/Http2SampleApp/Dockerfile
  54. new file mode 100644
  55. index 00000000000..e93d563bde5
  56. --- /dev/null
  57. +++ b/samples/Http2SampleApp/Dockerfile
  58. @@ -0,0 +1,14 @@
  59. +FROM microsoft/aspnetcore:2.0.0-stretch
  60. +
  61. +RUN apt-get update && \
  62. + apt-get install -y --no-install-recommends \
  63. + libssl-dev && \
  64. + rm -rf /var/lib/apt/lists/*
  65. +
  66. +ARG CONFIGURATION=Debug
  67. +
  68. +WORKDIR /app
  69. +
  70. +COPY ./bin/${CONFIGURATION}/netcoreapp2.0/publish/ /app
  71. +
  72. +ENTRYPOINT [ "/usr/bin/dotnet", "/app/Http2SampleApp.dll" ]
  73. diff --git a/samples/Http2SampleApp/Http2SampleApp.csproj b/samples/Http2SampleApp/Http2SampleApp.csproj
  74. new file mode 100644
  75. index 00000000000..0fd184ca8d2
  76. --- /dev/null
  77. +++ b/samples/Http2SampleApp/Http2SampleApp.csproj
  78. @@ -0,0 +1,21 @@
  79. +<Project Sdk="Microsoft.NET.Sdk.Web">
  80. +
  81. + <PropertyGroup>
  82. + <TargetFrameworks>netcoreapp2.0</TargetFrameworks>
  83. + <IsPackable>false</IsPackable>
  84. + </PropertyGroup>
  85. +
  86. + <ItemGroup>
  87. + <ProjectReference Include="..\..\src\Kestrel\Kestrel.csproj" />
  88. + <ProjectReference Include="..\..\src\Kestrel.Tls\Kestrel.Tls.csproj" />
  89. + </ItemGroup>
  90. +
  91. + <ItemGroup>
  92. + <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
  93. + </ItemGroup>
  94. +
  95. + <ItemGroup>
  96. + <Content Include="../../test/shared/TestCertificates/testCert.pfx" CopyToOutputDirectory="PreserveNewest" />
  97. + </ItemGroup>
  98. +
  99. +</Project>
  100. diff --git a/samples/Http2SampleApp/Program.cs b/samples/Http2SampleApp/Program.cs
  101. new file mode 100644
  102. index 00000000000..249c41347c7
  103. --- /dev/null
  104. +++ b/samples/Http2SampleApp/Program.cs
  105. @@ -0,0 +1,50 @@
  106. +using System.Globalization;
  107. +using System.IO;
  108. +using System.Net;
  109. +using Microsoft.AspNetCore.Hosting;
  110. +using Microsoft.AspNetCore.Server.Kestrel.Core;
  111. +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
  112. +using Microsoft.Extensions.Configuration;
  113. +using Microsoft.Extensions.Logging;
  114. +
  115. +namespace Http2SampleApp
  116. +{
  117. + public class Program
  118. + {
  119. + public static void Main(string[] args)
  120. + {
  121. + var configuration = new ConfigurationBuilder()
  122. + .AddEnvironmentVariables()
  123. + .Build();
  124. +
  125. + if (!ushort.TryParse(configuration["BASE_PORT"], NumberStyles.None, CultureInfo.InvariantCulture, out var basePort))
  126. + {
  127. + basePort = 5000;
  128. + }
  129. +
  130. + var hostBuilder = new WebHostBuilder()
  131. + .ConfigureLogging((_, factory) =>
  132. + {
  133. + // Set logging to the MAX.
  134. + factory.SetMinimumLevel(LogLevel.Trace);
  135. + factory.AddConsole();
  136. + })
  137. + .UseKestrel(options =>
  138. + {
  139. + // Run callbacks on the transport thread
  140. + options.ApplicationSchedulingMode = SchedulingMode.Inline;
  141. +
  142. + options.Listen(IPAddress.Any, basePort, listenOptions =>
  143. + {
  144. + listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
  145. + listenOptions.UseTls("testCert.pfx", "testPassword");
  146. + listenOptions.UseConnectionLogging();
  147. + });
  148. + })
  149. + .UseContentRoot(Directory.GetCurrentDirectory())
  150. + .UseStartup<Startup>();
  151. +
  152. + hostBuilder.Build().Run();
  153. + }
  154. + }
  155. +}
  156. diff --git a/samples/Http2SampleApp/Startup.cs b/samples/Http2SampleApp/Startup.cs
  157. new file mode 100644
  158. index 00000000000..6dce6b8a196
  159. --- /dev/null
  160. +++ b/samples/Http2SampleApp/Startup.cs
  161. @@ -0,0 +1,29 @@
  162. +using System;
  163. +using System.Collections.Generic;
  164. +using System.Linq;
  165. +using System.Threading.Tasks;
  166. +using Microsoft.AspNetCore.Builder;
  167. +using Microsoft.AspNetCore.Hosting;
  168. +using Microsoft.AspNetCore.Http;
  169. +using Microsoft.Extensions.DependencyInjection;
  170. +
  171. +namespace Http2SampleApp
  172. +{
  173. + public class Startup
  174. + {
  175. + // This method gets called by the runtime. Use this method to add services to the container.
  176. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
  177. + public void ConfigureServices(IServiceCollection services)
  178. + {
  179. + }
  180. +
  181. + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  182. + public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  183. + {
  184. + app.Run(async (context) =>
  185. + {
  186. + await context.Response.WriteAsync("Hello World!");
  187. + });
  188. + }
  189. + }
  190. +}
  191. diff --git a/samples/Http2SampleApp/scripts/build-docker.ps1 b/samples/Http2SampleApp/scripts/build-docker.ps1
  192. new file mode 100644
  193. index 00000000000..eda82ace6f8
  194. --- /dev/null
  195. +++ b/samples/Http2SampleApp/scripts/build-docker.ps1
  196. @@ -0,0 +1,3 @@
  197. +dotnet publish --framework netcoreapp2.0 "$PSScriptRoot/../Http2SampleApp.csproj"
  198. +
  199. +docker build -t kestrel-http2-sample (Convert-Path "$PSScriptRoot/..")
  200. diff --git a/samples/Http2SampleApp/scripts/build-docker.sh b/samples/Http2SampleApp/scripts/build-docker.sh
  201. new file mode 100755
  202. index 00000000000..ca226f0b53d
  203. --- /dev/null
  204. +++ b/samples/Http2SampleApp/scripts/build-docker.sh
  205. @@ -0,0 +1,6 @@
  206. +#!/usr/bin/env bash
  207. +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
  208. +
  209. +dotnet publish --framework netcoreapp2.0 "$DIR/../Http2SampleApp.csproj"
  210. +
  211. +docker build -t kestrel-http2-sample "$DIR/.."
  212. diff --git a/samples/Http2SampleApp/scripts/run-docker.ps1 b/samples/Http2SampleApp/scripts/run-docker.ps1
  213. new file mode 100644
  214. index 00000000000..7b371b6dde9
  215. --- /dev/null
  216. +++ b/samples/Http2SampleApp/scripts/run-docker.ps1
  217. @@ -0,0 +1 @@
  218. +docker run -p 5000:5000 -it --rm kestrel-http2-sample
  219. diff --git a/samples/Http2SampleApp/scripts/run-docker.sh b/samples/Http2SampleApp/scripts/run-docker.sh
  220. new file mode 100755
  221. index 00000000000..3039b34a988
  222. --- /dev/null
  223. +++ b/samples/Http2SampleApp/scripts/run-docker.sh
  224. @@ -0,0 +1,2 @@
  225. +#!/usr/bin/env bash
  226. +docker run -it -p 5000:5000 --rm kestrel-http2-sample
  227. diff --git a/samples/SampleApp/SampleApp.csproj b/samples/SampleApp/SampleApp.csproj
  228. index 7d62f701775..2225608125f 100644
  229. --- a/samples/SampleApp/SampleApp.csproj
  230. +++ b/samples/SampleApp/SampleApp.csproj
  231. @@ -6,10 +6,6 @@
  232. <NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
  233. </PropertyGroup>
  234. - <ItemGroup>
  235. - <Content Include="testCert.pfx" CopyToOutputDirectory="PreserveNewest" />
  236. - </ItemGroup>
  237. -
  238. <ItemGroup>
  239. <ProjectReference Include="..\..\src\Kestrel\Kestrel.csproj" />
  240. <ProjectReference Include="..\..\src\Kestrel.Https\Kestrel.Https.csproj" />
  241. @@ -19,4 +15,8 @@
  242. <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
  243. </ItemGroup>
  244. + <ItemGroup>
  245. + <Content Include="../../test/shared/TestCertificates/testCert.pfx" CopyToOutputDirectory="PreserveNewest" />
  246. + </ItemGroup>
  247. +
  248. </Project>
  249. diff --git a/samples/SampleApp/testCert.pfx b/samples/SampleApp/testCert.pfx
  250. deleted file mode 100644
  251. index 7118908c2d7..00000000000
  252. Binary files a/samples/SampleApp/testCert.pfx and /dev/null differ
  253. diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs
  254. index 9a0760fe6e6..91f0edb72a6 100644
  255. --- a/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs
  256. +++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.Data.cs
  257. @@ -2,6 +2,7 @@
  258. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  259. using System;
  260. +using Microsoft.Extensions.Logging;
  261. namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
  262. {
  263. @@ -40,5 +41,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
  264. Payload.Slice(Length - padLength.Value).Fill(0);
  265. }
  266. }
  267. +
  268. + private void DataTraceFrame(ILogger logger)
  269. + {
  270. + logger.LogTrace("'DATA' Frame. Flags = {DataFlags}, PadLength = {PadLength}, PayloadLength = {PayloadLength}", DataFlags, DataPadLength, DataPayload.Count);
  271. + }
  272. }
  273. }
  274. diff --git a/src/Kestrel.Core/Internal/Http2/Http2Frame.cs b/src/Kestrel.Core/Internal/Http2/Http2Frame.cs
  275. index e0dbf8bd091..f983b632a39 100644
  276. --- a/src/Kestrel.Core/Internal/Http2/Http2Frame.cs
  277. +++ b/src/Kestrel.Core/Internal/Http2/Http2Frame.cs
  278. @@ -2,6 +2,7 @@
  279. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  280. using System;
  281. +using Microsoft.Extensions.Logging;
  282. namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
  283. {
  284. diff --git a/src/Kestrel.Tls/ListenOptionsTlsExtensions.cs b/src/Kestrel.Tls/ListenOptionsTlsExtensions.cs
  285. index cb2c459152f..695297eb56c 100644
  286. --- a/src/Kestrel.Tls/ListenOptionsTlsExtensions.cs
  287. +++ b/src/Kestrel.Tls/ListenOptionsTlsExtensions.cs
  288. @@ -10,12 +10,12 @@ namespace Microsoft.AspNetCore.Hosting
  289. {
  290. public static class ListenOptionsTlsExtensions
  291. {
  292. - public static ListenOptions UseTls(this ListenOptions listenOptions, string certificatePath, string privateKeyPath)
  293. + public static ListenOptions UseTls(this ListenOptions listenOptions, string certificatePath, string password)
  294. {
  295. return listenOptions.UseTls(new TlsConnectionAdapterOptions
  296. {
  297. CertificatePath = certificatePath,
  298. - PrivateKeyPath = privateKeyPath,
  299. + Password = password,
  300. Protocols = listenOptions.Protocols
  301. });
  302. }
  303. diff --git a/src/Kestrel.Tls/OpenSsl.cs b/src/Kestrel.Tls/OpenSsl.cs
  304. index 17568e4b9c8..52af7a88570 100644
  305. --- a/src/Kestrel.Tls/OpenSsl.cs
  306. +++ b/src/Kestrel.Tls/OpenSsl.cs
  307. @@ -15,23 +15,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls
  308. public const int OPENSSL_NPN_NEGOTIATED = 1;
  309. public const int SSL_TLSEXT_ERR_OK = 0;
  310. public const int SSL_TLSEXT_ERR_NOACK = 3;
  311. + public const int SSL_CTRL_CHAIN = 88;
  312. private const int BIO_C_SET_BUF_MEM_EOF_RETURN = 130;
  313. private const int SSL_CTRL_SET_ECDH_AUTO = 94;
  314. - public static int SSL_library_init()
  315. + public static void SSL_library_init()
  316. {
  317. - return NativeMethods.SSL_library_init();
  318. + try
  319. + {
  320. + // Try OpenSSL 1.0.2
  321. + NativeMethods.SSL_library_init();
  322. + }
  323. + catch (EntryPointNotFoundException)
  324. + {
  325. + // It's fine, OpenSSL 1.1 doesn't need initialization
  326. + }
  327. }
  328. public static void SSL_load_error_strings()
  329. {
  330. - NativeMethods.SSL_load_error_strings();
  331. + try
  332. + {
  333. + NativeMethods.SSL_load_error_strings();
  334. + }
  335. + catch (EntryPointNotFoundException)
  336. + {
  337. + // Also fine, OpenSSL 1.1 doesn't need it.
  338. + }
  339. }
  340. public static void OpenSSL_add_all_algorithms()
  341. {
  342. - NativeMethods.OPENSSL_add_all_algorithms_noconf();
  343. + try
  344. + {
  345. + NativeMethods.OPENSSL_add_all_algorithms_noconf();
  346. + }
  347. + catch (EntryPointNotFoundException)
  348. + {
  349. + // Also fine, OpenSSL 1.1 doesn't need it.
  350. + }
  351. }
  352. public static IntPtr TLSv1_2_method()
  353. @@ -49,6 +72,41 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls
  354. NativeMethods.SSL_CTX_free(ctx);
  355. }
  356. + public unsafe static int SSL_CTX_Set_Pfx(IntPtr ctx, string path, string password)
  357. + {
  358. + var pass = Marshal.StringToHGlobalAnsi(password);
  359. + var key = IntPtr.Zero;
  360. + var cert = IntPtr.Zero;
  361. + var ca = IntPtr.Zero;
  362. +
  363. + try
  364. + {
  365. + var file = System.IO.File.ReadAllBytes(path);
  366. +
  367. + fixed (void* f = file)
  368. + {
  369. + var buffer = (IntPtr)f;
  370. + var pkcs = NativeMethods.d2i_PKCS12(IntPtr.Zero, ref buffer, file.Length);
  371. + var result = NativeMethods.PKCS12_parse(pkcs, pass, ref key, ref cert, ref ca);
  372. + if (result != 1)
  373. + {
  374. + return -1;
  375. + }
  376. + if (NativeMethods.SSL_CTX_use_certificate(ctx, cert) != 1) return -1;
  377. + if (NativeMethods.SSL_CTX_use_PrivateKey(ctx, key) != 1) return -1;
  378. + if (NativeMethods.SSL_CTX_ctrl(ctx, SSL_CTRL_CHAIN, 1, ca) != 1) return -1;
  379. + return 1;
  380. + }
  381. + }
  382. + finally
  383. + {
  384. + Marshal.FreeHGlobal(pass);
  385. + if (key != IntPtr.Zero) NativeMethods.EVP_PKEY_free(key);
  386. + if (cert != IntPtr.Zero) NativeMethods.X509_free(cert);
  387. + if (ca != IntPtr.Zero) NativeMethods.sk_X509_pop_free(ca);
  388. + }
  389. + }
  390. +
  391. public static int SSL_CTX_set_ecdh_auto(IntPtr ctx, int onoff)
  392. {
  393. return (int)NativeMethods.SSL_CTX_ctrl(ctx, SSL_CTRL_SET_ECDH_AUTO, onoff, IntPtr.Zero);
  394. @@ -109,6 +167,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls
  395. return NativeMethods.SSL_get_error(ssl, ret);
  396. }
  397. + public static int ERR_get_error()
  398. + {
  399. + return NativeMethods.ERR_get_error();
  400. + }
  401. +
  402. + public static string ERR_error_string(int error)
  403. + {
  404. + var buf = NativeMethods.ERR_error_string(error, IntPtr.Zero);
  405. +
  406. + // Don't free the buffer! It's a static buffer
  407. + return Marshal.PtrToStringAnsi(buf);
  408. + }
  409. +
  410. public static void SSL_set_accept_state(IntPtr ssl)
  411. {
  412. NativeMethods.SSL_set_accept_state(ssl);
  413. @@ -263,6 +334,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls
  414. [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
  415. public static extern void ERR_load_BIO_strings();
  416. +
  417. + [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
  418. + public static extern int ERR_get_error();
  419. +
  420. + [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
  421. + public static extern IntPtr ERR_error_string(int error, IntPtr buf);
  422. +
  423. + [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
  424. + public static extern IntPtr d2i_PKCS12(IntPtr unsused, ref IntPtr bufferPointer, long length);
  425. +
  426. + [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
  427. + public static extern int PKCS12_parse(IntPtr p12, IntPtr pass, ref IntPtr pkey, ref IntPtr cert, ref IntPtr ca);
  428. +
  429. + [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
  430. + public static extern void PKCS12_free(IntPtr p12);
  431. +
  432. + [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
  433. + public static extern void EVP_PKEY_free(IntPtr pkey);
  434. +
  435. + [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
  436. + public static extern void X509_free(IntPtr a);
  437. +
  438. + [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
  439. + public static extern void sk_X509_pop_free(IntPtr ca);
  440. +
  441. + [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
  442. + public static extern int SSL_CTX_ctrl(IntPtr ctx, int cmd, int larg, IntPtr parg);
  443. +
  444. + [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
  445. + public static extern int SSL_CTX_set1_chain(IntPtr ctx, IntPtr sk);
  446. +
  447. + [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
  448. + public static extern int SSL_CTX_use_certificate(IntPtr ctx, IntPtr x509);
  449. +
  450. + [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
  451. + public static extern int SSL_CTX_use_PrivateKey(IntPtr ctx, IntPtr pkey);
  452. }
  453. }
  454. }
  455. diff --git a/src/Kestrel.Tls/TlsConnectionAdapter.cs b/src/Kestrel.Tls/TlsConnectionAdapter.cs
  456. index 539c8404f3b..8dee80eb689 100644
  457. --- a/src/Kestrel.Tls/TlsConnectionAdapter.cs
  458. +++ b/src/Kestrel.Tls/TlsConnectionAdapter.cs
  459. @@ -40,9 +40,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls
  460. throw new ArgumentException("Certificate path must be non-null.", nameof(options));
  461. }
  462. - if (options.PrivateKeyPath == null)
  463. + if (options.Password == null)
  464. {
  465. - throw new ArgumentException("Private key path must be non-null.", nameof(options));
  466. + throw new ArgumentException("Password must be non-null.", nameof(options));
  467. }
  468. _options = options;
  469. @@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls
  470. private async Task<IAdaptedConnection> InnerOnConnectionAsync(ConnectionAdapterContext context)
  471. {
  472. - var tlsStream = new TlsStream(context.ConnectionStream, _options.CertificatePath, _options.PrivateKeyPath, _serverProtocols);
  473. + var tlsStream = new TlsStream(context.ConnectionStream, _options.CertificatePath, _options.Password, _serverProtocols);
  474. try
  475. {
  476. diff --git a/src/Kestrel.Tls/TlsConnectionAdapterOptions.cs b/src/Kestrel.Tls/TlsConnectionAdapterOptions.cs
  477. index 88d107ffdd4..220bd47d9c7 100644
  478. --- a/src/Kestrel.Tls/TlsConnectionAdapterOptions.cs
  479. +++ b/src/Kestrel.Tls/TlsConnectionAdapterOptions.cs
  480. @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls
  481. {
  482. public string CertificatePath { get; set; } = string.Empty;
  483. - public string PrivateKeyPath { get; set; } = string.Empty;
  484. + public string Password { get; set; } = string.Empty;
  485. public HttpProtocols Protocols { get; set; }
  486. }
  487. diff --git a/src/Kestrel.Tls/TlsStream.cs b/src/Kestrel.Tls/TlsStream.cs
  488. index 0b1b583167e..497317bab7a 100644
  489. --- a/src/Kestrel.Tls/TlsStream.cs
  490. +++ b/src/Kestrel.Tls/TlsStream.cs
  491. @@ -9,11 +9,15 @@ using System.Runtime.InteropServices;
  492. using System.Text;
  493. using System.Threading;
  494. using System.Threading.Tasks;
  495. +using Microsoft.Extensions.Logging;
  496. namespace Microsoft.AspNetCore.Server.Kestrel.Tls
  497. {
  498. public class TlsStream : Stream
  499. {
  500. + // Error code that indicates that a handshake failed because unencrypted HTTP was sent
  501. + private const int SSL_ERROR_HTTP_REQUEST = 336130204;
  502. +
  503. private static unsafe OpenSsl.alpn_select_cb_t _alpnSelectCallback = AlpnSelectCallback;
  504. private readonly Stream _innerStream;
  505. @@ -36,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls
  506. OpenSsl.OpenSSL_add_all_algorithms();
  507. }
  508. - public TlsStream(Stream innerStream, string certificatePath, string privateKeyPath, IEnumerable<string> protocols)
  509. + public TlsStream(Stream innerStream, string certificatePath, string password, IEnumerable<string> protocols)
  510. {
  511. _innerStream = innerStream;
  512. _protocols = ToWireFormat(protocols);
  513. @@ -49,18 +53,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls
  514. throw new Exception("Unable to create SSL context.");
  515. }
  516. - OpenSsl.SSL_CTX_set_ecdh_auto(_ctx, 1);
  517. -
  518. - if (OpenSsl.SSL_CTX_use_certificate_file(_ctx, certificatePath, 1) != 1)
  519. + if(OpenSsl.SSL_CTX_Set_Pfx(_ctx, certificatePath, password) != 1)
  520. {
  521. - throw new Exception("Unable to load certificate file.");
  522. - }
  523. -
  524. - if (OpenSsl.SSL_CTX_use_PrivateKey_file(_ctx, privateKeyPath, 1) != 1)
  525. - {
  526. - throw new Exception("Unable to load private key file.");
  527. + throw new InvalidOperationException("Unable to load PFX");
  528. }
  529. + OpenSsl.SSL_CTX_set_ecdh_auto(_ctx, 1);
  530. +
  531. OpenSsl.SSL_CTX_set_alpn_select_cb(_ctx, _alpnSelectCallback, GCHandle.ToIntPtr(_protocolsHandle));
  532. _ssl = OpenSsl.SSL_new(_ctx);
  533. @@ -181,9 +180,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tls
  534. {
  535. var error = OpenSsl.SSL_get_error(_ssl, ret);
  536. - if (error != 2)
  537. + if (error == 1)
  538. {
  539. - throw new IOException($"TLS handshake failed: {nameof(OpenSsl.SSL_do_handshake)} error {error}.");
  540. + // SSL error, get it from the OpenSSL error queue
  541. + error = OpenSsl.ERR_get_error();
  542. + if (error == SSL_ERROR_HTTP_REQUEST)
  543. + {
  544. + throw new InvalidOperationException("Unencrypted HTTP traffic was sent to an HTTPS endpoint");
  545. + }
  546. + var errorString = OpenSsl.ERR_error_string(error);
  547. + throw new IOException($"TLS handshake failed: {nameof(OpenSsl.SSL_do_handshake)} error {error}. {errorString}");
  548. }
  549. }