diff --git a/Frends.HTTP.DownloadFile/CHANGELOG.md b/Frends.HTTP.DownloadFile/CHANGELOG.md
index 597e1b9..20f6d39 100644
--- a/Frends.HTTP.DownloadFile/CHANGELOG.md
+++ b/Frends.HTTP.DownloadFile/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## [1.4.0] - 2025-11-18
+### Added
+- Added CertificateStoreLocation option to allow selection between CurrentUser and LocalMachine certificate stores when using certificate authentication.
+
## [1.3.0] - 2025-05-15
### Changed
- Added new Overwrite parameter to control whether the downloaded file should replace an existing one.
diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/Frends.HTTP.DownloadFile.Tests.csproj b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/Frends.HTTP.DownloadFile.Tests.csproj
index 40a3537..9163594 100644
--- a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/Frends.HTTP.DownloadFile.Tests.csproj
+++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/Frends.HTTP.DownloadFile.Tests.csproj
@@ -13,6 +13,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs
index 00db7d0..cfb392a 100644
--- a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs
+++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs
@@ -1,11 +1,12 @@
using Frends.HTTP.DownloadFile.Definitions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Pluralsight.Crypto;
using System;
using System.Collections.Generic;
using System.IO;
+using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
+using Pluralsight.Crypto;
namespace Frends.HTTP.DownloadFile.Tests;
@@ -14,7 +15,9 @@ public class UnitTests
{
private static readonly string _directory = Path.Combine(Environment.CurrentDirectory, "testfiles");
private static readonly string _filePath = Path.Combine(_directory, "picture.jpg");
- private static readonly string _targetFileAddress = @"https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google_2015_logo.svg/1200px-Google_2015_logo.svg.png";
+
+ private static readonly string _targetFileAddress =
+ "https://frendsfonts.blob.core.windows.net/images/frendsLogo.png";//@"http://localhost:9999/testfile.png";
private readonly string _certificatePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestFiles", "certwithpk.pfx");
private readonly string _privateKeyPassword = "password";
@@ -138,9 +141,21 @@ public async Task TestFileDownload_WithHeaders()
{
var headers = new[] { new Header() { Name = "foo", Value = "bar" } };
- var auths = new List() { Authentication.None, Authentication.Basic, Authentication.WindowsAuthentication, Authentication.WindowsIntegratedSecurity, Authentication.OAuth };
+ var auths = new List
+ {
+ Authentication.None,
+ Authentication.Basic,
+ Authentication.WindowsAuthentication,
+ Authentication.WindowsIntegratedSecurity,
+ Authentication.OAuth
+ };
- var certSource = new List() { CertificateSource.CertificateStore, CertificateSource.File, CertificateSource.String };
+ var certSource = new List
+ {
+ CertificateSource.CertificateStore,
+ CertificateSource.File,
+ CertificateSource.String
+ };
var input = new Input
{
@@ -188,7 +203,12 @@ public async Task TestFileDownload_WithHeaders()
[TestMethod]
public async Task TestFileDownload_Certification()
{
- var certSources = new List() { CertificateSource.File, CertificateSource.String, CertificateSource.CertificateStore };
+ var certSources = new List
+ {
+ CertificateSource.File,
+ CertificateSource.String,
+ CertificateSource.CertificateStore
+ };
var input = new Input
{
@@ -328,4 +348,339 @@ public async Task TestFileDownload_WithOverwriteTrue_ShouldOverwriteExistingFile
var actualContent = File.ReadAllText(_filePath);
Assert.AreNotEqual("OLD CONTENT", actualContent, "File should have been overwritten.");
}
+
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentNullException))]
+ public async Task TestFileDownload_WithEmptyUrl_ShouldThrowException()
+ {
+ var input = new Input
+ {
+ Url = "",
+ FilePath = _filePath,
+ Headers = null
+ };
+
+ var options = new Options
+ {
+ AllowInvalidCertificate = true,
+ Authentication = Authentication.None,
+ ConnectionTimeoutSeconds = 60
+ };
+
+ await HTTP.DownloadFile(input, options, default);
+ }
+
+ [TestMethod]
+ public async Task TestFileDownload_WithCertificateStoreLocation_CurrentUser()
+ {
+ var tp = CertificateHandler(_certificatePath, _privateKeyPassword, false, null);
+
+ var input = new Input
+ {
+ Url = _targetFileAddress,
+ FilePath = _filePath,
+ Headers = null
+ };
+
+ var options = new Options
+ {
+ AllowInvalidCertificate = true,
+ AllowInvalidResponseContentTypeCharSet = true,
+ Authentication = Authentication.ClientCertificate,
+ AutomaticCookieHandling = true,
+ CertificateThumbprint = tp,
+ ClientCertificateFilePath = _certificatePath,
+ ClientCertificateInBase64 = "",
+ ClientCertificateKeyPhrase = _privateKeyPassword,
+ ClientCertificateSource = CertificateSource.CertificateStore,
+ CertificateStoreLocation = CertificateStoreLocation.CurrentUser,
+ ConnectionTimeoutSeconds = 60,
+ FollowRedirects = true,
+ LoadEntireChainForCertificate = false,
+ Password = "",
+ ThrowExceptionOnErrorResponse = true,
+ Token = "",
+ Username = "domain\\username"
+ };
+
+ var result = await HTTP.DownloadFile(input, options, default);
+
+ Assert.IsNotNull(result);
+ Assert.IsTrue(result.Success);
+ Assert.IsNotNull(result.FilePath);
+ Assert.IsTrue(File.Exists(result.FilePath));
+
+ CertificateHandler(_certificatePath, _privateKeyPassword, true, tp);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(Exception))]
+ public async Task TestFileDownload_WithCertificateStoreLocation_LocalMachine_NotFound()
+ {
+ // Use real HTTP client factory to test certificate lookup failure
+ HTTP.ClientFactory = new HttpClientFactory();
+
+ var input = new Input
+ {
+ Url = _targetFileAddress,
+ FilePath = _filePath,
+ Headers = null
+ };
+
+ var options = new Options
+ {
+ AllowInvalidCertificate = true,
+ AllowInvalidResponseContentTypeCharSet = true,
+ Authentication = Authentication.ClientCertificate,
+ AutomaticCookieHandling = true,
+ CertificateThumbprint = "NONEXISTENTTHUMBPRINT",
+ ClientCertificateFilePath = "",
+ ClientCertificateInBase64 = "",
+ ClientCertificateKeyPhrase = "",
+ ClientCertificateSource = CertificateSource.CertificateStore,
+ CertificateStoreLocation = CertificateStoreLocation.LocalMachine,
+ ConnectionTimeoutSeconds = 60,
+ FollowRedirects = true,
+ LoadEntireChainForCertificate = false,
+ Password = "",
+ ThrowExceptionOnErrorResponse = true,
+ Token = "",
+ Username = "domain\\username"
+ };
+
+ await HTTP.DownloadFile(input, options, default);
+ }
+
+ [TestMethod]
+ public async Task TestFileDownload_WithOverwriteFalse_ExistingFile_ShouldThrow()
+ {
+ File.WriteAllText(_filePath, "OLD CONTENT");
+
+ var input = new Input
+ {
+ Url = _targetFileAddress,
+ FilePath = _filePath,
+ Headers = null
+ };
+
+ var options = new Options
+ {
+ AllowInvalidCertificate = true,
+ AllowInvalidResponseContentTypeCharSet = true,
+ Authentication = Authentication.None,
+ AutomaticCookieHandling = true,
+ CertificateThumbprint = "",
+ ClientCertificateFilePath = "",
+ ClientCertificateInBase64 = "",
+ ClientCertificateKeyPhrase = "",
+ ClientCertificateSource = CertificateSource.File,
+ ConnectionTimeoutSeconds = 60,
+ FollowRedirects = true,
+ LoadEntireChainForCertificate = true,
+ Password = "",
+ ThrowExceptionOnErrorResponse = true,
+ Token = "",
+ Username = "domain\\username",
+ Overwrite = false
+ };
+
+ await Assert.ThrowsExceptionAsync(async () =>
+ await HTTP.DownloadFile(input, options, default));
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(Exception))]
+ public async Task TestFileDownload_WindowsAuth_InvalidUsername_ShouldThrow()
+ {
+ // Use real HTTP client factory to test username validation
+ HTTP.ClientFactory = new HttpClientFactory();
+
+ var input = new Input
+ {
+ Url = _targetFileAddress,
+ FilePath = _filePath,
+ Headers = null
+ };
+
+ var options = new Options
+ {
+ AllowInvalidCertificate = true,
+ AllowInvalidResponseContentTypeCharSet = true,
+ Authentication = Authentication.WindowsAuthentication,
+ AutomaticCookieHandling = true,
+ CertificateThumbprint = "",
+ ClientCertificateFilePath = "",
+ ClientCertificateInBase64 = "",
+ ClientCertificateKeyPhrase = "",
+ ClientCertificateSource = CertificateSource.File,
+ ConnectionTimeoutSeconds = 60,
+ FollowRedirects = true,
+ LoadEntireChainForCertificate = false,
+ Password = "password",
+ ThrowExceptionOnErrorResponse = true,
+ Token = "",
+ Username = "invalid_username_without_domain"
+ };
+
+ await HTTP.DownloadFile(input, options, default);
+ }
+
+ [TestMethod]
+ public async Task TestFileDownload_CertificateFromFile_WithoutKeyPhrase()
+ {
+ var tp = CertificateHandler(_certificatePath, _privateKeyPassword, false, null);
+
+ var input = new Input
+ {
+ Url = _targetFileAddress,
+ FilePath = _filePath,
+ Headers = null
+ };
+
+ var options = new Options
+ {
+ AllowInvalidCertificate = true,
+ AllowInvalidResponseContentTypeCharSet = true,
+ Authentication = Authentication.ClientCertificate,
+ AutomaticCookieHandling = true,
+ CertificateThumbprint = "",
+ ClientCertificateFilePath = _certificatePath,
+ ClientCertificateInBase64 = "",
+ ClientCertificateKeyPhrase = _privateKeyPassword,
+ ClientCertificateSource = CertificateSource.File,
+ ConnectionTimeoutSeconds = 60,
+ FollowRedirects = true,
+ LoadEntireChainForCertificate = false,
+ Password = "",
+ ThrowExceptionOnErrorResponse = true,
+ Token = "",
+ Username = "domain\\username"
+ };
+
+ var result = await HTTP.DownloadFile(input, options, default);
+
+ Assert.IsNotNull(result);
+ Assert.IsTrue(result.Success);
+
+ CertificateHandler(_certificatePath, _privateKeyPassword, true, tp);
+ }
+
+ [TestMethod]
+ public async Task TestFileDownload_CertificateFromString_WithKeyPhrase()
+ {
+ var tp = CertificateHandler(_certificatePath, _privateKeyPassword, false, null);
+
+ var input = new Input
+ {
+ Url = _targetFileAddress,
+ FilePath = _filePath,
+ Headers = null
+ };
+
+ var options = new Options
+ {
+ AllowInvalidCertificate = true,
+ AllowInvalidResponseContentTypeCharSet = true,
+ Authentication = Authentication.ClientCertificate,
+ AutomaticCookieHandling = true,
+ CertificateThumbprint = "",
+ ClientCertificateFilePath = "",
+ ClientCertificateInBase64 = Convert.ToBase64String(File.ReadAllBytes(_certificatePath)),
+ ClientCertificateKeyPhrase = _privateKeyPassword,
+ ClientCertificateSource = CertificateSource.String,
+ ConnectionTimeoutSeconds = 60,
+ FollowRedirects = true,
+ LoadEntireChainForCertificate = false,
+ Password = "",
+ ThrowExceptionOnErrorResponse = true,
+ Token = "",
+ Username = "domain\\username"
+ };
+
+ var result = await HTTP.DownloadFile(input, options, default);
+
+ Assert.IsNotNull(result);
+ Assert.IsTrue(result.Success);
+
+ CertificateHandler(_certificatePath, _privateKeyPassword, true, tp);
+ }
+
+ [TestMethod]
+ public async Task TestFileDownload_WithNullHeaders()
+ {
+ var input = new Input
+ {
+ Url = _targetFileAddress,
+ FilePath = _filePath,
+ Headers = null
+ };
+
+ var options = new Options
+ {
+ AllowInvalidCertificate = true,
+ AllowInvalidResponseContentTypeCharSet = true,
+ Authentication = Authentication.Basic,
+ AutomaticCookieHandling = true,
+ CertificateThumbprint = "",
+ ClientCertificateFilePath = "",
+ ClientCertificateInBase64 = "",
+ ClientCertificateKeyPhrase = "",
+ ClientCertificateSource = CertificateSource.File,
+ ConnectionTimeoutSeconds = 60,
+ FollowRedirects = true,
+ LoadEntireChainForCertificate = false,
+ Password = "password",
+ ThrowExceptionOnErrorResponse = false,
+ Token = "",
+ Username = "domain\\username"
+ };
+
+ var result = await HTTP.DownloadFile(input, options, default);
+
+ Assert.IsNotNull(result);
+ Assert.IsTrue(result.Success);
+ }
+
+ [TestMethod]
+ public async Task TestFileDownload_CachedClient()
+ {
+ var input = new Input
+ {
+ Url = _targetFileAddress,
+ FilePath = _filePath,
+ Headers = null
+ };
+
+ var options = new Options
+ {
+ AllowInvalidCertificate = true,
+ AllowInvalidResponseContentTypeCharSet = true,
+ Authentication = Authentication.None,
+ AutomaticCookieHandling = true,
+ CertificateThumbprint = "",
+ ClientCertificateFilePath = "",
+ ClientCertificateInBase64 = "",
+ ClientCertificateKeyPhrase = "",
+ ClientCertificateSource = CertificateSource.File,
+ ConnectionTimeoutSeconds = 60,
+ FollowRedirects = true,
+ LoadEntireChainForCertificate = false,
+ Password = "",
+ ThrowExceptionOnErrorResponse = false,
+ Token = "",
+ Username = ""
+ };
+
+ // First request creates client
+ var result1 = await HTTP.DownloadFile(input, options, default);
+ Assert.IsTrue(result1.Success);
+
+ // Cleanup and recreate directory for second request
+ Cleanup();
+ Directory.CreateDirectory(_directory);
+
+ // Second request should use cached client
+ var result2 = await HTTP.DownloadFile(input, options, default);
+ Assert.IsTrue(result2.Success);
+ }
}
\ No newline at end of file
diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/CertificateStoreLocation.cs b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/CertificateStoreLocation.cs
new file mode 100644
index 0000000..83cfeb7
--- /dev/null
+++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/CertificateStoreLocation.cs
@@ -0,0 +1,16 @@
+namespace Frends.HTTP.DownloadFile.Definitions;
+
+///
+/// Certificate store location.
+///
+public enum CertificateStoreLocation
+{
+ ///
+ /// The X.509 certificate store assigned to the current user.
+ ///
+ CurrentUser,
+ ///
+ /// The X.509 certificate store assigned to the local machine.
+ ///
+ LocalMachine
+}
diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/Options.cs b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/Options.cs
index d2d70a0..9bb7b41 100644
--- a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/Options.cs
+++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/Options.cs
@@ -81,6 +81,15 @@ public class Options
[UIHint(nameof(Authentication), "", Authentication.ClientCertificate)]
public string CertificateThumbprint { get; set; }
+ ///
+ /// Applicable only when Certificate Source is "CertificateStore".
+ /// Store location for the certificate.
+ ///
+ /// CurrentUser
+ [UIHint(nameof(Authentication), "", Authentication.ClientCertificate)]
+ [DefaultValue(CertificateStoreLocation.CurrentUser)]
+ public CertificateStoreLocation CertificateStoreLocation { get; set; }
+
///
/// Should the entire certificate chain be loaded from the certificate store and included in the request. Only valid when using Certificate Store as the Certificate Source
///
diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Extensions.cs b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Extensions.cs
index 3ab6b87..1bc35f0 100644
--- a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Extensions.cs
+++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Extensions.cs
@@ -54,7 +54,7 @@ private static X509Certificate[] GetCertificates(Options options)
{
case CertificateSource.CertificateStore:
var thumbprint = options.CertificateThumbprint;
- certificates = GetCertificatesFromStore(thumbprint, options.LoadEntireChainForCertificate);
+ certificates = GetCertificatesFromStore(thumbprint, options.LoadEntireChainForCertificate, options.CertificateStoreLocation);
break;
case CertificateSource.File:
certificates = GetCertificatesFromFile(options.ClientCertificateFilePath, options.ClientCertificateKeyPhrase);
@@ -94,14 +94,21 @@ private static X509Certificate2[] GetCertificatesFromFile(string clientCertifica
}
private static X509Certificate2[] GetCertificatesFromStore(string thumbprint,
- bool loadEntireChain)
+ bool loadEntireChain, CertificateStoreLocation storeLocation)
{
thumbprint = Regex.Replace(thumbprint, @"[^\da-zA-z]", string.Empty).ToUpper();
- using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
+ var location = storeLocation == CertificateStoreLocation.CurrentUser
+ ? StoreLocation.CurrentUser
+ : StoreLocation.LocalMachine;
+ var locationText = storeLocation == CertificateStoreLocation.CurrentUser
+ ? "current user"
+ : "local machine";
+
+ using var store = new X509Store(StoreName.My, location);
store.Open(OpenFlags.ReadOnly);
var signingCert = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (signingCert.Count == 0)
- throw new FileNotFoundException($"Certificate with thumbprint: '{thumbprint}' not found in current user cert store.");
+ throw new FileNotFoundException($"Certificate with thumbprint: '{thumbprint}' not found in {locationText} cert store.");
var certificate = signingCert[0];
@@ -114,7 +121,7 @@ private static X509Certificate2[] GetCertificatesFromStore(string thumbprint,
// include the whole chain
var certificates = chain
- .ChainElements.Cast()
+ .ChainElements
.Select(c => c.Certificate)
.OrderByDescending(c => c.HasPrivateKey)
.ToArray();
diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.csproj b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.csproj
index 524cdd5..a870dfb 100644
--- a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.csproj
+++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.csproj
@@ -2,7 +2,7 @@
net6.0
- 1.3.0
+ 1.4.0
Frends
Frends
Frends
diff --git a/Frends.HTTP.Request/CHANGELOG.md b/Frends.HTTP.Request/CHANGELOG.md
index 6b27b00..e9a5705 100644
--- a/Frends.HTTP.Request/CHANGELOG.md
+++ b/Frends.HTTP.Request/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## [1.6.0] - 2025-11-18
+### Added
+- Added CertificateStoreLocation option to allow selection between CurrentUser and LocalMachine certificate stores when using certificate authentication.
+
## [1.5.0] - 2025-10-03
### Changed
- Changed default return format from String to JToken
diff --git a/Frends.HTTP.Request/Frends.HTTP.Request/Definitions/CertificateStoreLocation.cs b/Frends.HTTP.Request/Frends.HTTP.Request/Definitions/CertificateStoreLocation.cs
new file mode 100644
index 0000000..7cbf8cb
--- /dev/null
+++ b/Frends.HTTP.Request/Frends.HTTP.Request/Definitions/CertificateStoreLocation.cs
@@ -0,0 +1,16 @@
+namespace Frends.HTTP.Request.Definitions;
+
+///
+/// Certificate store location.
+///
+public enum CertificateStoreLocation
+{
+ ///
+ /// The X.509 certificate store assigned to the current user.
+ ///
+ CurrentUser,
+ ///
+ /// The X.509 certificate store assigned to the local machine.
+ ///
+ LocalMachine
+}
diff --git a/Frends.HTTP.Request/Frends.HTTP.Request/Definitions/Options.cs b/Frends.HTTP.Request/Frends.HTTP.Request/Definitions/Options.cs
index 27d1ccd..73f27d4 100644
--- a/Frends.HTTP.Request/Frends.HTTP.Request/Definitions/Options.cs
+++ b/Frends.HTTP.Request/Frends.HTTP.Request/Definitions/Options.cs
@@ -80,6 +80,15 @@ public class Options
[UIHint(nameof(Authentication), "", Authentication.ClientCertificate)]
public string CertificateThumbprint { get; set; }
+ ///
+ /// Applicable only when Certificate Source is "CertificateStore".
+ /// Store location for the certificate.
+ ///
+ /// CurrentUser
+ [UIHint(nameof(Authentication), "", Authentication.ClientCertificate)]
+ [DefaultValue(CertificateStoreLocation.CurrentUser)]
+ public CertificateStoreLocation CertificateStoreLocation { get; set; }
+
///
/// Should the entire certificate chain be loaded from the certificate store and included in the request. Only valid when using Certificate Store as the Certificate Source
///
diff --git a/Frends.HTTP.Request/Frends.HTTP.Request/Extensions.cs b/Frends.HTTP.Request/Frends.HTTP.Request/Extensions.cs
index 5c41542..ba4efdf 100644
--- a/Frends.HTTP.Request/Frends.HTTP.Request/Extensions.cs
+++ b/Frends.HTTP.Request/Frends.HTTP.Request/Extensions.cs
@@ -61,7 +61,7 @@ private static X509Certificate[] GetCertificates(Options options)
{
case CertificateSource.CertificateStore:
var thumbprint = options.CertificateThumbprint;
- certificates = GetCertificatesFromStore(thumbprint, options.LoadEntireChainForCertificate);
+ certificates = GetCertificatesFromStore(thumbprint, options.LoadEntireChainForCertificate, options.CertificateStoreLocation);
break;
case CertificateSource.File:
certificates = GetCertificatesFromFile(options.ClientCertificateFilePath, options.ClientCertificateKeyPhrase);
@@ -105,17 +105,24 @@ private static X509Certificate2[] GetCertificatesFromFile(string clientCertifica
}
private static X509Certificate2[] GetCertificatesFromStore(string thumbprint,
- bool loadEntireChain)
+ bool loadEntireChain, CertificateStoreLocation storeLocation)
{
thumbprint = Regex.Replace(thumbprint, @"[^\da-zA-z]", string.Empty).ToUpper();
- using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
+ var location = storeLocation == CertificateStoreLocation.CurrentUser
+ ? StoreLocation.CurrentUser
+ : StoreLocation.LocalMachine;
+ var locationText = storeLocation == CertificateStoreLocation.CurrentUser
+ ? "current user"
+ : "local machine";
+
+ using (var store = new X509Store(StoreName.My, location))
{
store.Open(OpenFlags.ReadOnly);
var signingCert = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (signingCert.Count == 0)
{
throw new FileNotFoundException(
- $"Certificate with thumbprint: '{thumbprint}' not found in current user cert store.");
+ $"Certificate with thumbprint: '{thumbprint}' not found in {locationText} cert store.");
}
var certificate = signingCert[0];
diff --git a/Frends.HTTP.Request/Frends.HTTP.Request/Frends.HTTP.Request.csproj b/Frends.HTTP.Request/Frends.HTTP.Request/Frends.HTTP.Request.csproj
index 382e8e1..3eb843e 100644
--- a/Frends.HTTP.Request/Frends.HTTP.Request/Frends.HTTP.Request.csproj
+++ b/Frends.HTTP.Request/Frends.HTTP.Request/Frends.HTTP.Request.csproj
@@ -2,7 +2,7 @@
net6.0
- 1.5.0
+ 1.6.0
Frends
Frends
Frends
diff --git a/Frends.HTTP.RequestBytes/CHANGELOG.md b/Frends.HTTP.RequestBytes/CHANGELOG.md
index d03e15b..e972dc9 100644
--- a/Frends.HTTP.RequestBytes/CHANGELOG.md
+++ b/Frends.HTTP.RequestBytes/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## [1.4.0] - 2025-11-18
+### Added
+- Added CertificateStoreLocation option to allow selection between CurrentUser and LocalMachine certificate stores when using certificate authentication.
+
## [1.3.0] - 2025-10-10
### Changed
- Changed the return type of RequestBytes from Task