diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.cs index ad5a34517b1a5..e7c08596a2c39 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.cs @@ -178,15 +178,38 @@ namespace System.Security.Cryptography.Apple { internal sealed class SafeSecKeyRefHandle : SafeHandle { + private SafeHandle? _parentHandle; + public SafeSecKeyRefHandle() : base(IntPtr.Zero, ownsHandle: true) { } + internal void SetParentHandle(SafeHandle parentHandle) + { + Debug.Assert(_parentHandle is null); + + bool added = false; + parentHandle.DangerousAddRef(ref added); + _parentHandle = parentHandle; + + // If we became invalid while the parent handle was being incremented, release the parent handle since + // ReleaseHandle will not get called. + if (IsInvalid) + { + _parentHandle.DangerousRelease(); + _parentHandle = null; + } + } + protected override bool ReleaseHandle() { Interop.CoreFoundation.CFRelease(handle); SetHandle(IntPtr.Zero); + + _parentHandle?.DangerousRelease(); + _parentHandle = null; + return true; } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.Keys.macOS.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.Keys.macOS.cs index d926074d88c48..228e70098870d 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.Keys.macOS.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.Keys.macOS.cs @@ -29,6 +29,7 @@ internal sealed partial class AppleCertificatePal : ICertificatePal publicKey = Interop.AppleCrypto.ImportEphemeralKey(_certData.SubjectPublicKeyInfo, false); } + privateKey.SetParentHandle(_certHandle); return new DSAImplementation.DSASecurityTransforms(publicKey, privateKey); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.cs index 1a6cf9467e4dd..781e08be5519b 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.cs @@ -330,6 +330,7 @@ public byte[] SubjectPublicKeyInfo Debug.Assert(!_identityHandle.IsInvalid); SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.X509GetPublicKey(_certHandle); SafeSecKeyRefHandle privateKey = Interop.AppleCrypto.X509GetPrivateKeyFromIdentity(_identityHandle); + privateKey.SetParentHandle(_certHandle); Debug.Assert(!publicKey.IsInvalid); return new RSAImplementation.RSASecurityTransforms(publicKey, privateKey); @@ -343,6 +344,7 @@ public byte[] SubjectPublicKeyInfo Debug.Assert(!_identityHandle.IsInvalid); SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.X509GetPublicKey(_certHandle); SafeSecKeyRefHandle privateKey = Interop.AppleCrypto.X509GetPrivateKeyFromIdentity(_identityHandle); + privateKey.SetParentHandle(_certHandle); Debug.Assert(!publicKey.IsInvalid); return new ECDsaImplementation.ECDsaSecurityTransforms(publicKey, privateKey); @@ -356,6 +358,7 @@ public byte[] SubjectPublicKeyInfo Debug.Assert(!_identityHandle.IsInvalid); SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.X509GetPublicKey(_certHandle); SafeSecKeyRefHandle privateKey = Interop.AppleCrypto.X509GetPrivateKeyFromIdentity(_identityHandle); + privateKey.SetParentHandle(_certHandle); Debug.Assert(!publicKey.IsInvalid); return new ECDiffieHellmanImplementation.ECDiffieHellmanSecurityTransforms(publicKey, privateKey); diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertTests.cs index c51987b37ca3a..4d4a46e33efcc 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertTests.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; +using System.Security.Cryptography.Dsa.Tests; +using System.Security.Cryptography.X509Certificates.Tests.CertificateCreation; using System.Threading; using Microsoft.DotNet.XUnitExtensions; using Test.Cryptography; @@ -25,6 +27,155 @@ public CertTests(ITestOutputHelper output) _log = output; } + [Fact] + public static void PublicPrivateKey_IndependentLifetimes_ECDsa() + { + X509Certificate2 loaded; + + using (ECDsa ca = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + { + CertificateRequest req = new("CN=potatos", ca, HashAlgorithmName.SHA256); + + using (X509Certificate2 cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddDays(3))) + { + loaded = new X509Certificate2(cert.Export(X509ContentType.Pkcs12, "carrots"), "carrots"); + } + } + + using (ECDsa verifyKey = loaded.GetECDsaPublicKey()) + { + byte[] signature; + byte[] data = RandomNumberGenerator.GetBytes(32); + + using (ECDsa signingKey = loaded.GetECDsaPrivateKey()) + { + loaded.Dispose(); + signature = signingKey.SignHash(data); + } + + Assert.True(verifyKey.VerifyHash(data, signature), nameof(verifyKey.VerifyHash)); + } + } + + [Fact] + public static void PublicPrivateKey_IndependentLifetimes_ECDiffieHellman() + { + X509Certificate2 loaded; + + using (ECDsa ca = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + using (ECDiffieHellman ecdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256)) + { + CertificateRequest issuerRequest = new CertificateRequest( + new X500DistinguishedName("CN=root"), + ca, + HashAlgorithmName.SHA256); + + issuerRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(true, false, 0, true)); + + CertificateRequest request = new CertificateRequest( + new X500DistinguishedName("CN=potato"), + new PublicKey(ecdh), + HashAlgorithmName.SHA256); + + request.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, true)); + request.CertificateExtensions.Add( + new X509KeyUsageExtension(X509KeyUsageFlags.KeyAgreement, true)); + + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(30); + byte[] serial = [1, 2, 3, 4, 5, 6, 7, 8]; + + using (X509Certificate2 issuer = issuerRequest.CreateSelfSigned(notBefore, notAfter)) + using (X509Certificate2 cert = request.Create(issuer, notBefore, notAfter, serial)) + using (X509Certificate2 certWithKey = cert.CopyWithPrivateKey(ecdh)) + { + loaded = new X509Certificate2(certWithKey.Export(X509ContentType.Pkcs12, "carrots"), "carrots");; + } + } + + using (ECDiffieHellman partyB = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256)) + using (ECDiffieHellman partyAPrivateKey = loaded.GetECDiffieHellmanPrivateKey()) + using (ECDiffieHellman partyAPublicKey = loaded.GetECDiffieHellmanPublicKey()) + { + loaded.Dispose(); + byte[] derivedB = partyB.DeriveKeyFromHash(partyAPublicKey.PublicKey, HashAlgorithmName.SHA256, null, null); + byte[] derivedA = partyAPrivateKey.DeriveKeyFromHash(partyB.PublicKey, HashAlgorithmName.SHA256, null, null); + Assert.Equal(derivedB, derivedA); + } + } + + [Fact] + public static void PublicPrivateKey_IndependentLifetimes_RSA() + { + X509Certificate2 loaded; + + using (RSA ca = RSA.Create(2048)) + { + CertificateRequest req = new("CN=potatos", ca, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + + using (X509Certificate2 cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddDays(3))) + { + loaded = new X509Certificate2(cert.Export(X509ContentType.Pkcs12, "carrots"), "carrots"); + } + } + + using (RSA verifyKey = loaded.GetRSAPublicKey()) + { + byte[] signature; + byte[] data = RandomNumberGenerator.GetBytes(32); + + using (RSA signingKey = loaded.GetRSAPrivateKey()) + { + loaded.Dispose(); + signature = signingKey.SignHash(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } + + Assert.True(verifyKey.VerifyHash(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1), nameof(verifyKey.VerifyHash)); + } + } + + [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] + public static void PublicPrivateKey_IndependentLifetimes_DSA() + { + X509Certificate2 loaded; + + using (DSA ca = DSA.Create()) + { + ca.ImportParameters(DSATestData.GetDSA1024Params()); + DSAX509SignatureGenerator gen = new DSAX509SignatureGenerator(ca); + X500DistinguishedName dn = new X500DistinguishedName("CN=potatos"); + + CertificateRequest req = new CertificateRequest( + dn, + gen.PublicKey, + HashAlgorithmName.SHA1); + + using (X509Certificate2 cert = req.Create(dn, gen, DateTimeOffset.Now, DateTimeOffset.Now.AddDays(3), new byte[] { 1, 2, 3 })) + using (X509Certificate2 certWithKey = cert.CopyWithPrivateKey(ca)) + { + + loaded = new X509Certificate2(certWithKey.Export(X509ContentType.Pkcs12, "carrots"), "carrots"); + } + } + + using (DSA verifyKey = loaded.GetDSAPublicKey()) + { + byte[] signature; + byte[] data = RandomNumberGenerator.GetBytes(20); + + using (DSA signingKey = loaded.GetDSAPrivateKey()) + { + loaded.Dispose(); + signature = signingKey.CreateSignature(data); + } + + Assert.True(verifyKey.VerifySignature(data, signature), nameof(verifyKey.VerifySignature)); + } + } + [Fact] public static void RaceDisposeAndKeyAccess() {