From 09533f1e3a1caee96eb651006f88700629fac376 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 10 Nov 2020 15:40:15 +0000 Subject: [PATCH] Add Custom Certificate settings (#537) --- WireMock.Net Solution.sln | 7 ++ .../Program.cs | 36 +++++++ ....Console.NETCoreApp3WithCertificate.csproj | 18 ++++ .../base64_encoded.cer | 28 +++++ .../base64_encoded.privatekey | Bin 0 -> 3785 bytes .../example.pfx | Bin 0 -> 4150 bytes src/WireMock.Net/Http/HttpClientHelper.cs | 2 +- .../HttpsCertificate/CertificateLoader.cs | 100 ++++++++++++++++++ .../ClientCertificateHelper.cs | 42 -------- .../Owin/AspNetCoreSelfHost.NETStandard.cs | 28 +++-- .../Owin/AspNetCoreSelfHost.NETStandard13.cs | 26 +++-- src/WireMock.Net/Owin/AspNetCoreSelfHost.cs | 20 ++-- src/WireMock.Net/Owin/HostUrlDetails.cs | 15 +++ src/WireMock.Net/Owin/HostUrlOptions.cs | 13 ++- .../Owin/IWireMockMiddlewareOptions.cs | 12 +++ .../Owin/WireMockMiddlewareOptions.cs | 20 ++++ src/WireMock.Net/Server/WireMockServer.cs | 9 ++ .../Settings/IWireMockCertificateSettings.cs | 44 ++++++++ .../Settings/IWireMockServerSettings.cs | 16 +++ .../Settings/WireMockCertificateSettings.cs | 36 +++++++ .../Settings/WireMockServerSettings.cs | 12 ++- .../Settings/WireMockServerSettingsParser.cs | 19 +++- src/WireMock.Net/Util/PortUtils.cs | 21 ++-- .../WireMock.Net.Tests/Util/PortUtilsTests.cs | 57 ++++++---- 24 files changed, 478 insertions(+), 103 deletions(-) create mode 100644 examples/WireMock.Net.Console.NETCoreApp3WithCertificate/Program.cs create mode 100644 examples/WireMock.Net.Console.NETCoreApp3WithCertificate/WireMock.Net.Console.NETCoreApp3WithCertificate.csproj create mode 100644 examples/WireMock.Net.Console.NETCoreApp3WithCertificate/base64_encoded.cer create mode 100644 examples/WireMock.Net.Console.NETCoreApp3WithCertificate/base64_encoded.privatekey create mode 100644 examples/WireMock.Net.Console.NETCoreApp3WithCertificate/example.pfx create mode 100644 src/WireMock.Net/HttpsCertificate/CertificateLoader.cs delete mode 100644 src/WireMock.Net/HttpsCertificate/ClientCertificateHelper.cs create mode 100644 src/WireMock.Net/Owin/HostUrlDetails.cs create mode 100644 src/WireMock.Net/Settings/IWireMockCertificateSettings.cs create mode 100644 src/WireMock.Net/Settings/WireMockCertificateSettings.cs diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln index 104064bd3..e295c7e7c 100644 --- a/WireMock.Net Solution.sln +++ b/WireMock.Net Solution.sln @@ -71,6 +71,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.WebApplication EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.WebApplication.NETCore3", "examples\WireMock.Net.WebApplication.NETCore3\WireMock.Net.WebApplication.NETCore3.csproj", "{E1C56967-3DC7-46CB-A1DF-B13167A0D9D4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Console.NETCoreApp3WithCertificate", "examples\WireMock.Net.Console.NETCoreApp3WithCertificate\WireMock.Net.Console.NETCoreApp3WithCertificate.csproj", "{925E421A-1B3F-4202-B48F-734743573A4B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -173,6 +175,10 @@ Global {E1C56967-3DC7-46CB-A1DF-B13167A0D9D4}.Debug|Any CPU.Build.0 = Debug|Any CPU {E1C56967-3DC7-46CB-A1DF-B13167A0D9D4}.Release|Any CPU.ActiveCfg = Release|Any CPU {E1C56967-3DC7-46CB-A1DF-B13167A0D9D4}.Release|Any CPU.Build.0 = Release|Any CPU + {925E421A-1B3F-4202-B48F-734743573A4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {925E421A-1B3F-4202-B48F-734743573A4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {925E421A-1B3F-4202-B48F-734743573A4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {925E421A-1B3F-4202-B48F-734743573A4B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -202,6 +208,7 @@ Global {B6269AAC-170A-4346-8B9A-579DED3D9A95} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {6F38CB3A-6DA1-408A-AECD-E434523C2838} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {E1C56967-3DC7-46CB-A1DF-B13167A0D9D4} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {925E421A-1B3F-4202-B48F-734743573A4B} = {985E0ADB-D4B4-473A-AA40-567E279B7946} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} diff --git a/examples/WireMock.Net.Console.NETCoreApp3WithCertificate/Program.cs b/examples/WireMock.Net.Console.NETCoreApp3WithCertificate/Program.cs new file mode 100644 index 000000000..54878ddc2 --- /dev/null +++ b/examples/WireMock.Net.Console.NETCoreApp3WithCertificate/Program.cs @@ -0,0 +1,36 @@ +using WireMock.Logging; +using WireMock.Server; +using WireMock.Settings; + +namespace WireMock.Net.Console.NETCoreApp3WithCertificate +{ + class Program + { + static void Main(string[] args) + { + string url = "https://localhost:8433/"; + + var server = WireMockServer.Start(new WireMockServerSettings + { + Urls = new[] { url }, + StartAdminInterface = true, + Logger = new WireMockConsoleLogger(), + CertificateSettings = new WireMockCertificateSettings + { + X509StoreName = "My", + X509StoreLocation = "CurrentUser", + X509StoreThumbprintOrSubjectName = "FE16586076A8B3F3E2F1466803A6C4C7CA35455B" + + // X509CertificateFilePath = "example.pfx", + // X509CertificatePassword = "wiremock" + } + + }); + System.Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls)); + + System.Console.WriteLine("Press any key to stop the server"); + System.Console.ReadKey(); + server.Stop(); + } + } +} \ No newline at end of file diff --git a/examples/WireMock.Net.Console.NETCoreApp3WithCertificate/WireMock.Net.Console.NETCoreApp3WithCertificate.csproj b/examples/WireMock.Net.Console.NETCoreApp3WithCertificate/WireMock.Net.Console.NETCoreApp3WithCertificate.csproj new file mode 100644 index 000000000..3e04aa7e3 --- /dev/null +++ b/examples/WireMock.Net.Console.NETCoreApp3WithCertificate/WireMock.Net.Console.NETCoreApp3WithCertificate.csproj @@ -0,0 +1,18 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + PreserveNewest + + + + diff --git a/examples/WireMock.Net.Console.NETCoreApp3WithCertificate/base64_encoded.cer b/examples/WireMock.Net.Console.NETCoreApp3WithCertificate/base64_encoded.cer new file mode 100644 index 000000000..4826bb012 --- /dev/null +++ b/examples/WireMock.Net.Console.NETCoreApp3WithCertificate/base64_encoded.cer @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEsDCCApigAwIBAgIQJbH6hSGKdoFI0B7qCIOK7jANBgkqhkiG9w0BAQUFADAU +MRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMjAxMDMwMjMwMDAwWhcNMzAxMTA2MjMw +MDAwWjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCl5fQSrRgT3Q6WoULR98Y+rrDWtTTgVpbLU04G0hLZ4yUeP7Wa +yuVbvx7zX8XT4lA8Hu5T/GG91U077JcSSEjnPBFsh4hE7FkRoSYIEW6BFG7D7eUG +dGHnDV8UkSRQ97LJPyjXuHVDJzNDJ9xQGMzOZ4n8vQ7SEKBw9hRG2ugkP5b2jVIN +e1E549tq2jnIVpKCZ4+prf64ZLsaokX7VHe+b/CW3GoAqUUaUjdTpAQ7LpypJuFz +415enOrKQe+UEBdqhGlgcC/O/Bw0uq4qVk+NNe5DEINVwoYs9XjNdzxuIkkAtcCt +avTEzhHf8zWYLb5Nt2DIOcRGVELvRhsBX4um5f7dOGzMbXzBfUdjkP2O4hi6crhm +Hba5bNkj4Zw2EHR9Xua3nadGCj22z0vpMKP2gXdFVnxFqQlaUWBLtwwN9p6tCQHl +kU7wypvOHUsMa2Ojg5eZP4RpYFvZG3kkc9zTZCSakgw2n0ampBbvxPP11/AYIXtz +HKu3CKcpjVQ+lE0DAU/Mm77QJ24TMbXmAydwCf1UCdFbDUZhdM9lspHvA0J9eiCv +LOE94BrpVKuZ6TrAW0LZjAmBnkqYQAewhTW7GSgARE+QQcwfyu03Ck7id3Zt4FeQ +sQDo0NNj7zQOy3Y1GK0ZYAVZv/GUeHMkxpClSWPoub/f5SJ4YzD5Il0cQQIDAQAB +MA0GCSqGSIb3DQEBBQUAA4ICAQBd91xfUepnWcKwmupie2h1CAAQZEunyW78i++t +evABfBu0TgV4s6Xe0umFv9V4r+O+rrF3ddSudbSOPBEb0Ooe+e3YGlNk1JrI1EEn +fhb0YI8bMfBNpl85yNqxgByra7JF2mG4qbAnjrCs/PZkXo/34N29SY6dyZ7mffR3 +r/l01Rdm3ogRwGkiMUeKb3iGwLUy1T55svuI3Zc13N+NJT1s9NqpwWeK/jFK/WRN +5Hi9W3DmlGCYAwFPCyBaQagxpGuGIpNsU0hKp86W5EvJpBpmCihfwlydH8ZbkHJ9 +jx2UDgTCaDzmaiKysiTP2HHDBsReL4tjakBksa9jkTfy5ajB53F3aUVs4jvTA46L +w8wcAJlRPBz5siBrv4CH/0lBMyNeYzuqmDY3ulF4IMKNb5Kk9Ye4Pt0474z50A4v +fSah+9iwI/mubaJ5tK522AtWtUoOIAswIwpDQyNeJPOggyzT2Y2OYZdGuFAoMYuq +ZD58k4Yo+vky9K88l8NuzNJJvtgTKtT+/9qfMucxFmnvwbKEEULP3sw1FUKkPtM4 +f242FIV/XnOeloDmhGGeTB7aODB+gGCvgmOH92njjUEIv+SnYQkflQaRhhyNIACi +ZvWlP/96H+X4fUG5kVNBHY021ZWmurUDqVxWUaswg63+DfsZcYtt6wgxiAN4ssXG +wLnLPw== +-----END CERTIFICATE----- diff --git a/examples/WireMock.Net.Console.NETCoreApp3WithCertificate/base64_encoded.privatekey b/examples/WireMock.Net.Console.NETCoreApp3WithCertificate/base64_encoded.privatekey new file mode 100644 index 0000000000000000000000000000000000000000..3e6949a598eaf26f0a9dfb2af429537739c9a116 GIT binary patch literal 3785 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∋x`Q(+wA83gUd$VJEHbo0yOUk-*Uuc^;We`~T5ERTmZAK{2ZT+lWVLu{ zb^a`|Yq6MlR)qvs74P+fH8c55HwX0@TcM-2ysSnRpu3woZXKD?U8x=(j-*ruKp_HIYf{`~$RJN=ZlR|X zlLGm3tU#oqvw=3iBv9D!>t7^z0!(2<(ZUP9R&)JoDfh$ub?b)sOLicBT@AXe&3Q9j z359PH8Q-Dlf^y$qal>mlKF;OezpVXXFMy{?43H-p_7FY^VXCb*(@eY^n`4};u zXsTrESVg9`LbJ?HZ?)%Lg}et*2I11X^KEFu9k>2(k7RG zUk(z@=Yh#-HvMFOcYnWKuw|r7o$(kUyd;~;oayed`NXF!a>Dve@R@0F_k#3yOX2S( zg(RSF;U57mcB7cHK{kffqHreRX0}T88#igB_Dmo6f}*7pIq#ftxeSfBpO8}C%mr{g zVW^%k)KaO$oDL)4T>-JDx>$*(hz<3K3EC)MQHn$vz)$>R)dLykWq}E}y#PaeHx2m> ztd~x;mf?M0si3jsaz3}JL03~!B64N0FGMM3jc>f39|)EKvxA91go2d#c=rhC{U+lB u!1uht?Wnb981qM=3^^mQ1oR3DA5`XzA;wfEQ{h4}`nB2-e3gG+p#K8Q5|M5I literal 0 HcmV?d00001 diff --git a/src/WireMock.Net/Http/HttpClientHelper.cs b/src/WireMock.Net/Http/HttpClientHelper.cs index 1eb163e41..eab1ac385 100644 --- a/src/WireMock.Net/Http/HttpClientHelper.cs +++ b/src/WireMock.Net/Http/HttpClientHelper.cs @@ -41,7 +41,7 @@ public static HttpClient CreateHttpClient(IProxyAndRecordSettings settings) { handler.ClientCertificateOptions = ClientCertificateOption.Manual; - var x509Certificate2 = ClientCertificateHelper.GetCertificate(settings.ClientX509Certificate2ThumbprintOrSubjectName); + var x509Certificate2 = CertificateLoader.LoadCertificate(settings.ClientX509Certificate2ThumbprintOrSubjectName); handler.ClientCertificates.Add(x509Certificate2); } diff --git a/src/WireMock.Net/HttpsCertificate/CertificateLoader.cs b/src/WireMock.Net/HttpsCertificate/CertificateLoader.cs new file mode 100644 index 000000000..409bbb6c6 --- /dev/null +++ b/src/WireMock.Net/HttpsCertificate/CertificateLoader.cs @@ -0,0 +1,100 @@ +using System; +using System.IO; +using System.Security.Cryptography.X509Certificates; + +namespace WireMock.HttpsCertificate +{ + internal static class CertificateLoader + { + /// + /// Used by the WireMock.Net server + /// + public static X509Certificate2 LoadCertificate( + string storeName, + string storeLocation, + string thumbprintOrSubjectName, + string filePath, + string password, + string host) + { + if (!string.IsNullOrEmpty(storeName) && !string.IsNullOrEmpty(storeLocation)) + { + var thumbprintOrSubjectNameOrHost = thumbprintOrSubjectName ?? host; + + var certStore = new X509Store((StoreName)Enum.Parse(typeof(StoreName), storeName), (StoreLocation)Enum.Parse(typeof(StoreLocation), storeLocation)); + try + { + certStore.Open(OpenFlags.ReadOnly); + + // Attempt to find by Thumbprint first + var matchingCertificates = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprintOrSubjectNameOrHost, false); + if (matchingCertificates.Count == 0) + { + // Fallback to SubjectName + matchingCertificates = certStore.Certificates.Find(X509FindType.FindBySubjectName, thumbprintOrSubjectNameOrHost, false); + if (matchingCertificates.Count == 0) + { + // No certificates matched the search criteria. + throw new FileNotFoundException($"No Certificate found with in store '{storeName}', location '{storeLocation}' for Thumbprint or SubjectName '{thumbprintOrSubjectNameOrHost}'."); + } + } + + // Use the first matching certificate. + return matchingCertificates[0]; + } + finally + { +#if NETSTANDARD || NET46 + certStore.Dispose(); +#else + certStore.Close(); +#endif + } + } + + if (!string.IsNullOrEmpty(filePath) && !string.IsNullOrEmpty(password)) + { + return new X509Certificate2(filePath, password); + } + + throw new InvalidOperationException("X509StoreName and X509StoreLocation OR X509CertificateFilePath and X509CertificatePassword are mandatory."); + } + + /// + /// Used for Proxy + /// + public static X509Certificate2 LoadCertificate(string thumbprintOrSubjectName) + { + var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine); + try + { + // Certificate must be in the local machine store + certStore.Open(OpenFlags.ReadOnly); + + // Attempt to find by Thumbprint first + var matchingCertificates = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprintOrSubjectName, false); + if (matchingCertificates.Count == 0) + { + // Fallback to SubjectName + matchingCertificates = certStore.Certificates.Find(X509FindType.FindBySubjectName, thumbprintOrSubjectName, false); + if (matchingCertificates.Count == 0) + { + // No certificates matched the search criteria. + throw new FileNotFoundException("No certificate found with specified Thumbprint or SubjectName.", thumbprintOrSubjectName); + } + } + + // Use the first matching certificate. + return matchingCertificates[0]; + } + finally + { +#if NETSTANDARD || NET46 + certStore.Dispose(); +#else + certStore.Close(); +#endif + } + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net/HttpsCertificate/ClientCertificateHelper.cs b/src/WireMock.Net/HttpsCertificate/ClientCertificateHelper.cs deleted file mode 100644 index bd81e4146..000000000 --- a/src/WireMock.Net/HttpsCertificate/ClientCertificateHelper.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.IO; -using System.Security.Cryptography.X509Certificates; - -namespace WireMock.HttpsCertificate -{ - internal static class ClientCertificateHelper - { - public static X509Certificate2 GetCertificate(string thumbprintOrSubjectName) - { - X509Store certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine); - try - { - // Certificate must be in the local machine store - certStore.Open(OpenFlags.ReadOnly); - - // Attempt to find by thumbprint first - var matchingCertificates = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprintOrSubjectName, false); - if (matchingCertificates.Count == 0) - { - // Fallback to subject name - matchingCertificates = certStore.Certificates.Find(X509FindType.FindBySubjectName, thumbprintOrSubjectName, false); - if (matchingCertificates.Count == 0) - { - // No certificates matched the search criteria. - throw new FileNotFoundException("No certificate found with specified Thumbprint or SubjectName.", thumbprintOrSubjectName); - } - } - - // Use the first matching certificate. - return matchingCertificates[0]; - } - finally - { -#if NETSTANDARD || NET46 - certStore.Dispose(); -#else - certStore.Close(); -#endif - } - } - } -} \ No newline at end of file diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard.cs index 19b9a15a0..aefc35e59 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard.cs +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard.cs @@ -1,10 +1,10 @@ #if USE_ASPNETCORE && !NETSTANDARD1_3 -using System; using System.Collections.Generic; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using WireMock.HttpsCertificate; namespace WireMock.Owin { @@ -18,20 +18,34 @@ private static void SetKestrelOptionsLimits(KestrelServerOptions options) options.Limits.MaxResponseBufferSize = null; } - private static void SetHttpsAndUrls(KestrelServerOptions options, ICollection<(string Url, int Port)> urlDetails) + private static void SetHttpsAndUrls(KestrelServerOptions kestrelOptions, IWireMockMiddlewareOptions wireMockMiddlewareOptions, IEnumerable urlDetails) { - foreach (var detail in urlDetails) + foreach (var urlDetail in urlDetails) { - if (detail.Url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + if (urlDetail.IsHttps) { - options.Listen(System.Net.IPAddress.Any, detail.Port, listenOptions => + kestrelOptions.Listen(System.Net.IPAddress.Any, urlDetail.Port, listenOptions => { - listenOptions.UseHttps(); + if (wireMockMiddlewareOptions.CustomCertificateDefined) + { + listenOptions.UseHttps(CertificateLoader.LoadCertificate( + wireMockMiddlewareOptions.X509StoreName, + wireMockMiddlewareOptions.X509StoreLocation, + wireMockMiddlewareOptions.X509ThumbprintOrSubjectName, + wireMockMiddlewareOptions.X509CertificateFilePath, + wireMockMiddlewareOptions.X509CertificatePassword, + urlDetail.Host) + ); + } + else + { + listenOptions.UseHttps(); + } }); } else { - options.Listen(System.Net.IPAddress.Any, detail.Port); + kestrelOptions.Listen(System.Net.IPAddress.Any, urlDetail.Port); } } } diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard13.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard13.cs index 80798cb7a..0d2bf3512 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard13.cs +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard13.cs @@ -1,7 +1,5 @@ #if USE_ASPNETCORE && NETSTANDARD1_3 -using System; using System.Collections.Generic; -using System.Linq; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.Extensions.Configuration; @@ -19,12 +17,28 @@ private static void SetKestrelOptionsLimits(KestrelServerOptions options) options.Limits.MaxResponseBufferSize = null; } - private static void SetHttpsAndUrls(KestrelServerOptions options, ICollection<(string Url, int Port)> urlDetails) + private static void SetHttpsAndUrls(KestrelServerOptions options, IWireMockMiddlewareOptions wireMockMiddlewareOptions, IEnumerable urlDetails) { - var urls = urlDetails.Select(u => u.Url); - if (urls.Any(u => u.StartsWith("https://", StringComparison.OrdinalIgnoreCase))) + foreach (var urlDetail in urlDetails) { - options.UseHttps(PublicCertificateHelper.GetX509Certificate2()); + if (urlDetail.IsHttps) + { + if (wireMockMiddlewareOptions.CustomCertificateDefined) + { + options.UseHttps(CertificateLoader.LoadCertificate( + wireMockMiddlewareOptions.X509StoreName, + wireMockMiddlewareOptions.X509StoreLocation, + wireMockMiddlewareOptions.X509ThumbprintOrSubjectName, + wireMockMiddlewareOptions.X509CertificateFilePath, + wireMockMiddlewareOptions.X509CertificatePassword, + urlDetail.Host) + ); + } + else + { + options.UseHttps(PublicCertificateHelper.GetX509Certificate2()); + } + } } } } diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs index 651831591..717200d69 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs @@ -18,7 +18,7 @@ namespace WireMock.Owin internal partial class AspNetCoreSelfHost : IOwinSelfHost { private readonly CancellationTokenSource _cts = new CancellationTokenSource(); - private readonly IWireMockMiddlewareOptions _options; + private readonly IWireMockMiddlewareOptions _wireMockMiddlewareOptions; private readonly IWireMockLogger _logger; private readonly HostUrlOptions _urlOptions; @@ -33,14 +33,14 @@ internal partial class AspNetCoreSelfHost : IOwinSelfHost public Exception RunningException => _runningException; - public AspNetCoreSelfHost([NotNull] IWireMockMiddlewareOptions options, [NotNull] HostUrlOptions urlOptions) + public AspNetCoreSelfHost([NotNull] IWireMockMiddlewareOptions wireMockMiddlewareOptions, [NotNull] HostUrlOptions urlOptions) { - Check.NotNull(options, nameof(options)); + Check.NotNull(wireMockMiddlewareOptions, nameof(wireMockMiddlewareOptions)); Check.NotNull(urlOptions, nameof(urlOptions)); - _logger = options.Logger ?? new WireMockConsoleLogger(); + _logger = wireMockMiddlewareOptions.Logger ?? new WireMockConsoleLogger(); - _options = options; + _wireMockMiddlewareOptions = wireMockMiddlewareOptions; _urlOptions = urlOptions; } @@ -61,7 +61,7 @@ public Task StartAsync() .ConfigureAppConfigurationUsingEnvironmentVariables() .ConfigureServices(services => { - services.AddSingleton(_options); + services.AddSingleton(_wireMockMiddlewareOptions); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -70,17 +70,17 @@ public Task StartAsync() { appBuilder.UseMiddleware(); - _options.PreWireMockMiddlewareInit?.Invoke(appBuilder); + _wireMockMiddlewareOptions.PreWireMockMiddlewareInit?.Invoke(appBuilder); appBuilder.UseMiddleware(); - _options.PostWireMockMiddlewareInit?.Invoke(appBuilder); + _wireMockMiddlewareOptions.PostWireMockMiddlewareInit?.Invoke(appBuilder); }) .UseKestrel(options => { SetKestrelOptionsLimits(options); - SetHttpsAndUrls(options, _urlOptions.GetDetails()); + SetHttpsAndUrls(options, _wireMockMiddlewareOptions, _urlOptions.GetDetails()); }) .ConfigureKestrelServerOptions() @@ -107,7 +107,7 @@ private Task RunHost(CancellationToken token) { Urls.Add(address.Replace("0.0.0.0", "localhost")); - PortUtils.TryExtract(address, out string protocol, out string host, out int port); + PortUtils.TryExtract(address, out bool isHttps, out string protocol, out string host, out int port); Ports.Add(port); } diff --git a/src/WireMock.Net/Owin/HostUrlDetails.cs b/src/WireMock.Net/Owin/HostUrlDetails.cs new file mode 100644 index 000000000..6bd8826ca --- /dev/null +++ b/src/WireMock.Net/Owin/HostUrlDetails.cs @@ -0,0 +1,15 @@ +namespace WireMock.Owin +{ + internal class HostUrlDetails + { + public bool IsHttps { get; set; } + + public string Url { get; set; } + + public string Protocol { get; set; } + + public string Host { get; set; } + + public int Port { get; set; } + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Owin/HostUrlOptions.cs b/src/WireMock.Net/Owin/HostUrlOptions.cs index e85c03245..dd94ce331 100644 --- a/src/WireMock.Net/Owin/HostUrlOptions.cs +++ b/src/WireMock.Net/Owin/HostUrlOptions.cs @@ -5,26 +5,29 @@ namespace WireMock.Owin { internal class HostUrlOptions { + private const string LOCALHOST = "localhost"; + public ICollection Urls { get; set; } public int? Port { get; set; } public bool UseSSL { get; set; } - public ICollection<(string Url, int Port)> GetDetails() + public ICollection GetDetails() { - var list = new List<(string Url, int Port)>(); + var list = new List(); if (Urls == null) { int port = Port > 0 ? Port.Value : FindFreeTcpPort(); - list.Add(($"{(UseSSL ? "https" : "http")}://localhost:{port}", port)); + string protocol = UseSSL ? "https" : "http"; + list.Add(new HostUrlDetails { IsHttps = UseSSL, Url = $"{protocol}://{LOCALHOST}:{port}", Protocol = protocol, Host = LOCALHOST, Port = port }); } else { foreach (string url in Urls) { - PortUtils.TryExtract(url, out string protocol, out string host, out int port); - list.Add((url, port)); + PortUtils.TryExtract(url, out bool isHttps, out string protocol, out string host, out int port); + list.Add(new HostUrlDetails { IsHttps = isHttps, Url = url, Protocol = protocol, Host = host, Port = port }); } } diff --git a/src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs b/src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs index 44f900e2f..f56eb877c 100644 --- a/src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs +++ b/src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs @@ -47,5 +47,17 @@ internal interface IWireMockMiddlewareOptions bool? DisableRequestBodyDecompressing { get; set; } bool? HandleRequestsSynchronously { get; set; } + + string X509StoreName { get; set; } + + string X509StoreLocation { get; set; } + + string X509ThumbprintOrSubjectName { get; set; } + + string X509CertificateFilePath { get; set; } + + string X509CertificatePassword { get; set; } + + bool CustomCertificateDefined { get; } } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs index e6b1b138b..8b9698b15 100644 --- a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs +++ b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs @@ -53,5 +53,25 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions /// public bool? HandleRequestsSynchronously { get; set; } + + /// + public string X509StoreName { get; set; } + + /// + public string X509StoreLocation { get; set; } + + /// + public string X509ThumbprintOrSubjectName { get; set; } + + /// + public string X509CertificateFilePath { get; set; } + + /// + public string X509CertificatePassword { get; set; } + + /// + public bool CustomCertificateDefined => + !string.IsNullOrEmpty(X509StoreName) && !string.IsNullOrEmpty(X509StoreLocation) || + !string.IsNullOrEmpty(X509CertificateFilePath) && !string.IsNullOrEmpty(X509CertificatePassword); } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.cs b/src/WireMock.Net/Server/WireMockServer.cs index 8702c338f..a58adde89 100644 --- a/src/WireMock.Net/Server/WireMockServer.cs +++ b/src/WireMock.Net/Server/WireMockServer.cs @@ -230,6 +230,15 @@ protected WireMockServer(IWireMockServerSettings settings) _options.DisableJsonBodyParsing = _settings.DisableJsonBodyParsing; _options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously; + if (settings.CustomCertificateDefined) + { + _options.X509StoreName = settings.CertificateSettings.X509StoreName; + _options.X509StoreLocation = settings.CertificateSettings.X509StoreLocation; + _options.X509ThumbprintOrSubjectName = settings.CertificateSettings.X509StoreThumbprintOrSubjectName; + _options.X509CertificateFilePath = settings.CertificateSettings.X509CertificateFilePath; + _options.X509CertificatePassword = settings.CertificateSettings.X509CertificatePassword; + } + _matcherMapper = new MatcherMapper(_settings); _mappingConverter = new MappingConverter(_matcherMapper); diff --git a/src/WireMock.Net/Settings/IWireMockCertificateSettings.cs b/src/WireMock.Net/Settings/IWireMockCertificateSettings.cs new file mode 100644 index 000000000..d2d476bb4 --- /dev/null +++ b/src/WireMock.Net/Settings/IWireMockCertificateSettings.cs @@ -0,0 +1,44 @@ +namespace WireMock.Settings +{ + /// + /// If https is used, these settings can be used to configure the CertificateSettings in case a custom certificate instead the default .NET certificate should be used. + /// + /// X509StoreName and X509StoreLocation should be defined + /// OR + /// X509CertificateFilePath and X509CertificatePassword should be defined + /// + public interface IWireMockCertificateSettings + { + /// + /// X509 StoreName (AddressBook, AuthRoot, CertificateAuthority, My, Root, TrustedPeople or TrustedPublisher) + /// + string X509StoreName { get; set; } + + /// + /// X509 StoreLocation (CurrentUser or LocalMachine) + /// + string X509StoreLocation { get; set; } + + /// + /// X509 Thumbprint or SubjectName (if not defined, the 'host' is used) + /// + string X509StoreThumbprintOrSubjectName { get; set; } + + /// + /// X509Certificate FilePath + /// + string X509CertificateFilePath { get; set; } + + /// + /// X509Certificate Password + /// + string X509CertificatePassword { get; set; } + + /// + /// X509StoreName and X509StoreLocation should be defined + /// OR + /// X509CertificateFilePath and X509CertificatePassword should be defined + /// + bool IsDefined { get; } + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Settings/IWireMockServerSettings.cs b/src/WireMock.Net/Settings/IWireMockServerSettings.cs index 65ce9a30c..ac511676d 100644 --- a/src/WireMock.Net/Settings/IWireMockServerSettings.cs +++ b/src/WireMock.Net/Settings/IWireMockServerSettings.cs @@ -170,5 +170,21 @@ public interface IWireMockServerSettings /// [PublicAPI] bool? ThrowExceptionWhenMatcherFails { get; set; } + + /// + /// If https is used, these settings can be used to configure the CertificateSettings in case a custom certificate instead the default .NET certificate should be used. + /// + /// X509StoreName and X509StoreLocation should be defined + /// OR + /// X509CertificateFilePath and X509CertificatePassword should be defined + /// + [PublicAPI] + IWireMockCertificateSettings CertificateSettings { get; set; } + + /// + /// Defines if custom CertificateSettings are defined + /// + [PublicAPI] + bool CustomCertificateDefined { get; } } } \ No newline at end of file diff --git a/src/WireMock.Net/Settings/WireMockCertificateSettings.cs b/src/WireMock.Net/Settings/WireMockCertificateSettings.cs new file mode 100644 index 000000000..a9234c308 --- /dev/null +++ b/src/WireMock.Net/Settings/WireMockCertificateSettings.cs @@ -0,0 +1,36 @@ +using JetBrains.Annotations; + +namespace WireMock.Settings +{ + /// + /// + /// + public class WireMockCertificateSettings : IWireMockCertificateSettings + { + /// + [PublicAPI] + public string X509StoreName { get; set; } + + /// + [PublicAPI] + public string X509StoreLocation { get; set; } + + /// + [PublicAPI] + public string X509StoreThumbprintOrSubjectName { get; set; } + + /// + [PublicAPI] + public string X509CertificateFilePath { get; set; } + + /// + [PublicAPI] + public string X509CertificatePassword { get; set; } + + /// + [PublicAPI] + public bool IsDefined => + !string.IsNullOrEmpty(X509StoreName) && !string.IsNullOrEmpty(X509StoreLocation) || + !string.IsNullOrEmpty(X509CertificateFilePath) && !string.IsNullOrEmpty(X509CertificatePassword); + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Settings/WireMockServerSettings.cs b/src/WireMock.Net/Settings/WireMockServerSettings.cs index ac1beb14c..1f655c4cf 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettings.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettings.cs @@ -1,6 +1,6 @@ -using HandlebarsDotNet; +using System; +using HandlebarsDotNet; using JetBrains.Annotations; -using System; using Newtonsoft.Json; using WireMock.Handlers; using WireMock.Logging; @@ -121,5 +121,13 @@ public class WireMockServerSettings : IWireMockServerSettings /// [PublicAPI] public bool? ThrowExceptionWhenMatcherFails { get; set; } + + /// + [PublicAPI] + public IWireMockCertificateSettings CertificateSettings { get; set; } + + /// + [PublicAPI] + public bool CustomCertificateDefined => CertificateSettings?.IsDefined == true; } } \ No newline at end of file diff --git a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs index be16b68a3..ff00bac5b 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs @@ -60,12 +60,12 @@ public static IWireMockServerSettings ParseArguments([NotNull] string[] args, [C settings.Urls = parser.GetValues("Urls", new[] { "http://*:9091/" }); } - string proxyURL = parser.GetStringValue("ProxyURL"); - if (!string.IsNullOrEmpty(proxyURL)) + string proxyUrl = parser.GetStringValue("ProxyURL") ?? parser.GetStringValue("ProxyUrl"); + if (!string.IsNullOrEmpty(proxyUrl)) { settings.ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = proxyURL, + Url = proxyUrl, SaveMapping = parser.GetBoolValue("SaveMapping"), SaveMappingToFile = parser.GetBoolValue("SaveMappingToFile"), SaveMappingForStatusCodePattern = parser.GetStringValue("SaveMappingForStatusCodePattern"), @@ -87,6 +87,19 @@ public static IWireMockServerSettings ParseArguments([NotNull] string[] args, [C } } + var certificateSettings = new WireMockCertificateSettings + { + X509StoreName = parser.GetStringValue("X509StoreName"), + X509StoreLocation = parser.GetStringValue("X509StoreLocation"), + X509StoreThumbprintOrSubjectName = parser.GetStringValue("X509StoreThumbprintOrSubjectName"), + X509CertificateFilePath = parser.GetStringValue("X509CertificateFilePath"), + X509CertificatePassword = parser.GetStringValue("X509CertificatePassword") + }; + if (certificateSettings.IsDefined) + { + settings.CertificateSettings = certificateSettings; + } + return settings; } } diff --git a/src/WireMock.Net/Util/PortUtils.cs b/src/WireMock.Net/Util/PortUtils.cs index 83ffe39f6..6bb189984 100644 --- a/src/WireMock.Net/Util/PortUtils.cs +++ b/src/WireMock.Net/Util/PortUtils.cs @@ -1,4 +1,5 @@ -using System.Net; +using System; +using System.Net; using System.Net.Sockets; using System.Text.RegularExpressions; @@ -32,21 +33,23 @@ public static int FindFreeTcpPort() } /// - /// Extract the protocol, host and port from a URL. + /// Extract the if-isHttps, protocol, host and port from a URL. /// - public static bool TryExtract(string url, out string protocol, out string host, out int port) + public static bool TryExtract(string url, out bool isHttps, out string protocol, out string host, out int port) { + isHttps = false; protocol = null; host = null; - port = default(int); + port = default; - Match m = UrlDetailsRegex.Match(url); - if (m.Success) + var match = UrlDetailsRegex.Match(url); + if (match.Success) { - protocol = m.Groups["proto"].Value; - host = m.Groups["host"].Value; + protocol = match.Groups["proto"].Value; + isHttps = protocol.StartsWith("https", StringComparison.OrdinalIgnoreCase); + host = match.Groups["host"].Value; - return int.TryParse(m.Groups["port"].Value, out port); + return int.TryParse(match.Groups["port"].Value, out port); } return false; diff --git a/test/WireMock.Net.Tests/Util/PortUtilsTests.cs b/test/WireMock.Net.Tests/Util/PortUtilsTests.cs index 27f10df8f..af3c18efc 100644 --- a/test/WireMock.Net.Tests/Util/PortUtilsTests.cs +++ b/test/WireMock.Net.Tests/Util/PortUtilsTests.cs @@ -1,3 +1,4 @@ +using FluentAssertions; using NFluent; using WireMock.Util; using Xunit; @@ -13,13 +14,14 @@ public void PortUtils_TryExtract_InvalidUrl_Returns_False() string url = "test"; // Act - bool result = PortUtils.TryExtract(url, out string proto, out string host, out int port); + bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); // Assert - Check.That(result).IsFalse(); - Check.That(proto).IsNull(); - Check.That(host).IsNull(); - Check.That(port).IsEqualTo(default(int)); + result.Should().BeFalse(); + isHttps.Should().BeFalse(); + proto.Should().BeNull(); + host.Should().BeNull(); + port.Should().Be(default(int)); } [Fact] @@ -29,39 +31,58 @@ public void PortUtils_TryExtract_UrlIsMissingPort_Returns_False() string url = "http://0.0.0.0"; // Act - bool result = PortUtils.TryExtract(url, out string proto, out string host, out int port); + bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); // Assert - Check.That(result).IsFalse(); - Check.That(proto).IsNull(); - Check.That(host).IsNull(); - Check.That(port).IsEqualTo(default(int)); + result.Should().BeFalse(); + isHttps.Should().BeFalse(); + proto.Should().BeNull(); + host.Should().BeNull(); + port.Should().Be(default(int)); } [Fact] - public void PortUtils_TryExtract_ValidUrl1_Returns_True() + public void PortUtils_TryExtract_Http_Returns_True() + { + // Assign + string url = "http://wiremock.net:1234"; + + // Act + bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); + + // Assert + result.Should().BeTrue(); + isHttps.Should().BeFalse(); + proto.Should().Be("http"); + host.Should().Be("wiremock.net"); + port.Should().Be(1234); + } + + [Fact] + public void PortUtils_TryExtract_Https_Returns_True() { // Assign string url = "https://wiremock.net:5000"; // Act - bool result = PortUtils.TryExtract(url, out string proto, out string host, out int port); + bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); // Assert - Check.That(result).IsTrue(); - Check.That(proto).IsEqualTo("https"); - Check.That(host).IsEqualTo("wiremock.net"); - Check.That(port).IsEqualTo(5000); + result.Should().BeTrue(); + isHttps.Should().BeTrue(); + proto.Should().Be("https"); + host.Should().Be("wiremock.net"); + port.Should().Be(5000); } [Fact] - public void PortUtils_TryExtract_ValidUrl2_Returns_True() + public void PortUtils_TryExtract_Https0_0_0_0_Returns_True() { // Assign string url = "https://0.0.0.0:5000"; // Act - bool result = PortUtils.TryExtract(url, out string proto, out string host, out int port); + bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); // Assert Check.That(result).IsTrue();