Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hold temporary keychain refs for certificates imported with X509Certificate2Collection.Import #82205

Merged
merged 7 commits into from
Feb 16, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ internal static SafeSecIdentityHandle X509CopyWithPrivateKey(
out identityHandle,
out osStatus);

SafeTemporaryKeychainHandle.TrackItem(identityHandle);

if (result == 1)
{
Debug.Assert(!identityHandle.IsInvalid);
Expand All @@ -288,13 +290,25 @@ internal static SafeSecIdentityHandle X509CopyWithPrivateKey(
{
SafeSecIdentityHandle identityHandle;
int osStatus;
int result;

int result = AppleCryptoNative_X509MoveToKeychain(
cert,
targetKeychain,
privateKey ?? SafeSecKeyRefHandle.InvalidHandle,
out identityHandle,
out osStatus);
// Ensure the keychain is not disposed, if there is any associated one
using (SafeKeychainHandle keychain = Interop.AppleCrypto.SecKeychainItemCopyKeychain(cert))
{
// AppleCryptoNative_X509MoveToKeychain can change the keychain of the input
// certificate, so we need to reflect that change.
SafeTemporaryKeychainHandle.UntrackItem(cert.DangerousGetHandle());
vcsjones marked this conversation as resolved.
Show resolved Hide resolved

result = AppleCryptoNative_X509MoveToKeychain(
cert,
targetKeychain,
privateKey ?? SafeSecKeyRefHandle.InvalidHandle,
out identityHandle,
out osStatus);

SafeTemporaryKeychainHandle.TrackItem(cert);
SafeTemporaryKeychainHandle.TrackItem(identityHandle);
}

if (result == 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,5 @@ public static ICertificatePal FromBlob(

return result ?? FromDerBlob(rawData, GetDerCertContentType(rawData), password, keyStorageFlags);
}

// No temporary keychain on iOS
partial void DisposeTempKeychain();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ namespace System.Security.Cryptography.X509Certificates
{
internal sealed partial class AppleCertificatePal : ICertificatePal
{
private SafeKeychainHandle? _tempKeychain;

public static ICertificatePal FromBlob(
ReadOnlySpan<byte> rawData,
SafePasswordHandle password,
Expand Down Expand Up @@ -53,20 +51,7 @@ public static ICertificatePal FromBlob(

using (keychain)
{
AppleCertificatePal ret = ImportPkcs12(rawData, password, exportable, keychain);
if (!persist)
{
// If we used temporary keychain we need to prevent deletion.
// on 10.15+ if keychain is unlinked, certain certificate operations may fail.
bool success = false;
keychain.DangerousAddRef(ref success);
if (success)
{
ret._tempKeychain = keychain;
}
}

return ret;
return ImportPkcs12(rawData, password, exportable, keychain);
}
}

Expand All @@ -92,11 +77,6 @@ public static ICertificatePal FromBlob(
throw new CryptographicException();
}

public void DisposeTempKeychain()
{
Interlocked.Exchange(ref _tempKeychain, null)?.Dispose();
}

internal unsafe byte[] ExportPkcs8(ReadOnlySpan<char> password)
{
Debug.Assert(_identityHandle != null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,6 @@ public void Dispose()

_certHandle = null!;
_identityHandle = null;

DisposeTempKeychain();
}

internal SafeSecCertificateHandle CertificateHandle => _certHandle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public void MoveTo(X509Certificate2Collection collection)

using (safeSecKeyRefHandle)
{
ICertificatePal newPal;
AppleCertificatePal newPal;

// SecItemImport doesn't seem to respect non-exportable import for PKCS#8,
// only PKCS#12.
Expand All @@ -119,6 +119,11 @@ public void MoveTo(X509Certificate2Collection collection)

X509Certificate2 cert = new X509Certificate2(newPal);
collection.Add(cert);

if (newPal != pal)
{
pal.Dispose();
}
Comment on lines +123 to +126
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: With this object disposed in deterministic manner the existing tests should reliably trip on miscounted references without a GC being triggered.

}
}
}
Expand Down