Skip to content
Merged
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
2 changes: 2 additions & 0 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Current package versions:
## Unreleased
No pending unreleased changes

- Add `ConfigurationOptions.SetUserPemCertificate(...)` and `ConfigurationOptions.SetUserPfxCertificate(...)` methods to simplify using client certificates ([#2873 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2873))

## 2.8.31

- Fix: Respect `IReconnectRetryPolicy` timing in the case that a node that was present disconnects indefinitely ([#2853](https://github.com/StackExchange/StackExchange.Redis/pull/2853) & [#2856](https://github.com/StackExchange/StackExchange.Redis/pull/2856) by NickCraver)
Expand Down
43 changes: 43 additions & 0 deletions src/StackExchange.Redis/ConfigurationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,49 @@ public bool HighIntegrity
/// <param name="issuerCertificatePath">The file system path to find the certificate at.</param>
public void TrustIssuer(string issuerCertificatePath) => CertificateValidationCallback = TrustIssuerCallback(issuerCertificatePath);

#if NET5_0_OR_GREATER
/// <summary>
/// Supply a user certificate from a PEM file pair and enable TLS.
/// </summary>
/// <param name="userCertificatePath">The path for the the user certificate (commonly a .crt file).</param>
/// <param name="userKeyPath">The path for the the user key (commonly a .key file).</param>
public void SetUserPemCertificate(string userCertificatePath, string? userKeyPath = null)
{
CertificateSelectionCallback = CreatePemUserCertificateCallback(userCertificatePath, userKeyPath);
Ssl = true;
}
#endif

/// <summary>
/// Supply a user certificate from a PFX file and optional password and enable TLS.
/// </summary>
/// <param name="userCertificatePath">The path for the the user certificate (commonly a .pfx file).</param>
/// <param name="password">The password for the certificate file.</param>
public void SetUserPfxCertificate(string userCertificatePath, string? password = null)
{
CertificateSelectionCallback = CreatePfxUserCertificateCallback(userCertificatePath, password);
Ssl = true;
}

#if NET5_0_OR_GREATER
internal static LocalCertificateSelectionCallback CreatePemUserCertificateCallback(string userCertificatePath, string? userKeyPath)
{
// PEM handshakes not universally supported and causes a runtime error about ephemeral certificates; to avoid, export as PFX
using var pem = X509Certificate2.CreateFromPemFile(userCertificatePath, userKeyPath);
#pragma warning disable SYSLIB0057 // Type or member is obsolete
var pfx = new X509Certificate2(pem.Export(X509ContentType.Pfx));
#pragma warning restore SYSLIB0057 // Type or member is obsolete

return (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => pfx;
}
#endif

internal static LocalCertificateSelectionCallback CreatePfxUserCertificateCallback(string userCertificatePath, string? password, X509KeyStorageFlags storageFlags = X509KeyStorageFlags.DefaultKeySet)
{
var pfx = new X509Certificate2(userCertificatePath, password ?? "", storageFlags);
return (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => pfx;
}

/// <summary>
/// Create a certificate validation check that checks against the supplied issuer even when not known by the machine.
/// </summary>
Expand Down
27 changes: 17 additions & 10 deletions src/StackExchange.Redis/PhysicalConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1504,21 +1504,28 @@ public ConnectionStatus GetStatus()
{
try
{
var pfxPath = Environment.GetEnvironmentVariable("SERedis_ClientCertPfxPath");
var pfxPassword = Environment.GetEnvironmentVariable("SERedis_ClientCertPassword");
var pfxStorageFlags = Environment.GetEnvironmentVariable("SERedis_ClientCertStorageFlags");

X509KeyStorageFlags? flags = null;
if (!string.IsNullOrEmpty(pfxStorageFlags))
var certificatePath = Environment.GetEnvironmentVariable("SERedis_ClientCertPfxPath");
if (!string.IsNullOrEmpty(certificatePath) && File.Exists(certificatePath))
{
flags = Enum.Parse(typeof(X509KeyStorageFlags), pfxStorageFlags) as X509KeyStorageFlags?;
var password = Environment.GetEnvironmentVariable("SERedis_ClientCertPassword");
var pfxStorageFlags = Environment.GetEnvironmentVariable("SERedis_ClientCertStorageFlags");
X509KeyStorageFlags storageFlags = X509KeyStorageFlags.DefaultKeySet;
if (!string.IsNullOrEmpty(pfxStorageFlags) && Enum.TryParse<X509KeyStorageFlags>(pfxStorageFlags, true, out var typedFlags))
{
storageFlags = typedFlags;
}

return ConfigurationOptions.CreatePfxUserCertificateCallback(certificatePath, password, storageFlags);
}

if (!string.IsNullOrEmpty(pfxPath) && File.Exists(pfxPath))
#if NET5_0_OR_GREATER
certificatePath = Environment.GetEnvironmentVariable("SERedis_ClientCertPemPath");
if (!string.IsNullOrEmpty(certificatePath) && File.Exists(certificatePath))
{
return (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) =>
new X509Certificate2(pfxPath, pfxPassword ?? "", flags ?? X509KeyStorageFlags.DefaultKeySet);
var passwordPath = Environment.GetEnvironmentVariable("SERedis_ClientCertPasswordPath");
return ConfigurationOptions.CreatePemUserCertificateCallback(certificatePath, passwordPath);
}
#endif
}
catch (Exception ex)
{
Expand Down
2 changes: 1 addition & 1 deletion src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1893,4 +1893,4 @@ virtual StackExchange.Redis.RedisResult.Length.get -> int
virtual StackExchange.Redis.RedisResult.this[int index].get -> StackExchange.Redis.RedisResult!
StackExchange.Redis.ConnectionMultiplexer.AddLibraryNameSuffix(string! suffix) -> void
StackExchange.Redis.IConnectionMultiplexer.AddLibraryNameSuffix(string! suffix) -> void

StackExchange.Redis.ConfigurationOptions.SetUserPfxCertificate(string! userCertificatePath, string? password = null) -> void
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
StackExchange.Redis.ConfigurationOptions.SslClientAuthenticationOptions.get -> System.Func<string!, System.Net.Security.SslClientAuthenticationOptions!>?
StackExchange.Redis.ConfigurationOptions.SslClientAuthenticationOptions.set -> void
System.Runtime.CompilerServices.IsExternalInit (forwarded, contained in System.Runtime)
System.Runtime.CompilerServices.IsExternalInit (forwarded, contained in System.Runtime)
StackExchange.Redis.ConfigurationOptions.SetUserPemCertificate(string! userCertificatePath, string? userKeyPath = null) -> void
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
StackExchange.Redis.ConfigurationOptions.SslClientAuthenticationOptions.get -> System.Func<string!, System.Net.Security.SslClientAuthenticationOptions!>?
StackExchange.Redis.ConfigurationOptions.SslClientAuthenticationOptions.set -> void
System.Runtime.CompilerServices.IsExternalInit (forwarded, contained in System.Runtime)
System.Runtime.CompilerServices.IsExternalInit (forwarded, contained in System.Runtime)
StackExchange.Redis.ConfigurationOptions.SetUserPemCertificate(string! userCertificatePath, string? userKeyPath = null) -> void
Loading