Skip to content

Added client certificate and request body encoding support #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified ClrHttpRequest/bin/Debug/ClrHttpRequest.dacpac
Binary file not shown.
Binary file modified ClrHttpRequest/bin/Debug/ClrHttpRequest.dll
Binary file not shown.
Binary file modified ClrHttpRequest/bin/Debug/ClrHttpRequest.pdb
Binary file not shown.
96 changes: 87 additions & 9 deletions ClrHttpRequest/clr_http_request.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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"))
{
Expand All @@ -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;
}
Expand Down Expand Up @@ -304,11 +312,81 @@ public static SqlXml clr_http_request(string requestMethod, string url, string p
}

/// <summary>
/// Generates an XML element with a standard format for debug steps
/// Tries to get encoding from options
/// </summary>
/// <param name="stepName">Name for this step in the process</param>
/// <returns>XElement representing a debug step</returns>
private static XElement GetDebugStepXElement(string stepName)
private static Encoding GetEncoding(Dictionary<string, string> 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;
}

/// <summary>
/// Tries to read certificate by its name from certificates store
/// </summary>
/// <param name="clientCertName">CN or Subject Distinguished name</param>
/// <returns>Client certificate</returns>
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}");
}

/// <summary>
/// Generates an XML element with a standard format for debug steps
/// </summary>
/// <param name="stepName">Name for this step in the process</param>
/// <returns>XElement representing a debug step</returns>
private static XElement GetDebugStepXElement(string stepName)
{
return new XElement(
"Step",
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: `<security_protocol>Tls12,Tls11,Tls</security_protocol>`

- encoding

Encoding of http request content. Default is ASCII

Example: `<encoding>utf-8</encoding>`

- 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: `<client_certificate_name>Cert's Subject Name</client_certificate_name>`

- timeout

Expand Down