Skip to content

Commit 73cd045

Browse files
brantburnettjeffrymorris
authored andcommitted
NCBC-4120: Prevent Capella CA certificate disposal on .NET 10
Motivation ---------- SSL certificate validation callbacks in .NET 10 gained an extra dispose operation on some X509Chain certificates. This is causing the shared copy of the certificate to be disposed, which in turn causes subsequent operations to fail with NREs during certificate validation. Modifications ------------- - Remove the Capella CA certificate from the ExtraStore before the callback completes - Use a UTF-8 encoded constant that reads directly from embedded storage in the binary for the certificate, avoiding some minor additional memory allocations and transcoding during startup Change-Id: Id4c9cadcb77e6996b4d6791112c9abb440080d62 Reviewed-on: https://review.couchbase.org/c/couchbase-net-client/+/238451 Reviewed-by: Jeffry Morris <jeffrymorris@gmail.com> Tested-by: Build Bot <build@couchbase.com>
1 parent 32f05a7 commit 73cd045

1 file changed

Lines changed: 42 additions & 30 deletions

File tree

src/Couchbase/Core/IO/Authentication/X509/CertificateFactory.cs

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Net.Security;
5-
using System.Security.Cryptography;
65
using System.Security.Cryptography.X509Certificates;
76
using Couchbase.Core.Compatibility;
87
using Couchbase.Core.Logging;
@@ -57,26 +56,28 @@ public static ICertificateFactory GetCertificatesFromStore(CertificateStoreSearc
5756
/// <remarks>
5857
/// This in-memory certificate does not work on .NET Framework (legacy) clients.
5958
/// </remarks>
60-
internal const string CapellaCaCertPem =
61-
@"-----BEGIN CERTIFICATE-----
62-
MIIDFTCCAf2gAwIBAgIRANLVkgOvtaXiQJi0V6qeNtswDQYJKoZIhvcNAQELBQAw
63-
JDESMBAGA1UECgwJQ291Y2hiYXNlMQ4wDAYDVQQLDAVDbG91ZDAeFw0xOTEyMDYy
64-
MjEyNTlaFw0yOTEyMDYyMzEyNTlaMCQxEjAQBgNVBAoMCUNvdWNoYmFzZTEOMAwG
65-
A1UECwwFQ2xvdWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfvOIi
66-
enG4Dp+hJu9asdxEMRmH70hDyMXv5ZjBhbo39a42QwR59y/rC/sahLLQuNwqif85
67-
Fod1DkqgO6Ng3vecSAwyYVkj5NKdycQu5tzsZkghlpSDAyI0xlIPSQjoORA/pCOU
68-
WOpymA9dOjC1bo6rDyw0yWP2nFAI/KA4Z806XeqLREuB7292UnSsgFs4/5lqeil6
69-
rL3ooAw/i0uxr/TQSaxi1l8t4iMt4/gU+W52+8Yol0JbXBTFX6itg62ppb/Eugmn
70-
mQRMgL67ccZs7cJ9/A0wlXencX2ohZQOR3mtknfol3FH4+glQFn27Q4xBCzVkY9j
71-
KQ20T1LgmGSngBInAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
72-
FJQOBPvrkU2In1Sjoxt97Xy8+cKNMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B
73-
AQsFAAOCAQEARgM6XwcXPLSpFdSf0w8PtpNGehmdWijPM3wHb7WZiS47iNen3oq8
74-
m2mm6V3Z57wbboPpfI+VEzbhiDcFfVnK1CXMC0tkF3fnOG1BDDvwt4jU95vBiNjY
75-
xdzlTP/Z+qr0cnVbGBSZ+fbXstSiRaaAVcqQyv3BRvBadKBkCyPwo+7svQnScQ5P
76-
Js7HEHKVms5tZTgKIw1fbmgR2XHleah1AcANB+MAPBCcTgqurqr5G7W2aPSBLLGA
77-
fRIiVzm7VFLc7kWbp7ENH39HVG6TZzKnfl9zJYeiklo5vQQhGSMhzBsO70z4RRzi
78-
DPFAN/4qZAgD5q3AFNIq2WWADFQGSwVJhg==
79-
-----END CERTIFICATE-----";
59+
internal static ReadOnlySpan<byte> CapellaCaCertPem =>
60+
"""
61+
-----BEGIN CERTIFICATE-----
62+
MIIDFTCCAf2gAwIBAgIRANLVkgOvtaXiQJi0V6qeNtswDQYJKoZIhvcNAQELBQAw
63+
JDESMBAGA1UECgwJQ291Y2hiYXNlMQ4wDAYDVQQLDAVDbG91ZDAeFw0xOTEyMDYy
64+
MjEyNTlaFw0yOTEyMDYyMzEyNTlaMCQxEjAQBgNVBAoMCUNvdWNoYmFzZTEOMAwG
65+
A1UECwwFQ2xvdWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfvOIi
66+
enG4Dp+hJu9asdxEMRmH70hDyMXv5ZjBhbo39a42QwR59y/rC/sahLLQuNwqif85
67+
Fod1DkqgO6Ng3vecSAwyYVkj5NKdycQu5tzsZkghlpSDAyI0xlIPSQjoORA/pCOU
68+
WOpymA9dOjC1bo6rDyw0yWP2nFAI/KA4Z806XeqLREuB7292UnSsgFs4/5lqeil6
69+
rL3ooAw/i0uxr/TQSaxi1l8t4iMt4/gU+W52+8Yol0JbXBTFX6itg62ppb/Eugmn
70+
mQRMgL67ccZs7cJ9/A0wlXencX2ohZQOR3mtknfol3FH4+glQFn27Q4xBCzVkY9j
71+
KQ20T1LgmGSngBInAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
72+
FJQOBPvrkU2In1Sjoxt97Xy8+cKNMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B
73+
AQsFAAOCAQEARgM6XwcXPLSpFdSf0w8PtpNGehmdWijPM3wHb7WZiS47iNen3oq8
74+
m2mm6V3Z57wbboPpfI+VEzbhiDcFfVnK1CXMC0tkF3fnOG1BDDvwt4jU95vBiNjY
75+
xdzlTP/Z+qr0cnVbGBSZ+fbXstSiRaaAVcqQyv3BRvBadKBkCyPwo+7svQnScQ5P
76+
Js7HEHKVms5tZTgKIw1fbmgR2XHleah1AcANB+MAPBCcTgqurqr5G7W2aPSBLLGA
77+
fRIiVzm7VFLc7kWbp7ENH39HVG6TZzKnfl9zJYeiklo5vQQhGSMhzBsO70z4RRzi
78+
DPFAN/4qZAgD5q3AFNIq2WWADFQGSwVJhg==
79+
-----END CERTIFICATE-----
80+
"""u8;
8081

8182
/// <summary>
8283
/// The certificate to use by default for connecting to *.cloud.couchbase.com.
@@ -87,12 +88,11 @@ public static ICertificateFactory GetCertificatesFromStore(CertificateStoreSearc
8788
[InterfaceStability(Level.Volatile)]
8889
#if NETSTANDARD2_0 || NETSTANDARD2_1 || NET8_0
8990
internal static readonly X509Certificate2 CapellaCaCert = new X509Certificate2(
90-
rawData: System.Text.Encoding.ASCII.GetBytes(CapellaCaCertPem),
91+
rawData: CapellaCaCertPem.ToArray(),
9192
password: (string?)null);
9293
#else
9394
internal static X509Certificate2 CapellaCaCert =
94-
X509CertificateLoader.LoadCertificate(
95-
System.Text.Encoding.ASCII.GetBytes(CapellaCaCertPem));
95+
X509CertificateLoader.LoadCertificate(CapellaCaCertPem);
9696
#endif
9797

9898
/// <summary>
@@ -136,15 +136,27 @@ internal static RemoteCertificateValidationCallback GetValidatorWithPredefinedCe
136136
{
137137
// first attempt - validation from system trust store plus whatever certs have been provided
138138
// user supplied or Capella. If self-signed, will not be sufficient and need to be CustomTrustStore
139-
foreach (var defaultCert in certs)
139+
140+
bool built;
141+
try
140142
{
141-
MaybeLogCert("X509 adding from certs to ExtraStore", defaultCert, logger);
142-
chain.ChainPolicy.ExtraStore.Add(defaultCert);
143-
}
143+
foreach (var defaultCert in certs)
144+
{
145+
MaybeLogCert("X509 adding from certs to ExtraStore", defaultCert, logger);
146+
chain.ChainPolicy.ExtraStore.Add(new X509Certificate2(defaultCert));
147+
}
144148

145-
MaybeLogChainElements("X509 chain element cert is", chain, logger);
149+
MaybeLogChainElements("X509 chain element cert is", chain, logger);
150+
151+
built = chain.Build(cert2);
152+
}
153+
finally
154+
{
155+
// Remove the extra certs so they aren't disposed during .NET cleanup after
156+
// the callback completes.
157+
chain.ChainPolicy.ExtraStore.Clear();
158+
}
146159

147-
var built = chain.Build(cert2);
148160
if (!built &&
149161
(chain.ChainStatus.First().Status ==
150162
X509ChainStatusFlags.UntrustedRoot /* probably Capella self-signed KV */ ||

0 commit comments

Comments
 (0)