diff --git a/ClrHttpRequest/bin/Debug/ClrHttpRequest.dacpac b/ClrHttpRequest/bin/Debug/ClrHttpRequest.dacpac index d4991c8..725a12c 100644 Binary files a/ClrHttpRequest/bin/Debug/ClrHttpRequest.dacpac and b/ClrHttpRequest/bin/Debug/ClrHttpRequest.dacpac differ diff --git a/ClrHttpRequest/bin/Debug/ClrHttpRequest.dll b/ClrHttpRequest/bin/Debug/ClrHttpRequest.dll index da0c6e4..bbed238 100644 Binary files a/ClrHttpRequest/bin/Debug/ClrHttpRequest.dll and b/ClrHttpRequest/bin/Debug/ClrHttpRequest.dll differ diff --git a/ClrHttpRequest/bin/Debug/ClrHttpRequest.pdb b/ClrHttpRequest/bin/Debug/ClrHttpRequest.pdb index 071ad1b..28b1543 100644 Binary files a/ClrHttpRequest/bin/Debug/ClrHttpRequest.pdb and b/ClrHttpRequest/bin/Debug/ClrHttpRequest.pdb differ diff --git a/ClrHttpRequest/clr_http_request.cs b/ClrHttpRequest/clr_http_request.cs index fc7bc1c..3d7636f 100644 --- a/ClrHttpRequest/clr_http_request.cs +++ b/ClrHttpRequest/clr_http_request.cs @@ -3,6 +3,7 @@ using System.Data.SqlTypes; using System.IO; using System.Net; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Xml.Linq; @@ -138,6 +139,12 @@ public static SqlXml clr_http_request(string requestMethod, string url, string p } debugXml.Add(GetDebugStepXElement("Processed Headers")); + if (options.ContainsKey("client_certificate_name")) + { + var clientCertName = options["client_certificate_name"]; + request.ClientCertificates.Add(ReadClientCertificate(clientCertName, debugXml)); + } + // Set the timeout if provided as an option if (options.ContainsKey("timeout")) { @@ -156,11 +163,12 @@ public static SqlXml clr_http_request(string requestMethod, string url, string p // Add in non-GET parameters provided if (requestMethod.ToUpper() != "GET" && !string.IsNullOrWhiteSpace(parameters)) { - // Convert to byte array - var parameterData = Encoding.ASCII.GetBytes(parameters); - - // Set content info - if (!contentLengthSetFromHeaders) + var encoding = GetEncoding(options, debugXml); + // Convert to byte array + var parameterData = encoding.GetBytes(parameters); + + // Set content info + if (!contentLengthSetFromHeaders) { request.ContentLength = parameterData.Length; } @@ -304,11 +312,81 @@ public static SqlXml clr_http_request(string requestMethod, string url, string p } /// - /// Generates an XML element with a standard format for debug steps + /// Tries to get encoding from options /// - /// Name for this step in the process - /// XElement representing a debug step - private static XElement GetDebugStepXElement(string stepName) + private static Encoding GetEncoding(Dictionary options, XElement debugXml) + { + var encodingKey = "encoding"; + Encoding result = Encoding.ASCII; + try + { + if (options.ContainsKey(encodingKey)) + { + result = Encoding.GetEncoding(options[encodingKey]); + } + } + catch (Exception ex) + { + debugXml.Add(GetDebugStepXElement($"Error while parse specified encoding value {options[encodingKey]}")); + + debugXml.Add(GetXElementFromException(ex)); + } + + debugXml.Add(GetDebugStepXElement($"Set encoding to {result}")); + + return result; + } + + /// + /// Tries to read certificate by its name from certificates store + /// + /// CN or Subject Distinguished name + /// Client certificate + private static X509Certificate2 ReadClientCertificate(string clientCertName, XElement debugXml) + { + var storeLocations = new StoreLocation[] { StoreLocation.LocalMachine, StoreLocation.CurrentUser }; + var storeNames = new StoreName[] { StoreName.Root, StoreName.My, StoreName.TrustedPeople, StoreName.AuthRoot }; + + foreach (var storeLocation in storeLocations) + foreach (var storeName in storeNames) + { + X509Store store = null; + try + { + store = new X509Store(storeName, storeLocation); + store.Open(OpenFlags.ReadOnly); + + foreach (var cert in store.Certificates) + { + if (cert.SubjectName?.Name?.Contains(clientCertName) == true + || cert.Subject?.Contains(clientCertName) == true) + { + debugXml.Add(GetDebugStepXElement($"Client certificate founded: {cert.Subject ?? cert.SubjectName?.Name}")); + return cert; + } + } + } + catch (Exception ex) + { + debugXml.Add(GetDebugStepXElement($"Error while reading store {storeName} for {storeLocation}")); + + debugXml.Add(GetXElementFromException(ex)); + } + finally + { + store?.Close(); + } + } + + throw new Exception($"Cannot find any certificate by name {clientCertName}"); + } + + /// + /// Generates an XML element with a standard format for debug steps + /// + /// Name for this step in the process + /// XElement representing a debug step + private static XElement GetDebugStepXElement(string stepName) { return new XElement( "Step", diff --git a/README.md b/README.md index 1180301..5b49fae 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,18 @@ If you're waiting for me or have any questions for me, bug me! Pass a CSV of protocols from the [SecurityProtocolType Enum](https://docs.microsoft.com/en-us/dotnet/api/system.net.securityprotocoltype) Example: `Tls12,Tls11,Tls` + +- encoding + + Encoding of http request content. Default is ASCII + + Example: `utf-8` + +- client_certificate_name + + For mTLS support, pass a client certificate name to locate it in the server's certificate store by Subject or Subject Name and use it in the HTTP request. + + Example: `Cert's Subject Name` - timeout