diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index d62a227f1..598be309d 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -76,7 +76,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Generate certificates
run: |
mkdir -p certs
@@ -89,7 +89,7 @@ jobs:
sudo chown -R $USER:$USER certs
sudo chmod -R 755 certs
- name: Upload certificates
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: certs
path: certs
@@ -123,21 +123,10 @@ jobs:
run: |
dotnet build --configuration ${{ matrix.configuration }} --framework ${{ matrix.framework }} src/EventStore.Client
- name: Download certificates
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v4
with:
name: certs
path: certs
- - name: Import certificates (Linux)
- if: runner.os == 'Linux'
- shell: bash
- run: |
- sudo cp certs/ca/ca.crt /usr/local/share/ca-certificates/eventstore_ca.crt
- sudo update-ca-certificates
- - name: Import certificates (Windows)
- if: runner.os == 'Windows'
- shell: pwsh
- run: |
- Import-Certificate -FilePath "certs\ca\ca.crt" -CertStoreLocation "Cert:\LocalMachine\Root"
- name: Run Tests (Linux)
if: runner.os == 'Linux'
shell: bash
@@ -191,7 +180,7 @@ jobs:
/p:RepositoryUrl=https://github.com/EventStore/EventStore-Client-Dotnet \
/p:RepositoryType=git
- name: Publish Artifacts
- uses: actions/upload-artifact@v1
+ uses: actions/upload-artifact@v4
with:
path: packages
name: nuget-packages
diff --git a/gencert.ps1 b/gencert.ps1
index 74fc80d59..4055131d2 100644
--- a/gencert.ps1
+++ b/gencert.ps1
@@ -21,5 +21,3 @@ docker run --rm --volume .\certs:/tmp docker.eventstore.com/eventstore-utils/es-
# Set permissions recursively for the directory
icacls .\certs /grant:r "$($env:UserName):(OI)(CI)F"
-
-Import-Certificate -FilePath ".\certs\ca\ca.crt" -CertStoreLocation Cert:\CurrentUser\Root
diff --git a/src/EventStore.Client/ChannelFactory.cs b/src/EventStore.Client/ChannelFactory.cs
index 9e9e6fee7..0dc28ee8e 100644
--- a/src/EventStore.Client/ChannelFactory.cs
+++ b/src/EventStore.Client/ChannelFactory.cs
@@ -5,7 +5,7 @@
using TChannel = Grpc.Net.Client.GrpcChannel;
namespace EventStore.Client {
-
+
internal static class ChannelFactory {
private const int MaxReceiveMessageLength = 17 * 1024 * 1024;
@@ -38,14 +38,8 @@ public static TChannel CreateChannel(EventStoreClientSettings settings, EndPoint
#if NET48
static HttpMessageHandler CreateHandler(EventStoreClientSettings settings) {
- if (settings.CreateHttpMessageHandler != null) {
+ if (settings.CreateHttpMessageHandler is not null)
return settings.CreateHttpMessageHandler.Invoke();
- }
-
- var certificate = settings.ConnectivitySettings.ClientCertificate ??
- settings.ConnectivitySettings.TlsCaFile;
-
- var configureClientCert = settings.ConnectivitySettings is { Insecure: false } && certificate != null;
var handler = new WinHttpHandler {
TcpKeepAliveEnabled = true,
@@ -56,42 +50,53 @@ static HttpMessageHandler CreateHandler(EventStoreClientSettings settings) {
if (settings.ConnectivitySettings.Insecure) return handler;
- if (configureClientCert) {
- handler.ClientCertificates.Add(certificate!);
- }
+ if (settings.ConnectivitySettings.ClientCertificate is not null)
+ handler.ClientCertificates.Add(settings.ConnectivitySettings.ClientCertificate);
- if (!settings.ConnectivitySettings.TlsVerifyCert) {
- handler.ServerCertificateValidationCallback = delegate { return true; };
- }
+ handler.ServerCertificateValidationCallback = settings.ConnectivitySettings.TlsVerifyCert switch {
+ false => delegate { return true; },
+ true when settings.ConnectivitySettings.TlsCaFile is not null => (sender, certificate, chain, errors) => {
+ if (chain is null) return false;
+
+ chain.ChainPolicy.ExtraStore.Add(settings.ConnectivitySettings.TlsCaFile);
+ return chain.Build(certificate);
+ },
+ _ => null
+ };
return handler;
}
#else
static HttpMessageHandler CreateHandler(EventStoreClientSettings settings) {
- if (settings.CreateHttpMessageHandler != null) {
+ if (settings.CreateHttpMessageHandler is not null)
return settings.CreateHttpMessageHandler.Invoke();
- }
-
- var certificate = settings.ConnectivitySettings.ClientCertificate ??
- settings.ConnectivitySettings.TlsCaFile;
-
- var configureClientCert = settings.ConnectivitySettings is { Insecure: false } && certificate != null;
var handler = new SocketsHttpHandler {
KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval,
KeepAlivePingTimeout = settings.ConnectivitySettings.KeepAliveTimeout,
- EnableMultipleHttp2Connections = true,
+ EnableMultipleHttp2Connections = true
};
- if (settings.ConnectivitySettings.Insecure) return handler;
+ if (settings.ConnectivitySettings.Insecure)
+ return handler;
- if (configureClientCert) {
- handler.SslOptions.ClientCertificates = new X509CertificateCollection { certificate! };
+ if (settings.ConnectivitySettings.ClientCertificate is not null) {
+ handler.SslOptions.ClientCertificates = new X509CertificateCollection {
+ settings.ConnectivitySettings.ClientCertificate
+ };
}
- if (!settings.ConnectivitySettings.TlsVerifyCert) {
- handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; };
- }
+ handler.SslOptions.RemoteCertificateValidationCallback = settings.ConnectivitySettings.TlsVerifyCert switch {
+ false => delegate { return true; },
+ true when settings.ConnectivitySettings.TlsCaFile is not null => (sender, certificate, chain, errors) => {
+ if (certificate is not X509Certificate2 peerCertificate || chain is null) return false;
+
+ chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
+ chain.ChainPolicy.CustomTrustStore.Add(settings.ConnectivitySettings.TlsCaFile);
+ return chain.Build(peerCertificate);
+ },
+ _ => null
+ };
return handler;
}
diff --git a/src/EventStore.Client/EventStore.Client.csproj b/src/EventStore.Client/EventStore.Client.csproj
index 321f89c8c..47a116602 100644
--- a/src/EventStore.Client/EventStore.Client.csproj
+++ b/src/EventStore.Client/EventStore.Client.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs
index b6d6ee60a..48eb84956 100644
--- a/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs
+++ b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs
@@ -235,10 +235,8 @@ private static EventStoreClientSettings CreateSettings(
#if NET48
HttpMessageHandler CreateDefaultHandler() {
- var certificate = settings.ConnectivitySettings.ClientCertificate ??
- settings.ConnectivitySettings.TlsCaFile;
-
- var configureClientCert = settings.ConnectivitySettings is { Insecure: false } && certificate != null;
+ if (settings.CreateHttpMessageHandler is not null)
+ return settings.CreateHttpMessageHandler.Invoke();
var handler = new WinHttpHandler {
TcpKeepAliveEnabled = true,
@@ -249,38 +247,50 @@ HttpMessageHandler CreateDefaultHandler() {
if (settings.ConnectivitySettings.Insecure) return handler;
- if (configureClientCert) {
- handler.ClientCertificates.Add(certificate!);
- }
+ if (settings.ConnectivitySettings.ClientCertificate is not null)
+ handler.ClientCertificates.Add(settings.ConnectivitySettings.ClientCertificate);
- if (!settings.ConnectivitySettings.TlsVerifyCert) {
- handler.ServerCertificateValidationCallback = delegate { return true; };
- }
+ handler.ServerCertificateValidationCallback = settings.ConnectivitySettings.TlsVerifyCert switch {
+ false => delegate { return true; },
+ true when settings.ConnectivitySettings.TlsCaFile is not null => (sender, certificate, chain, errors) => {
+ if (chain is null) return false;
+
+ chain.ChainPolicy.ExtraStore.Add(settings.ConnectivitySettings.TlsCaFile);
+ return chain.Build(certificate);
+ },
+ _ => null
+ };
return handler;
}
#else
HttpMessageHandler CreateDefaultHandler() {
- var certificate = settings.ConnectivitySettings.ClientCertificate ??
- settings.ConnectivitySettings.TlsCaFile;
-
- var configureClientCert = settings.ConnectivitySettings is { Insecure: false } && certificate != null;
-
var handler = new SocketsHttpHandler {
KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval,
KeepAlivePingTimeout = settings.ConnectivitySettings.KeepAliveTimeout,
- EnableMultipleHttp2Connections = true,
+ EnableMultipleHttp2Connections = true
};
- if (settings.ConnectivitySettings.Insecure) return handler;
+ if (settings.ConnectivitySettings.Insecure)
+ return handler;
- if (configureClientCert) {
- handler.SslOptions.ClientCertificates = [certificate!];
+ if (settings.ConnectivitySettings.ClientCertificate is not null) {
+ handler.SslOptions.ClientCertificates = new X509CertificateCollection {
+ settings.ConnectivitySettings.ClientCertificate
+ };
}
- if (!settings.ConnectivitySettings.TlsVerifyCert) {
- handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; };
- }
+ handler.SslOptions.RemoteCertificateValidationCallback = settings.ConnectivitySettings.TlsVerifyCert switch {
+ false => delegate { return true; },
+ true when settings.ConnectivitySettings.TlsCaFile is not null => (sender, certificate, chain, errors) => {
+ if (certificate is not X509Certificate2 peerCertificate || chain is null) return false;
+
+ chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
+ chain.ChainPolicy.CustomTrustStore.Add(settings.ConnectivitySettings.TlsCaFile);
+ return chain.Build(peerCertificate);
+ },
+ _ => null
+ };
return handler;
}
diff --git a/src/EventStore.Client/HttpFallback.cs b/src/EventStore.Client/HttpFallback.cs
index 44a72cf7e..3e5420e9a 100644
--- a/src/EventStore.Client/HttpFallback.cs
+++ b/src/EventStore.Client/HttpFallback.cs
@@ -21,14 +21,27 @@ internal HttpFallback(EventStoreClientSettings settings) {
if (!settings.ConnectivitySettings.Insecure) {
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
- if (settings.ConnectivitySettings.TlsCaFile != null)
- handler.ClientCertificates.Add(settings.ConnectivitySettings.TlsCaFile);
-
- if (settings.ConnectivitySettings.ClientCertificate != null)
+ if (settings.ConnectivitySettings.ClientCertificate is not null)
handler.ClientCertificates.Add(settings.ConnectivitySettings.ClientCertificate);
- if (!settings.ConnectivitySettings.TlsVerifyCert)
- handler.ServerCertificateCustomValidationCallback = delegate { return true; };
+ handler.ServerCertificateCustomValidationCallback = settings.ConnectivitySettings.TlsVerifyCert switch {
+ false => delegate { return true; },
+ true when settings.ConnectivitySettings.TlsCaFile is not null => (sender, certificate, chain, errors) => {
+ if (certificate is null || chain is null) return false;
+
+ chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+
+#if NET48
+ chain.ChainPolicy.ExtraStore.Add(settings.ConnectivitySettings.TlsCaFile);
+#else
+ chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
+ chain.ChainPolicy.CustomTrustStore.Add(settings.ConnectivitySettings.TlsCaFile);
+#endif
+
+ return chain.Build(certificate);
+ },
+ _ => null
+ };
}
_httpClient = new HttpClient(handler);
@@ -45,9 +58,9 @@ internal async Task HttpGetAsync(string path, ChannelInfo channelInfo, Tim
UserCredentials? userCredentials, Action onNotFound, CancellationToken cancellationToken) {
var request = CreateRequest(path, HttpMethod.Get, channelInfo, userCredentials);
-
+
var httpResult = await HttpSendAsync(request, onNotFound, deadline, cancellationToken).ConfigureAwait(false);
-
+
#if NET
var json = await httpResult.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
#else
@@ -66,7 +79,7 @@ internal async Task HttpPostAsync(string path, string query, ChannelInfo channel
UserCredentials? userCredentials, Action onNotFound, CancellationToken cancellationToken) {
var request = CreateRequest(path, query, HttpMethod.Post, channelInfo, userCredentials);
-
+
await HttpSendAsync(request, onNotFound, deadline, cancellationToken).ConfigureAwait(false);
}
@@ -74,18 +87,18 @@ private async Task HttpSendAsync(HttpRequestMessage request
TimeSpan? deadline, CancellationToken cancellationToken) {
if (!deadline.HasValue) {
- return await HttpSendAsync(request, onNotFound, cancellationToken).ConfigureAwait(false);
+ return await HttpSendAsync(request, onNotFound, cancellationToken).ConfigureAwait(false);
}
-
+
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(deadline.Value);
-
+
return await HttpSendAsync(request, onNotFound, cts.Token).ConfigureAwait(false);
}
-
+
async Task HttpSendAsync(HttpRequestMessage request, Action onNotFound,
CancellationToken cancellationToken) {
-
+
var httpResult = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (httpResult.IsSuccessStatusCode) {
return httpResult;
@@ -107,7 +120,7 @@ private HttpRequestMessage CreateRequest(string path, HttpMethod method, Channel
private HttpRequestMessage CreateRequest(string path, string query, HttpMethod method, ChannelInfo channelInfo,
UserCredentials? credentials) {
-
+
var uriBuilder = new UriBuilder($"{_addressScheme}://{channelInfo.Channel.Target}") {
Path = path,
Query = query
@@ -119,7 +132,7 @@ private HttpRequestMessage CreateRequest(string path, string query, HttpMethod m
if (credentials != null) {
httpRequest.Headers.Add(Constants.Headers.Authorization, credentials.ToString());
}
-
+
return httpRequest;
}
diff --git a/test/EventStore.Client.Plugins.Tests/ClientCertificate.cs b/test/EventStore.Client.Plugins.Tests/ClientCertificate.cs
deleted file mode 100644
index b680dbb56..000000000
--- a/test/EventStore.Client.Plugins.Tests/ClientCertificate.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-namespace EventStore.Client.Plugins.Tests;
-
-[Trait("Category", "Target:Plugins")]
-[Trait("Category", "Type:UserCertificate")]
-public class ClientCertificate(ITestOutputHelper output, EventStoreFixture fixture)
- : EventStoreTests(output, fixture) {
- public static IEnumerable