diff --git a/examples/WireMock.Net.Console.Record.NETCoreApp/Program.cs b/examples/WireMock.Net.Console.Record.NETCoreApp/Program.cs
index 94943df5d..55dc79bf1 100644
--- a/examples/WireMock.Net.Console.Record.NETCoreApp/Program.cs
+++ b/examples/WireMock.Net.Console.Record.NETCoreApp/Program.cs
@@ -10,11 +10,12 @@ static void Main(params string[] args)
{
var server = FluentMockServer.Start(new FluentMockServerSettings
{
- Urls = new[] { "http://localhost:9095/", "https://localhost:9096/" },
+ Urls = new[] { "http://localhost:9090/", "https://localhost:9096/" },
StartAdminInterface = true,
ProxyAndRecordSettings = new ProxyAndRecordSettings
{
Url = "https://www.msn.com",
+ X509Certificate2ThumbprintOrSubjectName = "x3bwbapi-dev.nzlb.service.dev",
SaveMapping = true
}
});
diff --git a/examples/WireMock.Net.ConsoleApplication/MainApp.cs b/examples/WireMock.Net.ConsoleApplication/MainApp.cs
index 91bfc9125..07f2c27be 100644
--- a/examples/WireMock.Net.ConsoleApplication/MainApp.cs
+++ b/examples/WireMock.Net.ConsoleApplication/MainApp.cs
@@ -96,17 +96,23 @@ public static void Run()
.RespondWith(Response.Create().WithStatusCode(200).WithBody("partial = 200"));
// http://localhost:8080/any/any?start=1000&stop=1&stop=2
+ //server
+ // .Given(Request.Create().WithPath("/*").UsingGet())
+ // .WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05")
+ // .AtPriority(server.Mappings.Count() + 1)
+ // .RespondWith(Response.Create()
+ // .WithStatusCode(200)
+ // .WithHeader("Content-Type", "application/json")
+ // .WithHeader("Transformed-Postman-Token", "token is {{request.headers.Postman-Token}}")
+ // .WithBody(@"{""msg"": ""Hello world CATCH-ALL on /*, {{request.path}}, bykey={{request.query.start}}, bykey={{request.query.stop}}, byidx0={{request.query.stop.[0]}}, byidx1={{request.query.stop.[1]}}"" }")
+ // .WithTransformer()
+ // .WithDelay(TimeSpan.FromMilliseconds(100))
+ // );
server
.Given(Request.Create().WithPath("/*").UsingGet())
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05")
- .AtPriority(server.Mappings.Count() + 1)
.RespondWith(Response.Create()
- .WithStatusCode(200)
- .WithHeader("Content-Type", "application/json")
- .WithHeader("Transformed-Postman-Token", "token is {{request.headers.Postman-Token}}")
- .WithBody(@"{""msg"": ""Hello world CATCH-ALL on /*, {{request.path}}, bykey={{request.query.start}}, bykey={{request.query.stop}}, byidx0={{request.query.stop.[0]}}, byidx1={{request.query.stop.[1]}}"" }")
- .WithTransformer()
- .WithDelay(TimeSpan.FromMilliseconds(100))
+ .WithProxy("https://semhub-test.lbtest.anznb.co.nz:5200", "D2DBF134A8D06ACCD0E1FAD9B8B28678DF7A9816")
);
System.Console.WriteLine("Press any key to stop the server");
diff --git a/src/WireMock.Net.StandAlone/StandAloneApp.cs b/src/WireMock.Net.StandAlone/StandAloneApp.cs
index 2d75efa3c..a4d3a9d1a 100644
--- a/src/WireMock.Net.StandAlone/StandAloneApp.cs
+++ b/src/WireMock.Net.StandAlone/StandAloneApp.cs
@@ -35,8 +35,8 @@ private class Options
[SwitchArgument("SaveProxyMapping", true, Description = "Save the proxied request and response mapping files in ./__admin/mappings. (default set to true).", Optional = true)]
public bool SaveMapping { get; set; }
- [ValueArgument(typeof(string), "X509Certificate2", Description = "The X509Certificate2 Filename to use.", Optional = true)]
- public string X509Certificate2Filename { get; set; }
+ [ValueArgument(typeof(string), "X509Certificate2ThumbprintOrSubjectName", Description = "The X509Certificate2 Thumbprint or SubjectName to use.", Optional = true)]
+ public string X509Certificate2ThumbprintOrSubjectName { get; set; }
}
///
@@ -91,7 +91,7 @@ public static FluentMockServer Start([NotNull] string[] args)
{
Url = options.ProxyURL,
SaveMapping = options.SaveMapping,
- X509Certificate2Filename = options.X509Certificate2Filename
+ X509Certificate2ThumbprintOrSubjectName = options.X509Certificate2ThumbprintOrSubjectName
};
}
diff --git a/src/WireMock.Net/Admin/Mappings/ResponseModel.cs b/src/WireMock.Net/Admin/Mappings/ResponseModel.cs
index bd12064a1..21b955425 100644
--- a/src/WireMock.Net/Admin/Mappings/ResponseModel.cs
+++ b/src/WireMock.Net/Admin/Mappings/ResponseModel.cs
@@ -84,5 +84,10 @@ public class ResponseModel
///
/// ProxyUrl
public string ProxyUrl { get; set; }
+
+ ///
+ /// The client X509Certificate2 Thumbprint or SubjectName to use.
+ ///
+ public string X509Certificate2ThumbprintOrSubjectName { get; set; }
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Http/CertificateUtil.cs b/src/WireMock.Net/Http/CertificateUtil.cs
new file mode 100644
index 000000000..66211225a
--- /dev/null
+++ b/src/WireMock.Net/Http/CertificateUtil.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Security.Cryptography.X509Certificates;
+
+namespace WireMock.Http
+{
+ internal static class CertificateUtil
+ {
+ 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 Exception($"No certificate found with Thumbprint or SubjectName '{thumbprintOrSubjectName}'");
+ }
+ }
+ // Use the first matching certificate.
+ return matchingCertificates[0];
+ }
+ finally
+ {
+ if (certStore != null)
+ {
+#if NETSTANDARD || NET46
+ certStore.Dispose();
+#else
+ certStore.Close();
+#endif
+ }
+ }
+ }
+ }
+}
diff --git a/src/WireMock.Net/Http/HttpClientHelper.cs b/src/WireMock.Net/Http/HttpClientHelper.cs
index 0ca2915b3..d30fb101f 100644
--- a/src/WireMock.Net/Http/HttpClientHelper.cs
+++ b/src/WireMock.Net/Http/HttpClientHelper.cs
@@ -1,15 +1,19 @@
using System;
using System.Linq;
+using System.Net;
using System.Net.Http;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace WireMock.Http
{
internal static class HttpClientHelper
{
- private static HttpClient CreateHttpClient(string clientX509Certificate2Filename = null)
+
+ private static HttpClient CreateHttpClient(string clientX509Certificate2ThumbprintOrSubjectName = null)
{
- if (!string.IsNullOrEmpty(clientX509Certificate2Filename))
+ if (!string.IsNullOrEmpty(clientX509Certificate2ThumbprintOrSubjectName))
{
#if NETSTANDARD || NET46
var handler = new HttpClientHandler
@@ -19,16 +23,20 @@ private static HttpClient CreateHttpClient(string clientX509Certificate2Filename
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
};
- handler.ClientCertificates.Add(new System.Security.Cryptography.X509Certificates.X509Certificate2(clientX509Certificate2Filename));
- return new HttpClient(handler);
+ var x509Certificate2 = CertificateUtil.GetCertificate(clientX509Certificate2ThumbprintOrSubjectName);
+ handler.ClientCertificates.Add(x509Certificate2);
+
+
#else
var handler = new WebRequestHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
- ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true
+ ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true,
+ AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
- handler.ClientCertificates.Add(new System.Security.Cryptography.X509Certificates.X509Certificate2(clientX509Certificate2Filename));
+ var x509Certificate2 = CertificateUtil.GetCertificate(clientX509Certificate2ThumbprintOrSubjectName);
+ handler.ClientCertificates.Add(x509Certificate2);
return new HttpClient(handler);
#endif
}
@@ -36,9 +44,9 @@ private static HttpClient CreateHttpClient(string clientX509Certificate2Filename
return new HttpClient();
}
- public static async Task SendAsync(RequestMessage requestMessage, string url, string clientX509Certificate2Filename = null)
+ public static async Task SendAsync(RequestMessage requestMessage, string url, string clientX509Certificate2ThumbprintOrSubjectName = null)
{
- var client = CreateHttpClient(clientX509Certificate2Filename);
+ var client = CreateHttpClient(clientX509Certificate2ThumbprintOrSubjectName);
var httpRequestMessage = new HttpRequestMessage(new HttpMethod(requestMessage.Method), url);
@@ -61,21 +69,29 @@ public static async Task SendAsync(RequestMessage requestMessag
}
// Call the URL
- var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead);
-
- // Transform response
- var responseMessage = new ResponseMessage
+ try
{
- StatusCode = (int)httpResponseMessage.StatusCode,
- Body = await httpResponseMessage.Content.ReadAsStringAsync()
- };
+ var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead);
- foreach (var header in httpResponseMessage.Headers)
+
+ // Transform response
+ var responseMessage = new ResponseMessage
+ {
+ StatusCode = (int)httpResponseMessage.StatusCode,
+ Body = await httpResponseMessage.Content.ReadAsStringAsync()
+ };
+
+ foreach (var header in httpResponseMessage.Headers)
+ {
+ responseMessage.AddHeader(header.Key, header.Value.FirstOrDefault());
+ }
+
+ return responseMessage;
+ }
+ catch(Exception ex)
{
- responseMessage.AddHeader(header.Key, header.Value.FirstOrDefault());
+ throw ex;
}
-
- return responseMessage;
}
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net/ResponseBuilders/IProxyResponseBuilder.cs b/src/WireMock.Net/ResponseBuilders/IProxyResponseBuilder.cs
index 820ee030e..bb5a7d812 100644
--- a/src/WireMock.Net/ResponseBuilders/IProxyResponseBuilder.cs
+++ b/src/WireMock.Net/ResponseBuilders/IProxyResponseBuilder.cs
@@ -18,8 +18,8 @@ public interface IProxyResponseBuilder : IStatusCodeResponseBuilder
/// With Proxy URL using X509Certificate2.
///
/// The proxy url.
- /// The X509Certificate2 file to use for client authentication.
+ /// The X509Certificate2 file to use for client authentication.
/// A .
- IResponseBuilder WithProxy([NotNull] string proxyUrl, [CanBeNull] string clientX509Certificate2Filename);
+ IResponseBuilder WithProxy([NotNull] string proxyUrl, [CanBeNull] string clientX509Certificate2ThumbprintOrSubjectName);
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net/ResponseBuilders/Response.cs b/src/WireMock.Net/ResponseBuilders/Response.cs
index 8c7529116..08b7cb3ad 100644
--- a/src/WireMock.Net/ResponseBuilders/Response.cs
+++ b/src/WireMock.Net/ResponseBuilders/Response.cs
@@ -35,9 +35,9 @@ public class Response : IResponseBuilder
public string ProxyUrl { get; private set; }
///
- /// The client X509Certificate2Filename to use.
+ /// The client X509Certificate2 Thumbprint or SubjectName to use.
///
- public string X509Certificate2Filename { get; private set; } = null;
+ public string X509Certificate2ThumbprintOrSubjectName { get; private set; }
///
/// Gets the response message.
@@ -247,7 +247,7 @@ public IResponseBuilder WithDelay(int milliseconds)
/// The proxy url.
/// A .
[PublicAPI]
- public IResponseBuilder WithProxy(string proxyUrl)
+ public IResponseBuilder WithProxy([NotNull] string proxyUrl)
{
Check.NotEmpty(proxyUrl, nameof(proxyUrl));
@@ -259,15 +259,15 @@ public IResponseBuilder WithProxy(string proxyUrl)
/// With Proxy URL.
///
/// The proxy url.
- /// The X509Certificate2 file to use for client authentication.
+ /// The X509Certificate2 file to use for client authentication.
/// A .
- public IResponseBuilder WithProxy(string proxyUrl, string clientX509Certificate2Filename)
+ public IResponseBuilder WithProxy([NotNull] string proxyUrl, [NotNull] string clientX509Certificate2ThumbprintOrSubjectName)
{
Check.NotEmpty(proxyUrl, nameof(proxyUrl));
- Check.NotEmpty(clientX509Certificate2Filename, nameof(clientX509Certificate2Filename));
+ Check.NotEmpty(clientX509Certificate2ThumbprintOrSubjectName, nameof(clientX509Certificate2ThumbprintOrSubjectName));
ProxyUrl = proxyUrl;
- X509Certificate2Filename = clientX509Certificate2Filename;
+ X509Certificate2ThumbprintOrSubjectName = clientX509Certificate2ThumbprintOrSubjectName;
return this;
}
@@ -285,7 +285,10 @@ public async Task ProvideResponseAsync(RequestMessage requestMe
if (ProxyUrl != null)
{
- return await HttpClientHelper.SendAsync(requestMessage, ProxyUrl, X509Certificate2Filename);
+ var requestUri = new Uri(requestMessage.Url);
+ var proxyUri = new Uri(ProxyUrl);
+ var proxyUriWithRequestPathAndQuery = new Uri(proxyUri, requestUri.PathAndQuery);
+ return await HttpClientHelper.SendAsync(requestMessage, proxyUriWithRequestPathAndQuery.AbsoluteUri, X509Certificate2ThumbprintOrSubjectName);
}
if (UseTransformer)
diff --git a/src/WireMock.Net/Server/FluentMockServer.Admin.cs b/src/WireMock.Net/Server/FluentMockServer.Admin.cs
index 1e8c94c6e..59800ea2c 100644
--- a/src/WireMock.Net/Server/FluentMockServer.Admin.cs
+++ b/src/WireMock.Net/Server/FluentMockServer.Admin.cs
@@ -129,7 +129,11 @@ private void InitProxyAndRecord(ProxyAndRecordSettings settings)
private async Task ProxyAndRecordAsync(RequestMessage requestMessage, ProxyAndRecordSettings settings)
{
- var responseMessage = await HttpClientHelper.SendAsync(requestMessage, settings.Url);
+ var requestUri = new Uri(requestMessage.Url);
+ var proxyUri = new Uri(settings.Url);
+ var proxyUriWithRequestPathAndQuery = new Uri(proxyUri, requestUri.PathAndQuery);
+
+ var responseMessage = await HttpClientHelper.SendAsync(requestMessage, proxyUriWithRequestPathAndQuery.AbsoluteUri, settings.X509Certificate2ThumbprintOrSubjectName);
if (settings.SaveMapping)
{
@@ -158,7 +162,7 @@ private ResponseMessage SettingsGet(RequestMessage requestMessage)
var model = new SettingsModel
{
AllowPartialMapping = _options.AllowPartialMapping,
- GlobalProcessingDelay = (int?) _options.RequestProcessingDelay?.TotalMilliseconds
+ GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds
};
return ToJson(model);
@@ -513,7 +517,13 @@ private IResponseBuilder InitResponseBuilder(ResponseModel responseModel)
if (!string.IsNullOrEmpty(responseModel.ProxyUrl))
{
- return responseBuilder.WithProxy(responseModel.ProxyUrl);
+ if (string.IsNullOrEmpty(responseModel.X509Certificate2ThumbprintOrSubjectName))
+ {
+ return responseBuilder.WithProxy(responseModel.ProxyUrl);
+ }
+
+ return responseBuilder.WithProxy(responseModel.ProxyUrl, responseModel.X509Certificate2ThumbprintOrSubjectName);
+
}
if (responseModel.StatusCode.HasValue)
diff --git a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs b/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs
index b3ed33f12..2c58136a6 100644
--- a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs
+++ b/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs
@@ -16,8 +16,8 @@ public class ProxyAndRecordSettings
public bool SaveMapping { get; set; } = true;
///
- /// The clientCertificateFilename to use. Example : "C:\certificates\cert.pfx"
+ /// The clientCertificate thumbprint or subject name fragment to use. Example thumbprint : "D2DBF135A8D06ACCD0E1FAD9BFB28678DF7A9818". Example subject name: "www.google.com""
///
- public string X509Certificate2Filename { get; set; }
+ public string X509Certificate2ThumbprintOrSubjectName { get; set; }
}
}
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/FluentMockServerTests.cs b/test/WireMock.Net.Tests/FluentMockServerTests.cs
index 6781eda75..4a383b319 100644
--- a/test/WireMock.Net.Tests/FluentMockServerTests.cs
+++ b/test/WireMock.Net.Tests/FluentMockServerTests.cs
@@ -363,12 +363,29 @@ public async Task Should_proxy_responses()
.RespondWith(Response.Create().WithProxy("http://www.google.com"));
// when
- var result = await new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/foo");
+ var result = await new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/search?q=test");
// then
Check.That(result).Contains("google");
}
+ //Leaving commented as this requires an actual certificate with password, along with a service that expects a client certificate
+ [Fact]
+ //public async Task Should_proxy_responses_with_client_certificate()
+ //{
+ // // given
+ // _server = FluentMockServer.Start();
+ // _server
+ // .Given(Request.Create().WithPath("/*"))
+ // .RespondWith(Response.Create().WithProxy("https://server-that-expects-a-client-certificate", @"\\yourclientcertificatecontainingprivatekey.pfx", "yourclientcertificatepassword"));
+
+ // // when
+ // var result = await new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/someurl?someQuery=someValue");
+
+ // // then
+ // Check.That(result).Contains("google");
+ //}
+
//[TearDown]
public void Dispose()
{