From 634c9c180cc588fe6f1f40aeac3b680f5c5a2793 Mon Sep 17 00:00:00 2001 From: Cesar Blum Silveira Date: Wed, 3 May 2017 11:48:37 -0700 Subject: [PATCH] Fix HTTPS functional tests on macOS (#96). --- .../HttpClientSlim.cs | 130 ++++++++++++++++++ ...icrosoft.AspNetCore.FunctionalTests.csproj | 2 +- .../TestArtifacts/Certificate.pfx | Bin 2461 -> 0 bytes .../WebHostFunctionalTests.cs | 25 ++-- .../testCert.pfx | Bin 0 -> 2483 bytes 5 files changed, 140 insertions(+), 17 deletions(-) create mode 100644 test/Microsoft.AspNetCore.FunctionalTests/HttpClientSlim.cs delete mode 100644 test/Microsoft.AspNetCore.FunctionalTests/TestArtifacts/Certificate.pfx create mode 100644 test/Microsoft.AspNetCore.FunctionalTests/testCert.pfx diff --git a/test/Microsoft.AspNetCore.FunctionalTests/HttpClientSlim.cs b/test/Microsoft.AspNetCore.FunctionalTests/HttpClientSlim.cs new file mode 100644 index 0000000000..a85b03aa40 --- /dev/null +++ b/test/Microsoft.AspNetCore.FunctionalTests/HttpClientSlim.cs @@ -0,0 +1,130 @@ +// 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.Net; +using System.Net.Http; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Testing +{ + // Lightweight version of HttpClient implemented using Socket and SslStream + public static class HttpClientSlim + { + public static async Task GetStringAsync(string requestUri, bool validateCertificate = true) + => await GetStringAsync(new Uri(requestUri), validateCertificate).ConfigureAwait(false); + + public static async Task GetStringAsync(Uri requestUri, bool validateCertificate = true) + { + using (var stream = await GetStream(requestUri, validateCertificate).ConfigureAwait(false)) + { + using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true)) + { + await writer.WriteAsync($"GET {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Host: {requestUri.Authority}\r\n").ConfigureAwait(false); + await writer.WriteAsync("\r\n").ConfigureAwait(false); + } + + return await ReadResponse(stream).ConfigureAwait(false); + } + } + + public static async Task PostAsync(string requestUri, HttpContent content, bool validateCertificate = true) + => await PostAsync(new Uri(requestUri), content, validateCertificate).ConfigureAwait(false); + + public static async Task PostAsync(Uri requestUri, HttpContent content, bool validateCertificate = true) + { + using (var stream = await GetStream(requestUri, validateCertificate)) + { + using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true)) + { + await writer.WriteAsync($"POST {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Host: {requestUri.Authority}\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Content-Type: {content.Headers.ContentType}\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Content-Length: {content.Headers.ContentLength}\r\n").ConfigureAwait(false); + await writer.WriteAsync("\r\n").ConfigureAwait(false); + } + + await content.CopyToAsync(stream).ConfigureAwait(false); + + return await ReadResponse(stream).ConfigureAwait(false); + } + } + + private static async Task ReadResponse(Stream stream) + { + using (var reader = new StreamReader(stream, Encoding.ASCII, detectEncodingFromByteOrderMarks: true, + bufferSize: 1024, leaveOpen: true)) + { + var response = await reader.ReadToEndAsync().ConfigureAwait(false); + + var status = GetStatus(response); + new HttpResponseMessage(status).EnsureSuccessStatusCode(); + + var body = response.Substring(response.IndexOf("\r\n\r\n") + 4); + return body; + } + + } + + private static HttpStatusCode GetStatus(string response) + { + var statusStart = response.IndexOf(' ') + 1; + var statusEnd = response.IndexOf(' ', statusStart) - 1; + var statusLength = statusEnd - statusStart + 1; + return (HttpStatusCode)int.Parse(response.Substring(statusStart, statusLength)); + } + + private static async Task GetStream(Uri requestUri, bool validateCertificate) + { + var socket = await GetSocket(requestUri); + var stream = new NetworkStream(socket, ownsSocket: true); + + if (requestUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) + { + var sslStream = new SslStream(stream, leaveInnerStreamOpen: false, userCertificateValidationCallback: + validateCertificate ? null : (RemoteCertificateValidationCallback)((a, b, c, d) => true)); + + await sslStream.AuthenticateAsClientAsync(requestUri.Host, clientCertificates: null, + enabledSslProtocols: SslProtocols.Tls11 | SslProtocols.Tls12, + checkCertificateRevocation: validateCertificate).ConfigureAwait(false); + return sslStream; + } + else + { + return stream; + } + } + + public static async Task GetSocket(Uri requestUri) + { + var tcs = new TaskCompletionSource(); + + var socketArgs = new SocketAsyncEventArgs(); + socketArgs.RemoteEndPoint = new DnsEndPoint(requestUri.DnsSafeHost, requestUri.Port); + socketArgs.Completed += (s, e) => tcs.TrySetResult(e.ConnectSocket); + + // Must use static ConnectAsync(), since instance Connect() does not support DNS names on OSX/Linux. + if (Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, socketArgs)) + { + await tcs.Task.ConfigureAwait(false); + } + + var socket = socketArgs.ConnectSocket; + + if (socket == null) + { + throw new SocketException((int)socketArgs.SocketError); + } + else + { + return socket; + } + } + } +} diff --git a/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj b/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj index 13d57e6616..e2596080ea 100644 --- a/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj @@ -7,7 +7,7 @@ - + diff --git a/test/Microsoft.AspNetCore.FunctionalTests/TestArtifacts/Certificate.pfx b/test/Microsoft.AspNetCore.FunctionalTests/TestArtifacts/Certificate.pfx deleted file mode 100644 index c792c522488adf723e6f37b354a9f4178dedf98d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2461 zcmV;O31apzf(e-d0Ru3C310>YDuzgg_YDCD0ic2jPy~VrOfZ58NHBr}{{{&vhDe6@ z4FLxRpn?PNFoFZ@0s#Opf&=9S2`Yw2hW8Bt2LUh~1_~;MNQUCLhc?KpZ79UX((b9gtz3l$ShHTW^w(4j=OB4)=~cBICF-(;4ox6qbLYN7}GC8?wNZs!fw~MG~CeO>3{q`?$!YE&|TW)7%ltY`g`J(4!EA+0qG<5f-@An+sS2s zyKPh^XzQo6k{=!X&J>f&qlC6rk8nC2#J1jlBp_18$8vKh-U$SOlzOIq)FBRBa&tg` zR?M@NvBD)UfX4@ypWaR=ez3Y(XT=|lyg$RizAl06K8 zr#J2k1>y5RV4K1`d870`A3QB{3^91xcGGJjnA z?o&nSF>%B)m;bj~^X${eq1{=%dD7bv41prq(x@sC@C{Ux|CklhNTJsPwbdHcnU6aO z_MGTcs3e!*5A|F5ucqU|`7#}Z*Khdo>V`x1Uo*CTvKwpEx#qsTud0(OiO61wr zWsY)LxoLde@Ttox)W3{NslcIT{a`vbL7X6WFHg+d7${@_O!$w4$&u67int_=`alI=5ZenfB@%ode@Q;fL^<;&t0j74IM#t*%O&Eb#E zO3Q0JLna^Ynq)c|cy-Ob09lVVleD}Lc6330Fpna<#uA`2$#oKNgzXM0hIDWxb(s$V z=mM&<1Vvca4{@Gq&+gV^)-i68+iSlE)4C9T6{pXBVY49f&hbPcQJn0EC!Fh`F_y+b z8JRS!vsd+UUsgd_z02z(#_2S|4Opb0xM2A#FoFd^1_>&LNQU<1f~E*r4=p<8OP?$r5q@pf%h z&EDD8e?4ydKLhh0X1jw|iGk*vgA=^gX0p1AZNR#zCAo>PbBd8-BIOL%BFE;!g*H$B z+%|ma9v2wNKO`Lk-c@XG95iEwCmv=hC0hfNaJ5Y?Vzl4P_ACkn6A6(*Xt)~gr24?& zc+?9X2d*291gesBlP(J`eTUNdobKwi*3?i~M`)ta3fK@;q{PEK+IrL7J&qIj20hwK z=FjLbo?5-}nuEo?0O*EM;WroV6@Y;9(0u=E0oZAnYKUmZgO@*BlUf&!yrg|^ZnYQS zWi0?G-9lB32QH2trNhMsRKwW8rzbJjF~4~>BJmD-z4*Ji`w!)TEIh(gI>dnMN`R$Z z=Jikm=|Vhkte489k2e;D)Mg|#af_PODc_GM$G;+R^1ik|-HCl-JgeBtM+o5lF|Di9xq1Z@Wssz@fb zbPnrus>$DBXV7xnJ^E#mF2Lg}$J%^TNrn2?I(t>5fuj^+cY|SyN8kR5EF23+oZTj1 z%$vAm>Ub#v)EGLri#9f_hQ_f2!3bDH)r&Jn${QBL?(7GS(_D`PTw67D>K*P2w4Q_&JRoj>7m zaA93;0I>mecVE^YkUui2b^mZQYw
=S3wwEn5^#$v~O)z)+%fl)_B{=vHjXvs{( zy@6^1ky#X!=Zw<(^`EROArA_6(b|7TC=s=GKFsVpF)PM*r(PTw&lG0&x0Io%!U9;2 zj=jihQxIh2UeSG6S6;zp+6D@@%7}M0&G{8oj}eIoFaW(cX}X(GB;Yqp2oNC;js&>K z4!MD=3=^sWTzc;ZOTmMxdB~e+wFY(hV*xIg)EC;wsZ`%SkjAUb@p2GYltVU5Ttp*T z(tN%{T<*Po);<(6l9pKk*YlK@3HVebh|1Js+1m~5UKrYT>M%KIi~nwzO{^ybk#t*r zNHqrglnI(D8rfWhnHTqMP8URj$C@;Qkk-JZ?!hcI;l{vNH9t+iV;tU}1irx}EWzGn zOf)IR=b-k6fsXnc*VTCl@&%|PujOc*)#Ii{f5SuZ-iR#-b2pc=YG2>2FDZ`JQ0=KS z;ZfPMXPehqQ>l~pxKFs91$jFd6km}OdnDGgAq4W!CMv_%kJO8i?vwn_V4kLz?&j|7 zi}in{dI)eyF*ULRbHg&bPFRx#AI%8;3jw~+y|_AIZ$(m2xa~0|Fe3&DDuzgg_YDCF z6)_eB6ng)VNS~j_hgq$}Xf4Qu|9}4v;V>~UAutIB1uG5%0vZJX1Qd*Ji=f=R`J3~d bWr6|Fzx*IOqG<#OPZEU6;7((40s;sCZpw)y diff --git a/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs b/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs index 00e1fbfba6..fa24b34684 100644 --- a/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs +++ b/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; using Xunit; @@ -134,7 +135,8 @@ namespace Microsoft.AspNetCore.Tests ""Certificates"": { ""TestCert"": { ""Source"": ""File"", - ""Path"": ""TestArtifacts/Certificate.pfx"" + ""Path"": ""testCert.pfx"", + ""Password"": ""testPassword"" } } } @@ -143,13 +145,8 @@ namespace Microsoft.AspNetCore.Tests { var port = GetWebHostPort(webHost); - Assert.NotEqual(0, port); - - using (var client = new HttpClient(new HttpClientHandler { ServerCertificateCustomValidationCallback = (msg, cert, chain, errors) => true })) - { - var response = await client.GetAsync($"https://127.0.0.1:{port}"); - response.EnsureSuccessStatusCode(); - } + var response = await HttpClientSlim.GetStringAsync($"https://127.0.0.1:{port}", validateCertificate: false); + Assert.Equal("Hello, World!", response); } } finally @@ -172,7 +169,8 @@ namespace Microsoft.AspNetCore.Tests ""Port"": 0, ""Certificate"": { ""Source"": ""File"", - ""Path"": ""TestArtifacts/Certificate.pfx"", + ""Path"": ""testCert.pfx"", + ""Password"": ""testPassword"" } } } @@ -183,13 +181,8 @@ namespace Microsoft.AspNetCore.Tests { var port = GetWebHostPort(webHost); - Assert.NotEqual(0, port); - - using (var client = new HttpClient(new HttpClientHandler { ServerCertificateCustomValidationCallback = (msg, cert, chain, errors) => true })) - { - var response = await client.GetAsync($"https://127.0.0.1:{port}"); - response.EnsureSuccessStatusCode(); - } + var response = await HttpClientSlim.GetStringAsync($"https://127.0.0.1:{port}", validateCertificate: false); + Assert.Equal("Hello, World!", response); } } finally diff --git a/test/Microsoft.AspNetCore.FunctionalTests/testCert.pfx b/test/Microsoft.AspNetCore.FunctionalTests/testCert.pfx new file mode 100644 index 0000000000000000000000000000000000000000..7118908c2d730670c16e9f8b2c532a262c951989 GIT binary patch literal 2483 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 literal 0 HcmV?d00001