diff --git a/StackExchange.Redis.sln.DotSettings b/StackExchange.Redis.sln.DotSettings
index 165f8337f..de893e54d 100644
--- a/StackExchange.Redis.sln.DotSettings
+++ b/StackExchange.Redis.sln.DotSettings
@@ -1,3 +1,4 @@
OK
- PONG
\ No newline at end of file
+ PONG
+ True
\ No newline at end of file
diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs
index dfdab5f4e..c0021f024 100644
--- a/src/StackExchange.Redis/ConfigurationOptions.cs
+++ b/src/StackExchange.Redis/ConfigurationOptions.cs
@@ -390,8 +390,18 @@ private static bool CheckTrustedIssuer(X509Certificate2 certificateToValidate, X
try
{
// This only verifies that the chain is valid, but with AllowUnknownCertificateAuthority could trust
- // self-signed or partial chained vertificates
- var chainIsVerified = chain.Build(certificateToValidate);
+ // self-signed or partial chained certificates
+ bool chainIsVerified;
+ try
+ {
+ chainIsVerified = chain.Build(certificateToValidate);
+ }
+ catch (ArgumentException ex) when ((ex.ParamName ?? ex.Message) == "certificate" && Runtime.IsMono)
+ {
+ // work around Mono cert limitation; report as rejected rather than fault
+ // (note also the likely .ctor mixup re param-name vs message)
+ chainIsVerified = false;
+ }
if (chainIsVerified)
{
// Our method is "TrustIssuer", which means any intermediate cert we're being told to trust
diff --git a/src/StackExchange.Redis/Runtime.cs b/src/StackExchange.Redis/Runtime.cs
new file mode 100644
index 000000000..879c9c325
--- /dev/null
+++ b/src/StackExchange.Redis/Runtime.cs
@@ -0,0 +1,9 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace StackExchange.Redis;
+
+internal static class Runtime
+{
+ public static readonly bool IsMono = RuntimeInformation.FrameworkDescription.StartsWith("Mono ", StringComparison.OrdinalIgnoreCase);
+}
diff --git a/tests/StackExchange.Redis.Tests/Certificates/CertValidationTests.cs b/tests/StackExchange.Redis.Tests/Certificates/CertValidationTests.cs
index 529b29a02..a0d9b5c88 100644
--- a/tests/StackExchange.Redis.Tests/Certificates/CertValidationTests.cs
+++ b/tests/StackExchange.Redis.Tests/Certificates/CertValidationTests.cs
@@ -16,30 +16,39 @@ public void CheckIssuerValidity()
// Trusting CA explicitly
var callback = ConfigurationOptions.TrustIssuerCallback(Path.Combine("Certificates", "ca.foo.com.pem"));
- Assert.True(callback(this, endpointCert, null, SslPolicyErrors.None));
- Assert.True(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors));
- Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNameMismatch));
- Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNotAvailable));
- Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNameMismatch));
- Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNotAvailable));
+ Assert.True(callback(this, endpointCert, null, SslPolicyErrors.None), "subtest 1a");
+ Assert.True(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors), "subtest 1b");
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNameMismatch), "subtest 1c");
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNotAvailable), "subtest 1d");
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNameMismatch), "subtest 1e");
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNotAvailable), "subtest 1f");
// Trusting the remote endpoint cert directly
callback = ConfigurationOptions.TrustIssuerCallback(Path.Combine("Certificates", "device01.foo.com.pem"));
- Assert.True(callback(this, endpointCert, null, SslPolicyErrors.None));
- Assert.True(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors));
- Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNameMismatch));
- Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNotAvailable));
- Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNameMismatch));
- Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNotAvailable));
+ Assert.True(callback(this, endpointCert, null, SslPolicyErrors.None), "subtest 2a");
+ if (Runtime.IsMono)
+ {
+ // Mono doesn't support this cert usage, reports as rejection (happy for someone to work around this, but isn't high priority)
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors), "subtest 2b");
+ }
+ else
+ {
+ Assert.True(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors), "subtest 2b");
+ }
+
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNameMismatch), "subtest 2c");
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNotAvailable), "subtest 2d");
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNameMismatch), "subtest 2e");
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNotAvailable), "subtest 2f");
// Attempting to trust another CA (mismatch)
callback = ConfigurationOptions.TrustIssuerCallback(Path.Combine("Certificates", "ca2.foo.com.pem"));
- Assert.True(callback(this, endpointCert, null, SslPolicyErrors.None));
- Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors));
- Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNameMismatch));
- Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNotAvailable));
- Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNameMismatch));
- Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNotAvailable));
+ Assert.True(callback(this, endpointCert, null, SslPolicyErrors.None), "subtest 3a");
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors), "subtest 3b");
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNameMismatch), "subtest 3c");
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNotAvailable), "subtest 3d");
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNameMismatch), "subtest 3e");
+ Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNotAvailable), "subtest 3f");
}
private static X509Certificate2 LoadCert(string certificatePath) => new X509Certificate2(File.ReadAllBytes(certificatePath));
diff --git a/tests/StackExchange.Redis.Tests/FormatTests.cs b/tests/StackExchange.Redis.Tests/FormatTests.cs
index 451db8c20..0054ce11d 100644
--- a/tests/StackExchange.Redis.Tests/FormatTests.cs
+++ b/tests/StackExchange.Redis.Tests/FormatTests.cs
@@ -68,7 +68,10 @@ public void ParseEndPoint(string data, EndPoint expected, string? expectedFormat
[InlineData(CommandFlags.DemandReplica | CommandFlags.FireAndForget, "PreferMaster, FireAndForget, DemandReplica")] // 2-bit flag is hit-and-miss
#endif
public void CommandFlagsFormatting(CommandFlags value, string expected)
- => Assert.Equal(expected, value.ToString());
+ {
+ Assert.SkipWhen(Runtime.IsMono, "Mono has different enum flag behavior");
+ Assert.Equal(expected, value.ToString());
+ }
[Theory]
[InlineData(ClientType.Normal, "Normal")]