diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.iOS.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.iOS.cs new file mode 100644 index 00000000000000..32346eaacb388a --- /dev/null +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.iOS.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; + +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class AppleCrypto + { + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_SecKeychainEnumerateCerts( + out SafeCFArrayHandle matches); + + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_SecKeychainEnumerateIdentities( + out SafeCFArrayHandle matches); + + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_X509StoreAddCertificate( + SafeHandle certOrIdentity); + + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_X509StoreRemoveCertificate( + SafeHandle certOrIdentity, + bool isReadOnlyMode); + + internal static SafeCFArrayHandle KeychainEnumerateCerts() + { + SafeCFArrayHandle matches; + int osStatus = AppleCryptoNative_SecKeychainEnumerateCerts(out matches); + + if (osStatus == 0) + { + return matches; + } + + matches.Dispose(); + throw CreateExceptionForOSStatus(osStatus); + } + + internal static SafeCFArrayHandle KeychainEnumerateIdentities() + { + SafeCFArrayHandle matches; + int osStatus = AppleCryptoNative_SecKeychainEnumerateIdentities(out matches); + + if (osStatus == 0) + { + return matches; + } + + matches.Dispose(); + throw CreateExceptionForOSStatus(osStatus); + } + + internal static void X509StoreAddCertificate(SafeHandle certOrIdentity) + { + int osStatus = AppleCryptoNative_X509StoreAddCertificate(certOrIdentity); + + if (osStatus != 0) + { + throw CreateExceptionForOSStatus(osStatus); + } + } + + internal static void X509StoreRemoveCertificate(SafeHandle certOrIdentity, bool isReadOnlyMode) + { + const int errSecItemNotFound = -25300; + + int osStatus = AppleCryptoNative_X509StoreRemoveCertificate(certOrIdentity, isReadOnlyMode); + + if (osStatus == 0 && isReadOnlyMode) + { + // The certificate exists in the store otherwise we would get errSecItemNotFound error + throw new CryptographicException(SR.Cryptography_X509_StoreReadOnly); + } + + if (osStatus != 0 && osStatus != errSecItemNotFound) + { + throw CreateExceptionForOSStatus(osStatus); + } + } + } +} diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.macOS.cs similarity index 85% rename from src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.cs rename to src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.macOS.cs index 5ba6dda27a2bf6..862760aa2c8de5 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.macOS.cs @@ -66,6 +66,19 @@ private static extern int AppleCryptoNative_SecKeychainEnumerateIdentities( out SafeCFArrayHandle matches, out int pOSStatus); + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_X509StoreAddCertificate( + SafeKeychainItemHandle cert, + SafeKeychainHandle keychain, + out int pOSStatus); + + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_X509StoreRemoveCertificate( + SafeKeychainItemHandle cert, + SafeKeychainHandle keychain, + bool isReadOnlyMode, + out int pOSStatus); + private static SafeKeychainHandle SecKeychainItemCopyKeychain(SafeHandle item) { bool addedRef = false; @@ -290,6 +303,54 @@ internal static void SecKeychainDelete(IntPtr handle, bool throwOnError=true) throw CreateExceptionForOSStatus(osStatus); } } + + internal static void X509StoreAddCertificate(SafeKeychainItemHandle certOrIdentity, SafeKeychainHandle keychain) + { + int osStatus; + int ret = AppleCryptoNative_X509StoreAddCertificate(certOrIdentity, keychain, out osStatus); + + if (ret == 0) + { + throw CreateExceptionForOSStatus(osStatus); + } + + if (ret != 1) + { + Debug.Fail($"Unexpected result from AppleCryptoNative_X509StoreAddCertificate: {ret}"); + throw new CryptographicException(); + } + } + + internal static void X509StoreRemoveCertificate(SafeKeychainItemHandle certHandle, SafeKeychainHandle keychain, bool isReadOnlyMode) + { + int osStatus; + int ret = AppleCryptoNative_X509StoreRemoveCertificate(certHandle, keychain, isReadOnlyMode, out osStatus); + + if (ret == 0) + { + throw CreateExceptionForOSStatus(osStatus); + } + + const int SuccessOrNoMatch = 1; + const int UserTrustExists = 2; + const int AdminTrustExists = 3; + const int ReadOnlyDelete = 4; + + switch (ret) + { + case SuccessOrNoMatch: + break; + case UserTrustExists: + throw new CryptographicException(SR.Cryptography_X509Store_WouldModifyUserTrust); + case AdminTrustExists: + throw new CryptographicException(SR.Cryptography_X509Store_WouldModifyAdminTrust); + case ReadOnlyDelete: + throw new CryptographicException(SR.Cryptography_X509_StoreReadOnly); + default: + Debug.Fail($"Unexpected result from AppleCryptoNative_X509StoreRemoveCertificate: {ret}"); + throw new CryptographicException(); + } + } } } diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs index daa1d95f8ccdbf..d50ca014c4e3a9 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs @@ -14,54 +14,6 @@ internal static partial class Interop { internal static partial class AppleCrypto { - private static readonly SafeCreateHandle s_emptyExportString = - CoreFoundation.CFStringCreateWithCString(""); - - private static int AppleCryptoNative_X509ImportCertificate( - ReadOnlySpan keyBlob, - X509ContentType contentType, - SafeCreateHandle cfPfxPassphrase, - SafeKeychainHandle tmpKeychain, - int exportable, - out SafeSecCertificateHandle pCertOut, - out SafeSecIdentityHandle pPrivateKeyOut, - out int pOSStatus) - { - return AppleCryptoNative_X509ImportCertificate( - ref MemoryMarshal.GetReference(keyBlob), - keyBlob.Length, - contentType, - cfPfxPassphrase, - tmpKeychain, - exportable, - out pCertOut, - out pPrivateKeyOut, - out pOSStatus); - } - - [DllImport(Libraries.AppleCryptoNative)] - private static extern int AppleCryptoNative_X509ImportCertificate( - ref byte pbKeyBlob, - int cbKeyBlob, - X509ContentType contentType, - SafeCreateHandle cfPfxPassphrase, - SafeKeychainHandle tmpKeychain, - int exportable, - out SafeSecCertificateHandle pCertOut, - out SafeSecIdentityHandle pPrivateKeyOut, - out int pOSStatus); - - [DllImport(Libraries.AppleCryptoNative)] - private static extern int AppleCryptoNative_X509ImportCollection( - ref byte pbKeyBlob, - int cbKeyBlob, - X509ContentType contentType, - SafeCreateHandle cfPfxPassphrase, - SafeKeychainHandle tmpKeychain, - int exportable, - out SafeCFArrayHandle pCollectionOut, - out int pOSStatus); - [DllImport(Libraries.AppleCryptoNative)] private static extern int AppleCryptoNative_X509GetRawData( SafeSecCertificateHandle cert, @@ -93,30 +45,6 @@ private static extern int AppleCryptoNative_X509DemuxAndRetainHandle( out SafeSecCertificateHandle certHandle, out SafeSecIdentityHandle identityHandle); - [DllImport(Libraries.AppleCryptoNative)] - private static extern int AppleCryptoNative_X509ExportData( - SafeCreateHandle data, - X509ContentType type, - SafeCreateHandle cfExportPassphrase, - out SafeCFDataHandle pExportOut, - out int pOSStatus); - - [DllImport(Libraries.AppleCryptoNative)] - private static extern int AppleCryptoNative_X509CopyWithPrivateKey( - SafeSecCertificateHandle certHandle, - SafeSecKeyRefHandle privateKeyHandle, - SafeKeychainHandle targetKeychain, - out SafeSecIdentityHandle pIdentityHandleOut, - out int pOSStatus); - - [DllImport(Libraries.AppleCryptoNative)] - private static extern int AppleCryptoNative_X509MoveToKeychain( - SafeSecCertificateHandle certHandle, - SafeKeychainHandle targetKeychain, - SafeSecKeyRefHandle privateKeyHandle, - out SafeSecIdentityHandle pIdentityHandleOut, - out int pOSStatus); - internal static byte[] X509GetRawData(SafeSecCertificateHandle cert) { int osStatus; @@ -141,198 +69,11 @@ internal static byte[] X509GetRawData(SafeSecCertificateHandle cert) throw new CryptographicException(); } - internal static SafeSecCertificateHandle X509ImportCertificate( - ReadOnlySpan bytes, - X509ContentType contentType, - SafePasswordHandle importPassword, - SafeKeychainHandle keychain, - bool exportable, - out SafeSecIdentityHandle identityHandle) - { - SafeCreateHandle? cfPassphrase = null; - bool releasePassword = false; - - try - { - if (!importPassword.IsInvalid) - { - importPassword.DangerousAddRef(ref releasePassword); - cfPassphrase = CoreFoundation.CFStringCreateFromSpan(importPassword.DangerousGetSpan()); - } - - return X509ImportCertificate( - bytes, - contentType, - cfPassphrase, - keychain, - exportable, - out identityHandle); - } - finally - { - if (releasePassword) - { - importPassword.DangerousRelease(); - } - - cfPassphrase?.Dispose(); - } - } - - private static SafeSecCertificateHandle X509ImportCertificate( - ReadOnlySpan bytes, - X509ContentType contentType, - SafeCreateHandle? importPassword, - SafeKeychainHandle keychain, - bool exportable, - out SafeSecIdentityHandle identityHandle) - { - SafeSecCertificateHandle certHandle; - int osStatus; - - SafeCreateHandle cfPassphrase = importPassword ?? s_nullExportString; - - int ret = AppleCryptoNative_X509ImportCertificate( - bytes, - contentType, - cfPassphrase, - keychain, - exportable ? 1 : 0, - out certHandle, - out identityHandle, - out osStatus); - - SafeTemporaryKeychainHandle.TrackItem(certHandle); - SafeTemporaryKeychainHandle.TrackItem(identityHandle); - - if (ret == 1) - { - return certHandle; - } - - certHandle.Dispose(); - identityHandle.Dispose(); - - const int SeeOSStatus = 0; - const int ImportReturnedEmpty = -2; - const int ImportReturnedNull = -3; - - switch (ret) - { - case SeeOSStatus: - throw CreateExceptionForOSStatus(osStatus); - case ImportReturnedNull: - case ImportReturnedEmpty: - throw new CryptographicException(); - default: - Debug.Fail($"Unexpected return value {ret}"); - throw new CryptographicException(); - } - } - - internal static SafeCFArrayHandle X509ImportCollection( - ReadOnlySpan bytes, - X509ContentType contentType, - SafePasswordHandle importPassword, - SafeKeychainHandle keychain, - bool exportable) - { - SafeCreateHandle cfPassphrase = s_nullExportString; - bool releasePassword = false; - - int ret; - SafeCFArrayHandle collectionHandle; - int osStatus; - - try - { - if (!importPassword.IsInvalid) - { - importPassword.DangerousAddRef(ref releasePassword); - IntPtr passwordHandle = importPassword.DangerousGetHandle(); - - if (passwordHandle != IntPtr.Zero) - { - cfPassphrase = CoreFoundation.CFStringCreateWithCString(passwordHandle); - } - } - - ret = AppleCryptoNative_X509ImportCollection( - ref MemoryMarshal.GetReference(bytes), - bytes.Length, - contentType, - cfPassphrase, - keychain, - exportable ? 1 : 0, - out collectionHandle, - out osStatus); - - if (ret == 1) - { - return collectionHandle; - } - } - finally - { - if (releasePassword) - { - importPassword.DangerousRelease(); - } - - if (cfPassphrase != s_nullExportString) - { - cfPassphrase.Dispose(); - } - } - - collectionHandle.Dispose(); - - const int SeeOSStatus = 0; - const int ImportReturnedEmpty = -2; - const int ImportReturnedNull = -3; - - switch (ret) - { - case SeeOSStatus: - throw CreateExceptionForOSStatus(osStatus); - case ImportReturnedNull: - case ImportReturnedEmpty: - throw new CryptographicException(); - default: - Debug.Fail($"Unexpected return value {ret}"); - throw new CryptographicException(); - } - } - - internal static SafeSecCertificateHandle X509GetCertFromIdentity(SafeSecIdentityHandle identity) - { - SafeSecCertificateHandle cert; - int osStatus = AppleCryptoNative_X509CopyCertFromIdentity(identity, out cert); - - SafeTemporaryKeychainHandle.TrackItem(cert); - - if (osStatus != 0) - { - cert.Dispose(); - throw CreateExceptionForOSStatus(osStatus); - } - - if (cert.IsInvalid) - { - cert.Dispose(); - throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); - } - - return cert; - } - internal static SafeSecKeyRefHandle X509GetPrivateKeyFromIdentity(SafeSecIdentityHandle identity) { SafeSecKeyRefHandle key; int osStatus = AppleCryptoNative_X509CopyPrivateKeyFromIdentity(identity, out key); - //SafeTemporaryKeychainHandle.TrackItem(key); - if (osStatus != 0) { key.Dispose(); @@ -354,8 +95,6 @@ internal static SafeSecKeyRefHandle X509GetPublicKey(SafeSecCertificateHandle ce int osStatus; int ret = AppleCryptoNative_X509GetPublicKey(cert, out publicKey, out osStatus); - //SafeTemporaryKeychainHandle.TrackItem(publicKey); - if (ret == 1) { return publicKey; @@ -371,200 +110,5 @@ internal static SafeSecKeyRefHandle X509GetPublicKey(SafeSecCertificateHandle ce Debug.Fail($"Unexpected return value {ret}"); throw new CryptographicException(); } - - internal static bool X509DemuxAndRetainHandle( - IntPtr handle, - out SafeSecCertificateHandle certHandle, - out SafeSecIdentityHandle identityHandle) - { - int result = AppleCryptoNative_X509DemuxAndRetainHandle(handle, out certHandle, out identityHandle); - - SafeTemporaryKeychainHandle.TrackItem(certHandle); - SafeTemporaryKeychainHandle.TrackItem(identityHandle); - - switch (result) - { - case 1: - return true; - case 0: - return false; - default: - Debug.Fail($"AppleCryptoNative_X509DemuxAndRetainHandle returned {result}"); - throw new CryptographicException(); - } - } - - internal static SafeSecIdentityHandle X509CopyWithPrivateKey( - SafeSecCertificateHandle certHandle, - SafeSecKeyRefHandle privateKeyHandle, - SafeKeychainHandle targetKeychain) - { - SafeSecIdentityHandle identityHandle; - int osStatus; - - int result = AppleCryptoNative_X509CopyWithPrivateKey( - certHandle, - privateKeyHandle, - targetKeychain, - out identityHandle, - out osStatus); - - if (result == 1) - { - Debug.Assert(!identityHandle.IsInvalid); - return identityHandle; - } - - identityHandle.Dispose(); - - if (result == 0) - { - throw CreateExceptionForOSStatus(osStatus); - } - - Debug.Fail($"AppleCryptoNative_X509CopyWithPrivateKey returned {result}"); - throw new CryptographicException(); - } - - internal static SafeSecIdentityHandle? X509MoveToKeychain( - SafeSecCertificateHandle cert, - SafeKeychainHandle targetKeychain, - SafeSecKeyRefHandle? privateKey) - { - SafeSecIdentityHandle identityHandle; - int osStatus; - - int result = AppleCryptoNative_X509MoveToKeychain( - cert, - targetKeychain, - privateKey ?? SafeSecKeyRefHandle.InvalidHandle, - out identityHandle, - out osStatus); - - if (result == 0) - { - identityHandle.Dispose(); - throw CreateExceptionForOSStatus(osStatus); - } - - if (result != 1) - { - Debug.Fail($"AppleCryptoNative_X509MoveToKeychain returned {result}"); - throw new CryptographicException(); - } - - if (privateKey?.IsInvalid == false) - { - // If a PFX has a mismatched association between a private key and the - // certificate public key then MoveToKeychain will write the NULL SecIdentityRef - // (after cleaning up the temporary key). - // - // When that happens, just treat the import as public-only. - if (!identityHandle.IsInvalid) - { - return identityHandle; - } - } - - // If the cert in the PFX had no key, but it was imported with PersistKeySet (imports into - // the default keychain) and a matching private key was already there, then an - // identityHandle could be found. But that's not desirable, since neither Windows or Linux would - // do that matching. - // - // So dispose the handle, no matter what. - identityHandle.Dispose(); - return null; - } - - private static byte[] X509Export(X509ContentType contentType, SafeCreateHandle cfPassphrase, IntPtr[] certHandles) - { - Debug.Assert(contentType == X509ContentType.Pkcs7 || contentType == X509ContentType.Pkcs12); - - using (SafeCreateHandle handlesArray = CoreFoundation.CFArrayCreate(certHandles, (UIntPtr)certHandles.Length)) - { - SafeCFDataHandle exportData; - int osStatus; - - int result = AppleCryptoNative_X509ExportData( - handlesArray, - contentType, - cfPassphrase, - out exportData, - out osStatus); - - using (exportData) - { - if (result != 1) - { - if (result == 0) - { - throw CreateExceptionForOSStatus(osStatus); - } - - Debug.Fail($"Unexpected result from AppleCryptoNative_X509ExportData: {result}"); - throw new CryptographicException(); - } - - Debug.Assert(!exportData.IsInvalid, "Successful export yielded no data"); - return CoreFoundation.CFGetData(exportData); - } - } - } - - internal static byte[] X509ExportPkcs7(IntPtr[] certHandles) - { - return X509Export(X509ContentType.Pkcs7, s_nullExportString, certHandles); - } - - internal static byte[] X509ExportPfx(IntPtr[] certHandles, SafePasswordHandle exportPassword) - { - SafeCreateHandle cfPassphrase = s_emptyExportString; - bool releasePassword = false; - - try - { - if (!exportPassword.IsInvalid) - { - exportPassword.DangerousAddRef(ref releasePassword); - IntPtr passwordHandle = exportPassword.DangerousGetHandle(); - - if (passwordHandle != IntPtr.Zero) - { - cfPassphrase = CoreFoundation.CFStringCreateWithCString(passwordHandle); - } - } - - return X509Export(X509ContentType.Pkcs12, cfPassphrase, certHandles); - } - finally - { - if (releasePassword) - { - exportPassword.DangerousRelease(); - } - - if (cfPassphrase != s_emptyExportString) - { - cfPassphrase.Dispose(); - } - } - } - } -} - -namespace System.Security.Cryptography.X509Certificates -{ - internal sealed class SafeSecIdentityHandle : SafeKeychainItemHandle - { - public SafeSecIdentityHandle() - { - } - } - - internal sealed class SafeSecCertificateHandle : SafeKeychainItemHandle - { - public SafeSecCertificateHandle() - { - } } } diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.iOS.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.iOS.cs new file mode 100644 index 00000000000000..71f388cc3ecb4a --- /dev/null +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.iOS.cs @@ -0,0 +1,242 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.X509Certificates; + +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class AppleCrypto + { + private static readonly SafeCreateHandle s_emptyExportString = + CoreFoundation.CFStringCreateWithCString(""); + + private static int AppleCryptoNative_X509ImportCertificate( + ReadOnlySpan keyBlob, + X509ContentType contentType, + SafeCreateHandle cfPfxPassphrase, + out SafeSecCertificateHandle pCertOut, + out SafeSecIdentityHandle pPrivateKeyOut) + { + return AppleCryptoNative_X509ImportCertificate( + ref MemoryMarshal.GetReference(keyBlob), + keyBlob.Length, + contentType, + cfPfxPassphrase, + out pCertOut, + out pPrivateKeyOut); + } + + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_X509ImportCertificate( + ref byte pbKeyBlob, + int cbKeyBlob, + X509ContentType contentType, + SafeCreateHandle cfPfxPassphrase, + out SafeSecCertificateHandle pCertOut, + out SafeSecIdentityHandle pPrivateKeyOut); + + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_X509ImportCollection( + ref byte pbKeyBlob, + int cbKeyBlob, + X509ContentType contentType, + SafeCreateHandle cfPfxPassphrase, + out SafeCFArrayHandle pCollectionOut); + + internal static SafeSecCertificateHandle X509ImportCertificate( + ReadOnlySpan bytes, + X509ContentType contentType, + SafePasswordHandle importPassword, + out SafeSecIdentityHandle identityHandle) + { + SafeCreateHandle? cfPassphrase = null; + bool releasePassword = false; + + try + { + if (!importPassword.IsInvalid) + { + importPassword.DangerousAddRef(ref releasePassword); + cfPassphrase = CoreFoundation.CFStringCreateFromSpan(importPassword.DangerousGetSpan()); + } + + return X509ImportCertificate( + bytes, + contentType, + cfPassphrase, + out identityHandle); + } + finally + { + if (releasePassword) + { + importPassword.DangerousRelease(); + } + + cfPassphrase?.Dispose(); + } + } + + private static SafeSecCertificateHandle X509ImportCertificate( + ReadOnlySpan bytes, + X509ContentType contentType, + SafeCreateHandle? importPassword, + out SafeSecIdentityHandle identityHandle) + { + SafeSecCertificateHandle certHandle; + SafeCreateHandle cfPassphrase = importPassword ?? s_emptyExportString; + + int osStatus = AppleCryptoNative_X509ImportCertificate( + bytes, + contentType, + cfPassphrase, + out certHandle, + out identityHandle); + + if (osStatus == 0) + { + return certHandle; + } + + certHandle.Dispose(); + identityHandle.Dispose(); + + throw CreateExceptionForOSStatus(osStatus); + } + + internal static SafeCFArrayHandle X509ImportCollection( + ReadOnlySpan bytes, + X509ContentType contentType, + SafePasswordHandle importPassword) + { + SafeCreateHandle cfPassphrase = s_emptyExportString; + bool releasePassword = false; + SafeCFArrayHandle collectionHandle; + int osStatus; + + try + { + if (!importPassword.IsInvalid) + { + importPassword.DangerousAddRef(ref releasePassword); + IntPtr passwordHandle = importPassword.DangerousGetHandle(); + + if (passwordHandle != IntPtr.Zero) + { + cfPassphrase = CoreFoundation.CFStringCreateWithCString(passwordHandle); + } + } + + osStatus = AppleCryptoNative_X509ImportCollection( + ref MemoryMarshal.GetReference(bytes), + bytes.Length, + contentType, + cfPassphrase, + out collectionHandle); + + if (osStatus == 0) + { + return collectionHandle; + } + } + finally + { + if (releasePassword) + { + importPassword.DangerousRelease(); + } + + if (cfPassphrase != s_emptyExportString) + { + cfPassphrase.Dispose(); + } + } + + collectionHandle.Dispose(); + throw CreateExceptionForOSStatus(osStatus); + } + + internal static SafeSecCertificateHandle X509GetCertFromIdentity(SafeSecIdentityHandle identity) + { + SafeSecCertificateHandle cert; + int osStatus = AppleCryptoNative_X509CopyCertFromIdentity(identity, out cert); + + if (osStatus != 0) + { + cert.Dispose(); + throw CreateExceptionForOSStatus(osStatus); + } + + if (cert.IsInvalid) + { + cert.Dispose(); + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + + return cert; + } + + internal static bool X509DemuxAndRetainHandle( + IntPtr handle, + out SafeSecCertificateHandle certHandle, + out SafeSecIdentityHandle identityHandle) + { + int result = AppleCryptoNative_X509DemuxAndRetainHandle(handle, out certHandle, out identityHandle); + + switch (result) + { + case 1: + return true; + case 0: + return false; + default: + Debug.Fail($"AppleCryptoNative_X509DemuxAndRetainHandle returned {result}"); + throw new CryptographicException(); + } + } + } +} + +namespace System.Security.Cryptography.X509Certificates +{ + internal sealed class SafeSecIdentityHandle : SafeHandle + { + public SafeSecIdentityHandle() + : base(IntPtr.Zero, ownsHandle: true) + { + } + + protected override bool ReleaseHandle() + { + Interop.CoreFoundation.CFRelease(handle); + SetHandle(IntPtr.Zero); + return true; + } + + public override bool IsInvalid => handle == IntPtr.Zero; + } + + internal sealed class SafeSecCertificateHandle : SafeHandle + { + public SafeSecCertificateHandle() + : base(IntPtr.Zero, ownsHandle: true) + { + } + + protected override bool ReleaseHandle() + { + Interop.CoreFoundation.CFRelease(handle); + SetHandle(IntPtr.Zero); + return true; + } + + public override bool IsInvalid => handle == IntPtr.Zero; + } +} diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.macOS.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.macOS.cs new file mode 100644 index 00000000000000..3caca43d8ece6c --- /dev/null +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.macOS.cs @@ -0,0 +1,469 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.X509Certificates; + +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class AppleCrypto + { + private static readonly SafeCreateHandle s_emptyExportString = + CoreFoundation.CFStringCreateWithCString(""); + + private static int AppleCryptoNative_X509ImportCertificate( + ReadOnlySpan keyBlob, + X509ContentType contentType, + SafeCreateHandle cfPfxPassphrase, + SafeKeychainHandle tmpKeychain, + int exportable, + out SafeSecCertificateHandle pCertOut, + out SafeSecIdentityHandle pPrivateKeyOut, + out int pOSStatus) + { + return AppleCryptoNative_X509ImportCertificate( + ref MemoryMarshal.GetReference(keyBlob), + keyBlob.Length, + contentType, + cfPfxPassphrase, + tmpKeychain, + exportable, + out pCertOut, + out pPrivateKeyOut, + out pOSStatus); + } + + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_X509ImportCertificate( + ref byte pbKeyBlob, + int cbKeyBlob, + X509ContentType contentType, + SafeCreateHandle cfPfxPassphrase, + SafeKeychainHandle tmpKeychain, + int exportable, + out SafeSecCertificateHandle pCertOut, + out SafeSecIdentityHandle pPrivateKeyOut, + out int pOSStatus); + + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_X509ImportCollection( + ref byte pbKeyBlob, + int cbKeyBlob, + X509ContentType contentType, + SafeCreateHandle cfPfxPassphrase, + SafeKeychainHandle tmpKeychain, + int exportable, + out SafeCFArrayHandle pCollectionOut, + out int pOSStatus); + + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_X509ExportData( + SafeCreateHandle data, + X509ContentType type, + SafeCreateHandle cfExportPassphrase, + out SafeCFDataHandle pExportOut, + out int pOSStatus); + + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_X509CopyWithPrivateKey( + SafeSecCertificateHandle certHandle, + SafeSecKeyRefHandle privateKeyHandle, + SafeKeychainHandle targetKeychain, + out SafeSecIdentityHandle pIdentityHandleOut, + out int pOSStatus); + + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_X509MoveToKeychain( + SafeSecCertificateHandle certHandle, + SafeKeychainHandle targetKeychain, + SafeSecKeyRefHandle privateKeyHandle, + out SafeSecIdentityHandle pIdentityHandleOut, + out int pOSStatus); + + internal static SafeSecCertificateHandle X509ImportCertificate( + ReadOnlySpan bytes, + X509ContentType contentType, + SafePasswordHandle importPassword, + SafeKeychainHandle keychain, + bool exportable, + out SafeSecIdentityHandle identityHandle) + { + SafeCreateHandle? cfPassphrase = null; + bool releasePassword = false; + + try + { + if (!importPassword.IsInvalid) + { + importPassword.DangerousAddRef(ref releasePassword); + cfPassphrase = CoreFoundation.CFStringCreateFromSpan(importPassword.DangerousGetSpan()); + } + + return X509ImportCertificate( + bytes, + contentType, + cfPassphrase, + keychain, + exportable, + out identityHandle); + } + finally + { + if (releasePassword) + { + importPassword.DangerousRelease(); + } + + cfPassphrase?.Dispose(); + } + } + + private static SafeSecCertificateHandle X509ImportCertificate( + ReadOnlySpan bytes, + X509ContentType contentType, + SafeCreateHandle? importPassword, + SafeKeychainHandle keychain, + bool exportable, + out SafeSecIdentityHandle identityHandle) + { + SafeSecCertificateHandle certHandle; + int osStatus; + + SafeCreateHandle cfPassphrase = importPassword ?? s_nullExportString; + + int ret = AppleCryptoNative_X509ImportCertificate( + bytes, + contentType, + cfPassphrase, + keychain, + exportable ? 1 : 0, + out certHandle, + out identityHandle, + out osStatus); + + SafeTemporaryKeychainHandle.TrackItem(certHandle); + SafeTemporaryKeychainHandle.TrackItem(identityHandle); + + if (ret == 1) + { + return certHandle; + } + + certHandle.Dispose(); + identityHandle.Dispose(); + + const int SeeOSStatus = 0; + const int ImportReturnedEmpty = -2; + const int ImportReturnedNull = -3; + + switch (ret) + { + case SeeOSStatus: + throw CreateExceptionForOSStatus(osStatus); + case ImportReturnedNull: + case ImportReturnedEmpty: + throw new CryptographicException(); + default: + Debug.Fail($"Unexpected return value {ret}"); + throw new CryptographicException(); + } + } + + internal static SafeCFArrayHandle X509ImportCollection( + ReadOnlySpan bytes, + X509ContentType contentType, + SafePasswordHandle importPassword, + SafeKeychainHandle keychain, + bool exportable) + { + SafeCreateHandle cfPassphrase = s_nullExportString; + bool releasePassword = false; + + int ret; + SafeCFArrayHandle collectionHandle; + int osStatus; + + try + { + if (!importPassword.IsInvalid) + { + importPassword.DangerousAddRef(ref releasePassword); + IntPtr passwordHandle = importPassword.DangerousGetHandle(); + + if (passwordHandle != IntPtr.Zero) + { + cfPassphrase = CoreFoundation.CFStringCreateWithCString(passwordHandle); + } + } + + ret = AppleCryptoNative_X509ImportCollection( + ref MemoryMarshal.GetReference(bytes), + bytes.Length, + contentType, + cfPassphrase, + keychain, + exportable ? 1 : 0, + out collectionHandle, + out osStatus); + + if (ret == 1) + { + return collectionHandle; + } + } + finally + { + if (releasePassword) + { + importPassword.DangerousRelease(); + } + + if (cfPassphrase != s_nullExportString) + { + cfPassphrase.Dispose(); + } + } + + collectionHandle.Dispose(); + + const int SeeOSStatus = 0; + const int ImportReturnedEmpty = -2; + const int ImportReturnedNull = -3; + + switch (ret) + { + case SeeOSStatus: + throw CreateExceptionForOSStatus(osStatus); + case ImportReturnedNull: + case ImportReturnedEmpty: + throw new CryptographicException(); + default: + Debug.Fail($"Unexpected return value {ret}"); + throw new CryptographicException(); + } + } + + internal static SafeSecIdentityHandle X509CopyWithPrivateKey( + SafeSecCertificateHandle certHandle, + SafeSecKeyRefHandle privateKeyHandle, + SafeKeychainHandle targetKeychain) + { + SafeSecIdentityHandle identityHandle; + int osStatus; + + int result = AppleCryptoNative_X509CopyWithPrivateKey( + certHandle, + privateKeyHandle, + targetKeychain, + out identityHandle, + out osStatus); + + if (result == 1) + { + Debug.Assert(!identityHandle.IsInvalid); + return identityHandle; + } + + identityHandle.Dispose(); + + if (result == 0) + { + throw CreateExceptionForOSStatus(osStatus); + } + + Debug.Fail($"AppleCryptoNative_X509CopyWithPrivateKey returned {result}"); + throw new CryptographicException(); + } + + internal static SafeSecIdentityHandle? X509MoveToKeychain( + SafeSecCertificateHandle cert, + SafeKeychainHandle targetKeychain, + SafeSecKeyRefHandle? privateKey) + { + SafeSecIdentityHandle identityHandle; + int osStatus; + + int result = AppleCryptoNative_X509MoveToKeychain( + cert, + targetKeychain, + privateKey ?? SafeSecKeyRefHandle.InvalidHandle, + out identityHandle, + out osStatus); + + if (result == 0) + { + identityHandle.Dispose(); + throw CreateExceptionForOSStatus(osStatus); + } + + if (result != 1) + { + Debug.Fail($"AppleCryptoNative_X509MoveToKeychain returned {result}"); + throw new CryptographicException(); + } + + if (privateKey?.IsInvalid == false) + { + // If a PFX has a mismatched association between a private key and the + // certificate public key then MoveToKeychain will write the NULL SecIdentityRef + // (after cleaning up the temporary key). + // + // When that happens, just treat the import as public-only. + if (!identityHandle.IsInvalid) + { + return identityHandle; + } + } + + // If the cert in the PFX had no key, but it was imported with PersistKeySet (imports into + // the default keychain) and a matching private key was already there, then an + // identityHandle could be found. But that's not desirable, since neither Windows or Linux would + // do that matching. + // + // So dispose the handle, no matter what. + identityHandle.Dispose(); + return null; + } + + private static byte[] X509Export(X509ContentType contentType, SafeCreateHandle cfPassphrase, IntPtr[] certHandles) + { + Debug.Assert(contentType == X509ContentType.Pkcs7 || contentType == X509ContentType.Pkcs12); + + using (SafeCreateHandle handlesArray = CoreFoundation.CFArrayCreate(certHandles, (UIntPtr)certHandles.Length)) + { + SafeCFDataHandle exportData; + int osStatus; + + int result = AppleCryptoNative_X509ExportData( + handlesArray, + contentType, + cfPassphrase, + out exportData, + out osStatus); + + using (exportData) + { + if (result != 1) + { + if (result == 0) + { + throw CreateExceptionForOSStatus(osStatus); + } + + Debug.Fail($"Unexpected result from AppleCryptoNative_X509ExportData: {result}"); + throw new CryptographicException(); + } + + Debug.Assert(!exportData.IsInvalid, "Successful export yielded no data"); + return CoreFoundation.CFGetData(exportData); + } + } + } + + internal static byte[] X509ExportPkcs7(IntPtr[] certHandles) + { + return X509Export(X509ContentType.Pkcs7, s_nullExportString, certHandles); + } + + internal static byte[] X509ExportPfx(IntPtr[] certHandles, SafePasswordHandle exportPassword) + { + SafeCreateHandle cfPassphrase = s_emptyExportString; + bool releasePassword = false; + + try + { + if (!exportPassword.IsInvalid) + { + exportPassword.DangerousAddRef(ref releasePassword); + IntPtr passwordHandle = exportPassword.DangerousGetHandle(); + + if (passwordHandle != IntPtr.Zero) + { + cfPassphrase = CoreFoundation.CFStringCreateWithCString(passwordHandle); + } + } + + return X509Export(X509ContentType.Pkcs12, cfPassphrase, certHandles); + } + finally + { + if (releasePassword) + { + exportPassword.DangerousRelease(); + } + + if (cfPassphrase != s_emptyExportString) + { + cfPassphrase.Dispose(); + } + } + } + + internal static SafeSecCertificateHandle X509GetCertFromIdentity(SafeSecIdentityHandle identity) + { + SafeSecCertificateHandle cert; + int osStatus = AppleCryptoNative_X509CopyCertFromIdentity(identity, out cert); + + SafeTemporaryKeychainHandle.TrackItem(cert); + + if (osStatus != 0) + { + cert.Dispose(); + throw CreateExceptionForOSStatus(osStatus); + } + + if (cert.IsInvalid) + { + cert.Dispose(); + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + + return cert; + } + + internal static bool X509DemuxAndRetainHandle( + IntPtr handle, + out SafeSecCertificateHandle certHandle, + out SafeSecIdentityHandle identityHandle) + { + int result = AppleCryptoNative_X509DemuxAndRetainHandle(handle, out certHandle, out identityHandle); + + SafeTemporaryKeychainHandle.TrackItem(certHandle); + SafeTemporaryKeychainHandle.TrackItem(identityHandle); + + switch (result) + { + case 1: + return true; + case 0: + return false; + default: + Debug.Fail($"AppleCryptoNative_X509DemuxAndRetainHandle returned {result}"); + throw new CryptographicException(); + } + } + } +} + +namespace System.Security.Cryptography.X509Certificates +{ + internal sealed class SafeSecIdentityHandle : SafeKeychainItemHandle + { + public SafeSecIdentityHandle() + { + } + } + + internal sealed class SafeSecCertificateHandle : SafeKeychainItemHandle + { + public SafeSecCertificateHandle() + { + } + } +} diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509Store.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509Store.cs deleted file mode 100644 index 80b8dcd7c34401..00000000000000 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509Store.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using System.Security.Cryptography.Apple; -using System.Security.Cryptography.X509Certificates; - -internal static partial class Interop -{ - internal static partial class AppleCrypto - { - [DllImport(Libraries.AppleCryptoNative)] - private static extern int AppleCryptoNative_X509StoreAddCertificate( - SafeKeychainItemHandle cert, - SafeKeychainHandle keychain, - out int pOSStatus); - - [DllImport(Libraries.AppleCryptoNative)] - private static extern int AppleCryptoNative_X509StoreRemoveCertificate( - SafeKeychainItemHandle cert, - SafeKeychainHandle keychain, - bool isReadOnlyMode, - out int pOSStatus); - - internal static void X509StoreAddCertificate(SafeKeychainItemHandle certOrIdentity, SafeKeychainHandle keychain) - { - int osStatus; - int ret = AppleCryptoNative_X509StoreAddCertificate(certOrIdentity, keychain, out osStatus); - - if (ret == 0) - { - throw CreateExceptionForOSStatus(osStatus); - } - - if (ret != 1) - { - Debug.Fail($"Unexpected result from AppleCryptoNative_X509StoreAddCertificate: {ret}"); - throw new CryptographicException(); - } - } - - internal static void X509StoreRemoveCertificate(SafeKeychainItemHandle certHandle, SafeKeychainHandle keychain, bool isReadOnlyMode) - { - int osStatus; - int ret = AppleCryptoNative_X509StoreRemoveCertificate(certHandle, keychain, isReadOnlyMode, out osStatus); - - if (ret == 0) - { - throw CreateExceptionForOSStatus(osStatus); - } - - const int SuccessOrNoMatch = 1; - const int UserTrustExists = 2; - const int AdminTrustExists = 3; - const int ReadOnlyDelete = 4; - - switch (ret) - { - case SuccessOrNoMatch: - break; - case UserTrustExists: - throw new CryptographicException(SR.Cryptography_X509Store_WouldModifyUserTrust); - case AdminTrustExists: - throw new CryptographicException(SR.Cryptography_X509Store_WouldModifyAdminTrust); - case ReadOnlyDelete: - throw new CryptographicException(SR.Cryptography_X509_StoreReadOnly); - default: - Debug.Fail($"Unexpected result from AppleCryptoNative_X509StoreRemoveCertificate: {ret}"); - throw new CryptographicException(); - } - } - } -} diff --git a/src/libraries/Common/src/System/Security/Cryptography/PemLabels.cs b/src/libraries/Common/src/System/Security/Cryptography/PemLabels.cs index ef4faf884f160c..9373bbb48349fa 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/PemLabels.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/PemLabels.cs @@ -12,5 +12,6 @@ internal static class PemLabels internal const string RsaPrivateKey = "RSA PRIVATE KEY"; internal const string EcPrivateKey = "EC PRIVATE KEY"; internal const string X509Certificate = "CERTIFICATE"; + internal const string Pkcs7Certificate = "PKCS7"; } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/PlatformSupport.cs b/src/libraries/Common/tests/System/Security/Cryptography/PlatformSupport.cs index b46fe55b0cafad..9d1bf1a06a73de 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/PlatformSupport.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/PlatformSupport.cs @@ -8,8 +8,12 @@ namespace Test.Cryptography { internal static class PlatformSupport { - // Platforms that support OpenSSL - all Unix except OSX and Android - internal const TestPlatforms OpenSSL = TestPlatforms.AnyUnix & ~(TestPlatforms.OSX | TestPlatforms.Android); + // Platforms that use Apple Cryptography + internal const TestPlatforms AppleCrypto = TestPlatforms.OSX | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst; + internal const TestPlatforms MobileAppleCrypto = TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst; + + // Platforms that support OpenSSL - all Unix except OSX/iOS/tvOS/MacCatalyst and Android + internal const TestPlatforms OpenSSL = TestPlatforms.AnyUnix & ~(AppleCrypto | TestPlatforms.Android); // Whether or not the current platform supports RC2 internal static readonly bool IsRC2Supported = !PlatformDetection.IsAndroid; diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 34c4bb86c37ded..6c0d4a1978999e 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -137,6 +137,8 @@ public static bool IsNonZeroLowerBoundArraySupported public static bool IsOpenSslSupported => IsLinux || IsFreeBSD || Isillumos || IsSolaris; + public static bool UsesAppleCrypto => IsOSX || IsMacCatalyst || IsiOS || IstvOS; + // Changed to `true` when linking public static bool IsBuiltWithAggressiveTrimming => false; diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/CMakeLists.txt b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/CMakeLists.txt index 22eea10958afb4..a9029ec6290686 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/CMakeLists.txt +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/CMakeLists.txt @@ -21,7 +21,13 @@ set(NATIVECRYPTO_SOURCES pal_x509chain.c ) -if (NOT CLR_CMAKE_TARGET_MACCATALYST AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS) +if (CLR_CMAKE_TARGET_MACCATALYST OR CLR_CMAKE_TARGET_IOS OR CLR_CMAKE_TARGET_TVOS) + set(NATIVECRYPTO_SOURCES + ${NATIVECRYPTO_SOURCES} + pal_keychain_ios.c + pal_x509_ios.c + ) +else() set(NATIVECRYPTO_SOURCES ${NATIVECRYPTO_SOURCES} pal_keychain_macos.c diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain_ios.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain_ios.c new file mode 100644 index 00000000000000..588154eb431e61 --- /dev/null +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain_ios.c @@ -0,0 +1,219 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_keychain_ios.h" +#include "pal_utilities.h" +#include "pal_x509.h" + +static int32_t EnumerateKeychain(CFStringRef matchType, CFArrayRef* pCertsOut) +{ + assert(pCertsOut != NULL); + assert(matchType != NULL); + + *pCertsOut = NULL; + + const void* keys[] = {kSecReturnRef, kSecMatchLimit, kSecClass}; + const void* values[] = {kCFBooleanTrue, kSecMatchLimitAll, matchType}; + CFDictionaryRef query = CFDictionaryCreate(kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(*keys), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + if (query == NULL) + { + return errSecAllocate; + } + + CFTypeRef result = NULL; + OSStatus status; + + status = SecItemCopyMatching(query, &result); + + CFRelease(query); + + if (status == noErr) + { + assert(result != NULL); + assert(CFGetTypeID(result) == CFArrayGetTypeID()); + CFRetain(result); + *pCertsOut = (CFArrayRef)result; + } + else if (status == errSecItemNotFound) + { + assert(result == NULL); + status = noErr; + } + else + { + if (result != NULL) + { + CFRelease(result); + } + } + + return status; +} + +int32_t AppleCryptoNative_SecKeychainEnumerateCerts(CFArrayRef* pCertsOut) +{ + return EnumerateKeychain(kSecClassCertificate, pCertsOut); +} + +int32_t AppleCryptoNative_SecKeychainEnumerateIdentities(CFArrayRef* pIdentitiesOut) +{ + return EnumerateKeychain(kSecClassIdentity, pIdentitiesOut); +} + +int32_t AppleCryptoNative_X509StoreAddCertificate(CFTypeRef certOrIdentity) +{ + OSStatus status; + + assert(certOrIdentity != NULL); + + const void* keys[] = {kSecValueRef}; + const void* values[] = {certOrIdentity}; + CFDictionaryRef query = CFDictionaryCreate(kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(*keys), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + if (query == NULL) + { + return errSecAllocate; + } + + status = SecItemAdd(query, NULL); + + return status == errSecDuplicateItem ? noErr : status; +} + +int32_t AppleCryptoNative_X509StoreRemoveCertificate(CFTypeRef certOrIdentity, uint8_t isReadOnlyMode) +{ + OSStatus status; + CFTypeRef result = NULL; + + assert(certOrIdentity != NULL); + + const void* keys[] = {kSecValueRef}; + const void* values[] = {certOrIdentity}; + CFDictionaryRef query = CFDictionaryCreate(kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(*keys), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + if (query == NULL) + { + return errSecAllocate; + } + + if (!isReadOnlyMode) + { + CFTypeID inputType = CFGetTypeID(certOrIdentity); + + if (inputType == SecCertificateGetTypeID()) + { + // If we got a certificate as input we have to try to delete private key + // as well. There's one-to-many relationship between keys and certificates + // so we save the public key fingerprint first. Then we delete the + // certificate and look whether there are any remaining ceritificates + // referencing the same key. If none are found then we try to delete + // the key. + + SecCertificateRef cert = (SecCertificateRef)CONST_CAST(void*, certOrIdentity); + SecKeyRef publicKey = NULL; + CFTypeRef publicKeyLabel = NULL; + int32_t dummyStatus; + + if (AppleCryptoNative_X509GetPublicKey(cert, &publicKey, &dummyStatus)) + { + CFDictionaryRef attrs = SecKeyCopyAttributes(publicKey); + publicKeyLabel = CFRetain(CFDictionaryGetValue(attrs, kSecAttrApplicationLabel)); + CFRelease(attrs); + CFRelease(publicKey); + } + + status = SecItemDelete(query); + + CFRelease(query); + + if (status == noErr && publicKeyLabel != NULL) + { + OSStatus keyStatus; + + const void* keys[] = {kSecClass, kSecAttrPublicKeyHash}; + const void* values[] = {kSecClassCertificate, publicKeyLabel}; + query = CFDictionaryCreate(kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(*keys), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + if (query == NULL) + { + CFRelease(publicKeyLabel); + return errSecAllocate; + } + + result = NULL; + keyStatus = SecItemCopyMatching(query, &result); + + CFRelease(query); + + if (result != NULL) + { + CFRelease(result); + } + + if (keyStatus == errSecItemNotFound) + { + const void* keys[] = {kSecClass, kSecAttrApplicationLabel}; + const void* values[] = {kSecClassKey, publicKeyLabel}; + query = CFDictionaryCreate(kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(*keys), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + if (query == NULL) + { + CFRelease(publicKeyLabel); + return errSecAllocate; + } + + SecItemDelete(query); + + CFRelease(query); + } + + CFRelease(publicKeyLabel); + } + } + else + { + status = SecItemDelete(query); + + CFRelease(query); + } + } + else + { + status = SecItemCopyMatching(query, &result); + + CFRelease(query); + + if (result != NULL) + { + CFRelease(result); + } + } + + return status; +} diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain_ios.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain_ios.h new file mode 100644 index 00000000000000..a788853fbfe043 --- /dev/null +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain_ios.h @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include "pal_compiler.h" +#include "pal_types.h" + +#include + +/* +Enumerate the certificate objects within the given keychain. + +Returns the last OSStatus value (noErr on success). + +Output: +pCertsOut: When the return value is not noErr, NULL. Otherwise NULL on "no certs found", or a CFArrayRef for the matches +(including a single match). +*/ +PALEXPORT int32_t AppleCryptoNative_SecKeychainEnumerateCerts(CFArrayRef* pCertsOut); + +/* +Enumerate the certificate objects within the given keychain. + +Returns the last OSStatus value (noErr on success). + +Note that any identity will also necessarily be returned as a certificate with no private key by +SecKeychainEnumerateCerts. De-duplication of values is the responsibility of the caller. + +Output: +pCertsOut: When the return value is not noErr, NULL. Otherwise NULL on "no certs found", or a CFArrayRef for the matches +(including a single match). +*/ +PALEXPORT int32_t AppleCryptoNative_SecKeychainEnumerateIdentities(CFArrayRef* pIdentitiesOut); + +/* +Add a certificate from the specified keychain. + +Returns the last OSStatus value (noErr on success). +*/ +PALEXPORT int32_t AppleCryptoNative_X509StoreAddCertificate(CFTypeRef certOrIdentity); + +/* +Remove a certificate from the specified keychain. + +Returns the last OSStatus value (noErr on success). +*/ +PALEXPORT int32_t AppleCryptoNative_X509StoreRemoveCertificate(CFTypeRef certOrIdentity, uint8_t isReadOnlyMode); diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.c index b044a1a7eca9e6..7d3e61f4b5d576 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.c @@ -6,9 +6,11 @@ #include #include +#if !defined(TARGET_MACCATALYST) static pthread_once_t once = PTHREAD_ONCE_INIT; static SecKeyRef (*secCertificateCopyKey)(SecCertificateRef); static OSStatus (*secCertificateCopyPublicKey)(SecCertificateRef, SecKeyRef*); +#endif int32_t AppleCryptoNative_X509DemuxAndRetainHandle(CFTypeRef handle, SecCertificateRef* pCertOut, SecIdentityRef* pIdentityOut) @@ -40,11 +42,22 @@ AppleCryptoNative_X509DemuxAndRetainHandle(CFTypeRef handle, SecCertificateRef* return 1; } +#if !defined(TARGET_MACCATALYST) static void InitCertificateCopy() { +#if defined(TARGET_IOS) || defined(TARGET_TVOS) + // SecCertificateCopyPublicKey on iOS/tvOS has same function prototype as SecCertificateCopyKey + secCertificateCopyKey = (SecKeyRef (*)(SecCertificateRef))dlsym(RTLD_DEFAULT, "SecCertificateCopyKey"); + if (secCertificateCopyKey == NULL) + { + secCertificateCopyKey = (SecKeyRef (*)(SecCertificateRef))dlsym(RTLD_DEFAULT, "SecCertificateCopyPublicKey"); + } +#else secCertificateCopyKey = (SecKeyRef (*)(SecCertificateRef))dlsym(RTLD_DEFAULT, "SecCertificateCopyKey"); secCertificateCopyPublicKey = (OSStatus (*)(SecCertificateRef, SecKeyRef*))dlsym(RTLD_DEFAULT, "SecCertificateCopyPublicKey"); +#endif } +#endif int32_t AppleCryptoNative_X509GetPublicKey(SecCertificateRef cert, SecKeyRef* pPublicKeyOut, int32_t* pOSStatusOut) @@ -57,7 +70,8 @@ AppleCryptoNative_X509GetPublicKey(SecCertificateRef cert, SecKeyRef* pPublicKey if (cert == NULL || pPublicKeyOut == NULL || pOSStatusOut == NULL) return kErrorUnknownState; - pthread_once (&once, InitCertificateCopy); +#if !defined(TARGET_MACCATALYST) + pthread_once(&once, InitCertificateCopy); // SecCertificateCopyPublicKey was deprecated in 10.14, so use SecCertificateCopyKey on the systems that have it (10.14+), // and SecCertificateCopyPublicKey on the systems that don’t. if (secCertificateCopyKey != NULL) @@ -73,6 +87,10 @@ AppleCryptoNative_X509GetPublicKey(SecCertificateRef cert, SecKeyRef* pPublicKey return kErrorBadInput; } return (*pOSStatusOut == noErr); +#else + *pPublicKeyOut = SecCertificateCopyKey(cert); + return 1; +#endif } PAL_X509ContentType AppleCryptoNative_X509GetContentType(uint8_t* pbData, int32_t cbData) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509_ios.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509_ios.c new file mode 100644 index 00000000000000..8ffa1751e1325b --- /dev/null +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509_ios.c @@ -0,0 +1,182 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_x509_ios.h" +#include "pal_utilities.h" +#include "pal_x509.h" +#include +#include + +int32_t AppleCryptoNative_X509ImportCertificate(uint8_t* pbData, + int32_t cbData, + PAL_X509ContentType contentType, + CFStringRef cfPfxPassphrase, + SecCertificateRef* pCertOut, + SecIdentityRef* pIdentityOut) +{ + OSStatus status; + + assert(pCertOut != NULL); + assert(pIdentityOut != NULL); + assert(pbData != NULL); + assert(cbData >= 0); + + *pCertOut = NULL; + *pIdentityOut = NULL; + + if (contentType != PAL_Certificate && contentType != PAL_Pkcs12) + { + return errSecUnknownFormat; + } + + CFDataRef cfData = CFDataCreateWithBytesNoCopy(NULL, pbData, cbData, kCFAllocatorNull); + + if (cfData == NULL) + { + return errSecAllocate; + } + + if (contentType == PAL_Certificate) + { + *pCertOut = SecCertificateCreateWithData(NULL, cfData); + CFRelease(cfData); + return *pCertOut == NULL ? errSecUnknownFormat : noErr; + } + else // PAL_Pkcs12 + { + const void* keys[] = {kSecImportExportPassphrase}; + const void* values[] = {cfPfxPassphrase}; + CFDictionaryRef attrs = CFDictionaryCreate(kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(*keys), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + if (attrs == NULL) + { + CFRelease(cfData); + return errSecAllocate; + } + + CFArrayRef p12Items = NULL; + status = SecPKCS12Import(cfData, attrs, &p12Items); + + CFRelease(cfData); + CFRelease(attrs); + + if (status == noErr) + { + if (CFArrayGetCount(p12Items) > 0) + { + CFDictionaryRef item_dict = CFArrayGetValueAtIndex(p12Items, 0); + *pIdentityOut = (SecIdentityRef)CFRetain(CFDictionaryGetValue(item_dict, kSecImportItemIdentity)); + } + CFRelease(p12Items); + } + + return status; + } +} + +int32_t AppleCryptoNative_X509ImportCollection(uint8_t* pbData, + int32_t cbData, + PAL_X509ContentType contentType, + CFStringRef cfPfxPassphrase, + CFArrayRef* pCollectionOut) +{ + OSStatus status; + CFMutableArrayRef outItems; + + assert(pCollectionOut != NULL); + assert(pbData != NULL); + assert(cbData >= 0); + + *pCollectionOut = NULL; + + if (contentType != PAL_Certificate && contentType != PAL_Pkcs12) + { + return errSecUnknownFormat; + } + + CFDataRef cfData = CFDataCreateWithBytesNoCopy(NULL, pbData, cbData, kCFAllocatorNull); + + if (cfData == NULL) + { + return errSecAllocate; + } + + if (contentType == PAL_Certificate) + { + SecCertificateRef certificate = SecCertificateCreateWithData(NULL, cfData); + + CFRelease(cfData); + + if (certificate != NULL) + { + outItems = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks); + + if (outItems == NULL) + { + CFRelease(certificate); + return errSecAllocate; + } + + CFArrayAppendValue(outItems, certificate); + *pCollectionOut = outItems; + return noErr; + } + else + { + return errSecUnknownFormat; + } + } + else // PAL_Pkcs12 + { + const void* keys[] = {kSecImportExportPassphrase}; + const void* values[] = {cfPfxPassphrase}; + CFDictionaryRef attrs = CFDictionaryCreate(kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(*keys), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + if (attrs == NULL) + { + CFRelease(cfData); + return errSecAllocate; + } + + CFArrayRef p12Items = NULL; + status = SecPKCS12Import(cfData, attrs, &p12Items); + + CFRelease(cfData); + CFRelease(attrs); + + if (status == noErr) + { + outItems = CFArrayCreateMutable(NULL, CFArrayGetCount(p12Items), &kCFTypeArrayCallBacks); + + if (outItems == NULL) + { + CFRelease(p12Items); + return errSecAllocate; + } + + for (int i = 0; i < CFArrayGetCount(p12Items); i++) + { + CFDictionaryRef item_dict = CFArrayGetValueAtIndex(p12Items, i); + SecIdentityRef identity = + (SecIdentityRef)CFRetain(CFDictionaryGetValue(item_dict, kSecImportItemIdentity)); + assert(identity != NULL); + CFArrayAppendValue(outItems, identity); + } + + CFRelease(p12Items); + *pCollectionOut = outItems; + } + + return status; + } +} \ No newline at end of file diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509_ios.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509_ios.h new file mode 100644 index 00000000000000..f2822ddb8278c0 --- /dev/null +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509_ios.h @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include "pal_compiler.h" +#include "pal_digest.h" +#include "pal_seckey.h" +#include + +#include + +/* +Read cbData bytes of data from pbData and interpret it to a single certificate (or identity). + +For a single X.509 certificate, that certificate is emitted. +For a PKCS#7 blob the signing certificate is returned. +For a PKCS#12 blob (PFX) the first public+private pair found is returned, or the first certificate. + +If cfPfxPassphrase represents the NULL (but not empty) passphrase and a PFX import gets a password +error then the empty passphrase is automatically attempted. + +Returns the last OSStatus value. + +Output: +pCertOut: If the best matched value was a certificate, receives the SecCertificateRef, otherwise receives NULL +pIdentityOut: If the best matched value was an identity, receives the SecIdentityRef, otherwise receives NULL +*/ +PALEXPORT int32_t AppleCryptoNative_X509ImportCertificate(uint8_t* pbData, + int32_t cbData, + PAL_X509ContentType contentType, + CFStringRef cfPfxPassphrase, + SecCertificateRef* pCertOut, + SecIdentityRef* pIdentityOut); + +/* +Read cbData bytes of data from pbData and interpret it to a collection of certificates (or identities). + +If cfPfxPassphrase represents the NULL (but not empty) passphrase and a PFX import gets a password +error then the empty passphrase is automatically attempted. + +Returns the last OSStatus value. + +Output: +pCollectionOut: Receives an array which contains SecCertificateRef, SecIdentityRef, and possibly other values which were +read out of the provided blob +*/ +PALEXPORT int32_t AppleCryptoNative_X509ImportCollection(uint8_t* pbData, + int32_t cbData, + PAL_X509ContentType contentType, + CFStringRef cfPfxPassphrase, + CFArrayRef* pCollectionOut); diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.ImportExport.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.ImportExport.cs new file mode 100644 index 00000000000000..f6b19f9bb03946 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.ImportExport.cs @@ -0,0 +1,161 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Formats.Asn1; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography.Pal +{ + internal sealed partial class AppleCertificatePal : ICertificatePal + { + private SafeKeychainHandle? _tempKeychain; + + public static ICertificatePal FromBlob( + ReadOnlySpan rawData, + SafePasswordHandle password, + X509KeyStorageFlags keyStorageFlags) + { + Debug.Assert(password != null); + + X509ContentType contentType = X509Certificate2.GetCertContentType(rawData); + + if (contentType == X509ContentType.Pkcs7) + { + // In single mode for a PKCS#7 signed or signed-and-enveloped file we're supposed to return + // the certificate which signed the PKCS#7 file. + // + // X509Certificate2Collection::Export(X509ContentType.Pkcs7) claims to be a signed PKCS#7, + // but doesn't emit a signature block. So this is hard to test. + // + // TODO(2910): Figure out how to extract the signing certificate, when it's present. + throw new CryptographicException(SR.Cryptography_X509_PKCS7_NoSigner); + } + + if (contentType == X509ContentType.Pkcs12) + { + if ((keyStorageFlags & X509KeyStorageFlags.EphemeralKeySet) == X509KeyStorageFlags.EphemeralKeySet) + { + throw new PlatformNotSupportedException(SR.Cryptography_X509_NoEphemeralPfx); + } + + bool exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable; + + bool persist = + (keyStorageFlags & X509KeyStorageFlags.PersistKeySet) == X509KeyStorageFlags.PersistKeySet; + + SafeKeychainHandle keychain = persist + ? Interop.AppleCrypto.SecKeychainCopyDefault() + : Interop.AppleCrypto.CreateTemporaryKeychain(); + + 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; + } + } + + SafeSecIdentityHandle identityHandle; + SafeSecCertificateHandle certHandle = Interop.AppleCrypto.X509ImportCertificate( + rawData, + contentType, + SafePasswordHandle.InvalidHandle, + SafeTemporaryKeychainHandle.InvalidHandle, + exportable: true, + out identityHandle); + + if (identityHandle.IsInvalid) + { + identityHandle.Dispose(); + return new AppleCertificatePal(certHandle); + } + + Debug.Fail("Non-PKCS12 import produced an identity handle"); + + identityHandle.Dispose(); + certHandle.Dispose(); + throw new CryptographicException(); + } + + public void DisposeTempKeychain() + { + SafeKeychainHandle? tempKeychain = Interlocked.Exchange(ref _tempKeychain, null); + if (tempKeychain != null) + { + tempKeychain.Dispose(); + } + } + + internal unsafe byte[] ExportPkcs8(ReadOnlySpan password) + { + Debug.Assert(_identityHandle != null); + + using (SafeSecKeyRefHandle key = Interop.AppleCrypto.X509GetPrivateKeyFromIdentity(_identityHandle)) + { + return ExportPkcs8(key, password); + } + } + + internal static unsafe byte[] ExportPkcs8(SafeSecKeyRefHandle key, ReadOnlySpan password) + { + using (SafeCFDataHandle data = Interop.AppleCrypto.SecKeyExportData(key, exportPrivate: true, password)) + { + ReadOnlySpan systemExport = Interop.CoreFoundation.CFDataDangerousGetSpan(data); + + fixed (byte* ptr = systemExport) + { + using (PointerMemoryManager manager = new PointerMemoryManager(ptr, systemExport.Length)) + { + // Apple's PKCS8 export exports using PBES2, which Win7, Win8.1, and Apple all fail to + // understand in their PKCS12 readers, so re-encrypt using the Win7 PKCS12-PBE parameters. + // + // Since Apple only reliably exports keys with encrypted PKCS#8 there's not a + // "so export it plaintext and only encrypt it once" option. + AsnWriter writer = KeyFormatHelper.ReencryptPkcs8( + password, + manager.Memory, + password, + UnixExportProvider.s_windowsPbe); + + return writer.Encode(); + } + } + } + } + + internal AppleCertificatePal? MoveToKeychain(SafeKeychainHandle keychain, SafeSecKeyRefHandle? privateKey) + { + SafeSecIdentityHandle? identity = Interop.AppleCrypto.X509MoveToKeychain( + _certHandle, + keychain, + privateKey); + + if (identity != null) + { + return new AppleCertificatePal(identity); + } + + return null; + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Keys.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Keys.cs new file mode 100644 index 00000000000000..4231c82f0ac958 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Keys.cs @@ -0,0 +1,177 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Formats.Asn1; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography.Pal +{ + internal sealed partial class AppleCertificatePal : ICertificatePal + { + public DSA? GetDSAPrivateKey() + { + if (_identityHandle == null) + return null; + + Debug.Assert(!_identityHandle.IsInvalid); + SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.X509GetPublicKey(_certHandle); + SafeSecKeyRefHandle privateKey = Interop.AppleCrypto.X509GetPrivateKeyFromIdentity(_identityHandle); + + if (publicKey.IsInvalid) + { + // SecCertificateCopyKey returns null for DSA, so fall back to manually building it. + publicKey = Interop.AppleCrypto.ImportEphemeralKey(_certData.SubjectPublicKeyInfo, false); + } + + return new DSAImplementation.DSASecurityTransforms(publicKey, privateKey); + } + + public ICertificatePal CopyWithPrivateKey(DSA privateKey) + { + var typedKey = privateKey as DSAImplementation.DSASecurityTransforms; + + if (typedKey != null) + { + return CopyWithPrivateKey(typedKey.GetKeys().PrivateKey); + } + + DSAParameters dsaParameters = privateKey.ExportParameters(true); + + using (PinAndClear.Track(dsaParameters.X!)) + using (typedKey = new DSAImplementation.DSASecurityTransforms()) + { + typedKey.ImportParameters(dsaParameters); + return CopyWithPrivateKey(typedKey.GetKeys().PrivateKey); + } + } + + public ICertificatePal CopyWithPrivateKey(ECDsa privateKey) + { + var typedKey = privateKey as ECDsaImplementation.ECDsaSecurityTransforms; + + if (typedKey != null) + { + return CopyWithPrivateKey(typedKey.GetKeys().PrivateKey); + } + + byte[] ecPrivateKey = privateKey.ExportECPrivateKey(); + + using (PinAndClear.Track(ecPrivateKey)) + using (SafeSecKeyRefHandle privateSecKey = Interop.AppleCrypto.ImportEphemeralKey(ecPrivateKey, true)) + { + return CopyWithPrivateKey(privateSecKey); + } + } + + public ICertificatePal CopyWithPrivateKey(ECDiffieHellman privateKey) + { + var typedKey = privateKey as ECDiffieHellmanImplementation.ECDiffieHellmanSecurityTransforms; + + if (typedKey != null) + { + return CopyWithPrivateKey(typedKey.GetKeys().PrivateKey); + } + + byte[] ecPrivateKey = privateKey.ExportECPrivateKey(); + + using (PinAndClear.Track(ecPrivateKey)) + using (SafeSecKeyRefHandle privateSecKey = Interop.AppleCrypto.ImportEphemeralKey(ecPrivateKey, true)) + { + return CopyWithPrivateKey(privateSecKey); + } + } + + public ICertificatePal CopyWithPrivateKey(RSA privateKey) + { + var typedKey = privateKey as RSAImplementation.RSASecurityTransforms; + + if (typedKey != null) + { + return CopyWithPrivateKey(typedKey.GetKeys().PrivateKey); + } + + byte[] rsaPrivateKey = privateKey.ExportRSAPrivateKey(); + + using (PinAndClear.Track(rsaPrivateKey)) + using (SafeSecKeyRefHandle privateSecKey = Interop.AppleCrypto.ImportEphemeralKey(rsaPrivateKey, true)) + { + return CopyWithPrivateKey(privateSecKey); + } + } + + private ICertificatePal CopyWithPrivateKey(SafeSecKeyRefHandle? privateKey) + { + if (privateKey == null) + { + // Both Windows and Linux/OpenSSL are unaware if they bound a public or private key. + // Here, we do know. So throw if we can't do what they asked. + throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); + } + + SafeKeychainHandle keychain = Interop.AppleCrypto.SecKeychainItemCopyKeychain(privateKey); + + // If we're using a key already in a keychain don't add the certificate to that keychain here, + // do it in the temporary add/remove in the shim. + SafeKeychainHandle cloneKeychain = SafeTemporaryKeychainHandle.InvalidHandle; + + if (keychain.IsInvalid) + { + keychain = Interop.AppleCrypto.CreateTemporaryKeychain(); + cloneKeychain = keychain; + } + + // Because SecIdentityRef only has private constructors we need to have the cert and the key + // in the same keychain. That almost certainly means we're going to need to add this cert to a + // keychain, and when a cert that isn't part of a keychain gets added to a keychain then the + // interior pointer of "what keychain did I come from?" used by SecKeychainItemCopyKeychain gets + // set. That makes this function have side effects, which is not desired. + // + // It also makes reference tracking on temporary keychains broken, since the cert can + // DangerousRelease a handle it didn't DangerousAddRef on. And so CopyWithPrivateKey makes + // a temporary keychain, then deletes it before anyone has a chance to (e.g.) export the + // new identity as a PKCS#12 blob. + // + // Solution: Clone the cert, like we do in Windows. + SafeSecCertificateHandle tempHandle; + + { + byte[] export = RawData; + const bool exportable = false; + SafeSecIdentityHandle identityHandle; + tempHandle = Interop.AppleCrypto.X509ImportCertificate( + export, + X509ContentType.Cert, + SafePasswordHandle.InvalidHandle, + cloneKeychain, + exportable, + out identityHandle); + + Debug.Assert(identityHandle.IsInvalid, "identityHandle should be IsInvalid"); + identityHandle.Dispose(); + + Debug.Assert(!tempHandle.IsInvalid, "tempHandle should not be IsInvalid"); + } + + using (keychain) + using (tempHandle) + { + SafeSecIdentityHandle identityHandle = Interop.AppleCrypto.X509CopyWithPrivateKey( + tempHandle, + privateKey, + keychain); + + AppleCertificatePal newPal = new AppleCertificatePal(identityHandle); + return newPal; + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Pkcs12.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Pkcs12.cs index cd4a1e8526f14f..4febd7080f082c 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Pkcs12.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Pkcs12.cs @@ -108,40 +108,5 @@ protected override byte[] ExportPkcs8(ICertificatePalCore certificatePal, ReadOn return AppleCertificatePal.ExportPkcs8(_privateKey, password); } } - - private sealed class TempExportPal : ICertificatePalCore - { - private readonly ICertificatePal _realPal; - - internal TempExportPal(AppleCertificatePal realPal) - { - _realPal = realPal; - } - - public bool HasPrivateKey => true; - - public void Dispose() - { - // No-op. - } - - // Forwarders to make the interface compliant. - public IntPtr Handle => _realPal.Handle; - public string Issuer => _realPal.Issuer; - public string Subject => _realPal.Subject; - public string LegacyIssuer => _realPal.LegacyIssuer; - public string LegacySubject => _realPal.LegacySubject; - public byte[] Thumbprint => _realPal.Thumbprint; - public string KeyAlgorithm => _realPal.KeyAlgorithm; - public byte[] KeyAlgorithmParameters => _realPal.KeyAlgorithmParameters; - public byte[] PublicKeyValue => _realPal.PublicKeyValue; - public byte[] SerialNumber => _realPal.SerialNumber; - public string SignatureAlgorithm => _realPal.SignatureAlgorithm; - public DateTime NotAfter => _realPal.NotAfter; - public DateTime NotBefore => _realPal.NotBefore; - public byte[] RawData => _realPal.RawData; - public byte[] Export(X509ContentType contentType, SafePasswordHandle password) => - _realPal.Export(contentType, password); - } } } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.TempExportPal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.TempExportPal.cs new file mode 100644 index 00000000000000..58553f50307b02 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.TempExportPal.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography.Pal +{ + internal sealed partial class AppleCertificatePal : ICertificatePal + { + private sealed class TempExportPal : ICertificatePalCore + { + private readonly ICertificatePal _realPal; + + internal TempExportPal(AppleCertificatePal realPal) + { + _realPal = realPal; + } + + public bool HasPrivateKey => true; + + public void Dispose() + { + // No-op. + } + + // Forwarders to make the interface compliant. + public IntPtr Handle => _realPal.Handle; + public string Issuer => _realPal.Issuer; + public string Subject => _realPal.Subject; + public string LegacyIssuer => _realPal.LegacyIssuer; + public string LegacySubject => _realPal.LegacySubject; + public byte[] Thumbprint => _realPal.Thumbprint; + public string KeyAlgorithm => _realPal.KeyAlgorithm; + public byte[] KeyAlgorithmParameters => _realPal.KeyAlgorithmParameters; + public byte[] PublicKeyValue => _realPal.PublicKeyValue; + public byte[] SerialNumber => _realPal.SerialNumber; + public string SignatureAlgorithm => _realPal.SignatureAlgorithm; + public DateTime NotAfter => _realPal.NotAfter; + public DateTime NotBefore => _realPal.NotBefore; + public byte[] RawData => _realPal.RawData; + public byte[] Export(X509ContentType contentType, SafePasswordHandle password) => + _realPal.Export(contentType, password); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs index 1a69f66df5d146..cb949b8a93f7ad 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs @@ -21,7 +21,6 @@ internal sealed partial class AppleCertificatePal : ICertificatePal private SafeSecCertificateHandle _certHandle; private CertificateData _certData; private bool _readCertData; - private SafeKeychainHandle? _tempKeychain; public static ICertificatePal? FromHandle(IntPtr handle) { @@ -72,84 +71,6 @@ internal sealed partial class AppleCertificatePal : ICertificatePal return pal; } - public static ICertificatePal FromBlob( - ReadOnlySpan rawData, - SafePasswordHandle password, - X509KeyStorageFlags keyStorageFlags) - { - Debug.Assert(password != null); - - X509ContentType contentType = X509Certificate2.GetCertContentType(rawData); - - if (contentType == X509ContentType.Pkcs7) - { - // In single mode for a PKCS#7 signed or signed-and-enveloped file we're supposed to return - // the certificate which signed the PKCS#7 file. - // - // X509Certificate2Collection::Export(X509ContentType.Pkcs7) claims to be a signed PKCS#7, - // but doesn't emit a signature block. So this is hard to test. - // - // TODO(2910): Figure out how to extract the signing certificate, when it's present. - throw new CryptographicException(SR.Cryptography_X509_PKCS7_NoSigner); - } - - if (contentType == X509ContentType.Pkcs12) - { - if ((keyStorageFlags & X509KeyStorageFlags.EphemeralKeySet) == X509KeyStorageFlags.EphemeralKeySet) - { - throw new PlatformNotSupportedException(SR.Cryptography_X509_NoEphemeralPfx); - } - - bool exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable; - - bool persist = - (keyStorageFlags & X509KeyStorageFlags.PersistKeySet) == X509KeyStorageFlags.PersistKeySet; - - SafeKeychainHandle keychain = persist - ? Interop.AppleCrypto.SecKeychainCopyDefault() - : Interop.AppleCrypto.CreateTemporaryKeychain(); - - 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; - } - } - - SafeSecIdentityHandle identityHandle; - SafeSecCertificateHandle certHandle = Interop.AppleCrypto.X509ImportCertificate( - rawData, - contentType, - SafePasswordHandle.InvalidHandle, - SafeTemporaryKeychainHandle.InvalidHandle, - exportable: true, - out identityHandle); - - if (identityHandle.IsInvalid) - { - identityHandle.Dispose(); - return new AppleCertificatePal(certHandle); - } - - Debug.Fail("Non-PKCS12 import produced an identity handle"); - - identityHandle.Dispose(); - certHandle.Dispose(); - throw new CryptographicException(); - } - public static ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) { Debug.Assert(password != null); @@ -181,11 +102,7 @@ public void Dispose() _certHandle = null!; _identityHandle = null; - SafeKeychainHandle? tempKeychain = Interlocked.Exchange(ref _tempKeychain, null); - if (tempKeychain != null) - { - tempKeychain.Dispose(); - } + DisposeTempKeychain(); } internal SafeSecCertificateHandle CertificateHandle => _certHandle; @@ -408,43 +325,6 @@ public byte[] SubjectPublicKeyInfo } } - internal unsafe byte[] ExportPkcs8(ReadOnlySpan password) - { - Debug.Assert(_identityHandle != null); - - using (SafeSecKeyRefHandle key = Interop.AppleCrypto.X509GetPrivateKeyFromIdentity(_identityHandle)) - { - return ExportPkcs8(key, password); - } - } - - internal static unsafe byte[] ExportPkcs8(SafeSecKeyRefHandle key, ReadOnlySpan password) - { - using (SafeCFDataHandle data = Interop.AppleCrypto.SecKeyExportData(key, exportPrivate: true, password)) - { - ReadOnlySpan systemExport = Interop.CoreFoundation.CFDataDangerousGetSpan(data); - - fixed (byte* ptr = systemExport) - { - using (PointerMemoryManager manager = new PointerMemoryManager(ptr, systemExport.Length)) - { - // Apple's PKCS8 export exports using PBES2, which Win7, Win8.1, and Apple all fail to - // understand in their PKCS12 readers, so re-encrypt using the Win7 PKCS12-PBE parameters. - // - // Since Apple only reliably exports keys with encrypted PKCS#8 there's not a - // "so export it plaintext and only encrypt it once" option. - AsnWriter writer = KeyFormatHelper.ReencryptPkcs8( - password, - manager.Memory, - password, - UnixExportProvider.s_windowsPbe); - - return writer.Encode(); - } - } - } - } - public RSA? GetRSAPrivateKey() { if (_identityHandle == null) @@ -458,24 +338,6 @@ internal static unsafe byte[] ExportPkcs8(SafeSecKeyRefHandle key, ReadOnlySpan< return new RSAImplementation.RSASecurityTransforms(publicKey, privateKey); } - public DSA? GetDSAPrivateKey() - { - if (_identityHandle == null) - return null; - - Debug.Assert(!_identityHandle.IsInvalid); - SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.X509GetPublicKey(_certHandle); - SafeSecKeyRefHandle privateKey = Interop.AppleCrypto.X509GetPrivateKeyFromIdentity(_identityHandle); - - if (publicKey.IsInvalid) - { - // SecCertificateCopyKey returns null for DSA, so fall back to manually building it. - publicKey = Interop.AppleCrypto.ImportEphemeralKey(_certData.SubjectPublicKeyInfo, false); - } - - return new DSAImplementation.DSASecurityTransforms(publicKey, privateKey); - } - public ECDsa? GetECDsaPrivateKey() { if (_identityHandle == null) @@ -502,160 +364,6 @@ internal static unsafe byte[] ExportPkcs8(SafeSecKeyRefHandle key, ReadOnlySpan< return new ECDiffieHellmanImplementation.ECDiffieHellmanSecurityTransforms(publicKey, privateKey); } - public ICertificatePal CopyWithPrivateKey(DSA privateKey) - { - var typedKey = privateKey as DSAImplementation.DSASecurityTransforms; - - if (typedKey != null) - { - return CopyWithPrivateKey(typedKey.GetKeys().PrivateKey); - } - - DSAParameters dsaParameters = privateKey.ExportParameters(true); - - using (PinAndClear.Track(dsaParameters.X!)) - using (typedKey = new DSAImplementation.DSASecurityTransforms()) - { - typedKey.ImportParameters(dsaParameters); - return CopyWithPrivateKey(typedKey.GetKeys().PrivateKey); - } - } - - public ICertificatePal CopyWithPrivateKey(ECDsa privateKey) - { - var typedKey = privateKey as ECDsaImplementation.ECDsaSecurityTransforms; - - if (typedKey != null) - { - return CopyWithPrivateKey(typedKey.GetKeys().PrivateKey); - } - - byte[] ecPrivateKey = privateKey.ExportECPrivateKey(); - - using (PinAndClear.Track(ecPrivateKey)) - using (SafeSecKeyRefHandle privateSecKey = Interop.AppleCrypto.ImportEphemeralKey(ecPrivateKey, true)) - { - return CopyWithPrivateKey(privateSecKey); - } - } - - public ICertificatePal CopyWithPrivateKey(ECDiffieHellman privateKey) - { - var typedKey = privateKey as ECDiffieHellmanImplementation.ECDiffieHellmanSecurityTransforms; - - if (typedKey != null) - { - return CopyWithPrivateKey(typedKey.GetKeys().PrivateKey); - } - - byte[] ecPrivateKey = privateKey.ExportECPrivateKey(); - - using (PinAndClear.Track(ecPrivateKey)) - using (SafeSecKeyRefHandle privateSecKey = Interop.AppleCrypto.ImportEphemeralKey(ecPrivateKey, true)) - { - return CopyWithPrivateKey(privateSecKey); - } - } - - public ICertificatePal CopyWithPrivateKey(RSA privateKey) - { - var typedKey = privateKey as RSAImplementation.RSASecurityTransforms; - - if (typedKey != null) - { - return CopyWithPrivateKey(typedKey.GetKeys().PrivateKey); - } - - byte[] rsaPrivateKey = privateKey.ExportRSAPrivateKey(); - - using (PinAndClear.Track(rsaPrivateKey)) - using (SafeSecKeyRefHandle privateSecKey = Interop.AppleCrypto.ImportEphemeralKey(rsaPrivateKey, true)) - { - return CopyWithPrivateKey(privateSecKey); - } - } - - internal AppleCertificatePal? MoveToKeychain(SafeKeychainHandle keychain, SafeSecKeyRefHandle? privateKey) - { - SafeSecIdentityHandle? identity = Interop.AppleCrypto.X509MoveToKeychain( - _certHandle, - keychain, - privateKey); - - if (identity != null) - { - return new AppleCertificatePal(identity); - } - - return null; - } - - private ICertificatePal CopyWithPrivateKey(SafeSecKeyRefHandle? privateKey) - { - if (privateKey == null) - { - // Both Windows and Linux/OpenSSL are unaware if they bound a public or private key. - // Here, we do know. So throw if we can't do what they asked. - throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); - } - - SafeKeychainHandle keychain = Interop.AppleCrypto.SecKeychainItemCopyKeychain(privateKey); - - // If we're using a key already in a keychain don't add the certificate to that keychain here, - // do it in the temporary add/remove in the shim. - SafeKeychainHandle cloneKeychain = SafeTemporaryKeychainHandle.InvalidHandle; - - if (keychain.IsInvalid) - { - keychain = Interop.AppleCrypto.CreateTemporaryKeychain(); - cloneKeychain = keychain; - } - - // Because SecIdentityRef only has private constructors we need to have the cert and the key - // in the same keychain. That almost certainly means we're going to need to add this cert to a - // keychain, and when a cert that isn't part of a keychain gets added to a keychain then the - // interior pointer of "what keychain did I come from?" used by SecKeychainItemCopyKeychain gets - // set. That makes this function have side effects, which is not desired. - // - // It also makes reference tracking on temporary keychains broken, since the cert can - // DangerousRelease a handle it didn't DangerousAddRef on. And so CopyWithPrivateKey makes - // a temporary keychain, then deletes it before anyone has a chance to (e.g.) export the - // new identity as a PKCS#12 blob. - // - // Solution: Clone the cert, like we do in Windows. - SafeSecCertificateHandle tempHandle; - - { - byte[] export = RawData; - const bool exportable = false; - SafeSecIdentityHandle identityHandle; - tempHandle = Interop.AppleCrypto.X509ImportCertificate( - export, - X509ContentType.Cert, - SafePasswordHandle.InvalidHandle, - cloneKeychain, - exportable, - out identityHandle); - - Debug.Assert(identityHandle.IsInvalid, "identityHandle should be IsInvalid"); - identityHandle.Dispose(); - - Debug.Assert(!tempHandle.IsInvalid, "tempHandle should not be IsInvalid"); - } - - using (keychain) - using (tempHandle) - { - SafeSecIdentityHandle identityHandle = Interop.AppleCrypto.X509CopyWithPrivateKey( - tempHandle, - privateKey, - keychain); - - AppleCertificatePal newPal = new AppleCertificatePal(identityHandle); - return newPal; - } - } - public string GetNameInfo(X509NameType nameType, bool forIssuer) { EnsureCertData(); @@ -694,6 +402,5 @@ private void EnsureCertData() _certData = new CertificateData(Interop.AppleCrypto.X509GetRawData(_certHandle)); _readCertData = true; } - } } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/X509Pal.ECKey.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/X509Pal.ECKey.cs new file mode 100644 index 00000000000000..d4295ada4b50dd --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/X509Pal.ECKey.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Diagnostics; +using System.Formats.Asn1; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.Asn1.Pkcs12; +using System.Security.Cryptography.X509Certificates; + +namespace Internal.Cryptography.Pal +{ + internal sealed partial class X509Pal + { + private sealed partial class AppleX509Pal : ManagedX509ExtensionProcessor, IX509Pal + { + public ECDsa DecodeECDsaPublicKey(ICertificatePal? certificatePal) + { + return new ECDsaImplementation.ECDsaSecurityTransforms(DecodeECPublicKey(certificatePal)); + } + + public ECDiffieHellman DecodeECDiffieHellmanPublicKey(ICertificatePal? certificatePal) + { + return new ECDiffieHellmanImplementation.ECDiffieHellmanSecurityTransforms(DecodeECPublicKey(certificatePal)); + } + + private static SafeSecKeyRefHandle DecodeECPublicKey(ICertificatePal? certificatePal) + { + const int errSecInvalidKeyRef = -67712; + const int errSecUnsupportedKeySize = -67735; + + if (certificatePal is null) + throw new NotSupportedException(SR.NotSupported_KeyAlgorithm); + + AppleCertificatePal applePal = (AppleCertificatePal)certificatePal; + SafeSecKeyRefHandle key = Interop.AppleCrypto.X509GetPublicKey(applePal.CertificateHandle); + + // If X509GetPublicKey uses the new SecCertificateCopyKey API it can return an invalid + // key reference for unsupported algorithms. This currently happens for the BrainpoolP160r1 + // algorithm in the test suite (as of macOS Mojave Developer Preview 4). + if (key.IsInvalid) + { + throw Interop.AppleCrypto.CreateExceptionForOSStatus(errSecInvalidKeyRef); + } + // EccGetKeySizeInBits can fail for two reasons. First, the Apple implementation has changed + // and we receive values from API that were not previously handled. In that case the + // implementation will need to be adjusted to handle these values. Second, we deliberately + // return 0 from the native code to prevent hitting buggy API implementations in Apple code + // later. + if (Interop.AppleCrypto.EccGetKeySizeInBits(key) == 0) + { + key.Dispose(); + throw Interop.AppleCrypto.CreateExceptionForOSStatus(errSecUnsupportedKeySize); + } + + return key; + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/X509Pal.X500Name.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/X509Pal.X500Name.cs new file mode 100644 index 00000000000000..0bcaae2ab5fc5a --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/X509Pal.X500Name.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +namespace Internal.Cryptography.Pal +{ + internal sealed partial class X509Pal + { + private sealed partial class AppleX509Pal : ManagedX509ExtensionProcessor, IX509Pal + { + public string X500DistinguishedNameDecode(byte[] encodedDistinguishedName, X500DistinguishedNameFlags flag) + { + return X500NameEncoder.X500DistinguishedNameDecode(encodedDistinguishedName, true, flag); + } + + public byte[] X500DistinguishedNameEncode(string distinguishedName, X500DistinguishedNameFlags flag) + { + return X500NameEncoder.X500DistinguishedNameEncode(distinguishedName, flag); + } + + public string X500DistinguishedNameFormat(byte[] encodedDistinguishedName, bool multiLine) + { + return X500NameEncoder.X500DistinguishedNameDecode( + encodedDistinguishedName, + true, + multiLine ? X500DistinguishedNameFlags.UseNewLines : X500DistinguishedNameFlags.None, + multiLine); + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/X509Pal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/X509Pal.cs index c730b7ff5b6f71..2b654b2b3b9c2f 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/X509Pal.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/X509Pal.cs @@ -23,16 +23,6 @@ private X509Pal() private sealed partial class AppleX509Pal : ManagedX509ExtensionProcessor, IX509Pal { - public ECDsa DecodeECDsaPublicKey(ICertificatePal? certificatePal) - { - return new ECDsaImplementation.ECDsaSecurityTransforms(DecodeECPublicKey(certificatePal)); - } - - public ECDiffieHellman DecodeECDiffieHellmanPublicKey(ICertificatePal? certificatePal) - { - return new ECDiffieHellmanImplementation.ECDiffieHellmanSecurityTransforms(DecodeECPublicKey(certificatePal)); - } - public AsymmetricAlgorithm DecodePublicKey(Oid oid, byte[] encodedKeyValue, byte[] encodedParameters, ICertificatePal? certificatePal) { @@ -72,38 +62,6 @@ public AsymmetricAlgorithm DecodePublicKey(Oid oid, byte[] encodedKeyValue, byte throw new NotSupportedException(SR.NotSupported_KeyAlgorithm); } - private static SafeSecKeyRefHandle DecodeECPublicKey(ICertificatePal? certificatePal) - { - const int errSecInvalidKeyRef = -67712; - const int errSecUnsupportedKeySize = -67735; - - if (certificatePal is null) - throw new NotSupportedException(SR.NotSupported_KeyAlgorithm); - - AppleCertificatePal applePal = (AppleCertificatePal)certificatePal; - SafeSecKeyRefHandle key = Interop.AppleCrypto.X509GetPublicKey(applePal.CertificateHandle); - - // If X509GetPublicKey uses the new SecCertificateCopyKey API it can return an invalid - // key reference for unsupported algorithms. This currently happens for the BrainpoolP160r1 - // algorithm in the test suite (as of macOS Mojave Developer Preview 4). - if (key.IsInvalid) - { - throw Interop.AppleCrypto.CreateExceptionForOSStatus(errSecInvalidKeyRef); - } - // EccGetKeySizeInBits can fail for two reasons. First, the Apple implementation has changed - // and we receive values from API that were not previously handled. In that case the - // implementation will need to be adjusted to handle these values. Second, we deliberately - // return 0 from the native code to prevent hitting buggy API implementations in Apple code - // later. - if (Interop.AppleCrypto.EccGetKeySizeInBits(key) == 0) - { - key.Dispose(); - throw Interop.AppleCrypto.CreateExceptionForOSStatus(errSecUnsupportedKeySize); - } - - return key; - } - private static AsymmetricAlgorithm DecodeRsaPublicKey(byte[] encodedKeyValue) { RSA rsa = RSA.Create(); @@ -154,25 +112,6 @@ private static AsymmetricAlgorithm DecodeDsaPublicKey(byte[] encodedKeyValue, by } } - public string X500DistinguishedNameDecode(byte[] encodedDistinguishedName, X500DistinguishedNameFlags flag) - { - return X500NameEncoder.X500DistinguishedNameDecode(encodedDistinguishedName, true, flag); - } - - public byte[] X500DistinguishedNameEncode(string distinguishedName, X500DistinguishedNameFlags flag) - { - return X500NameEncoder.X500DistinguishedNameEncode(distinguishedName, flag); - } - - public string X500DistinguishedNameFormat(byte[] encodedDistinguishedName, bool multiLine) - { - return X500NameEncoder.X500DistinguishedNameDecode( - encodedDistinguishedName, - true, - multiLine ? X500DistinguishedNameFlags.UseNewLines : X500DistinguishedNameFlags.None, - multiLine); - } - public X509ContentType GetCertContentType(ReadOnlySpan rawData) { const int errSecUnknownFormat = -25257; diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificateExporter.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificateExporter.cs new file mode 100644 index 00000000000000..54bf659a1fcca5 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificateExporter.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography.Pal +{ + internal sealed class AppleCertificateExporter : UnixExportProvider + { + private AsymmetricAlgorithm? _privateKey; + + public AppleCertificateExporter(ICertificatePalCore cert) + : base(cert) + { + } + + public AppleCertificateExporter(ICertificatePalCore cert, AsymmetricAlgorithm privateKey) + : base(cert) + { + _privateKey = privateKey; + } + + public AppleCertificateExporter(X509Certificate2Collection certs) + : base(certs) + { + } + + protected override byte[] ExportPkcs7() + { + throw new CryptographicException( + SR.Cryptography_X509_PKCS7_Unsupported, + new PlatformNotSupportedException(SR.Cryptography_X509_PKCS7_Unsupported)); + } + + protected override byte[] ExportPkcs8(ICertificatePalCore certificatePal, ReadOnlySpan password) + { + if (_privateKey != null) + { + return _privateKey.ExportEncryptedPkcs8PrivateKey(password, s_windowsPbe); + } + + Debug.Assert(certificatePal.HasPrivateKey); + ICertificatePal pal = (ICertificatePal)certificatePal; + AsymmetricAlgorithm algorithm; + + switch (pal.KeyAlgorithm) + { + case Oids.Rsa: + algorithm = pal.GetRSAPrivateKey()!; + break; + case Oids.EcPublicKey: + algorithm = pal.GetECDsaPrivateKey()!; + break; + case Oids.Dsa: + default: + throw new CryptographicException(SR.Format(SR.Cryptography_UnknownKeyAlgorithm, pal.KeyAlgorithm)); + }; + + using (algorithm) + { + return algorithm.ExportEncryptedPkcs8PrivateKey(password, s_windowsPbe); + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.ImportExport.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.ImportExport.cs new file mode 100644 index 00000000000000..dfdb5b554f4a70 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.ImportExport.cs @@ -0,0 +1,166 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Formats.Asn1; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.Asn1.Pkcs12; +using System.Security.Cryptography.Asn1.Pkcs7; +using System.Text; +using System.Threading; +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography.Pal +{ + internal sealed partial class AppleCertificatePal : ICertificatePal + { + private static bool IsPkcs12(ReadOnlySpan rawData) + { + try + { + unsafe + { + fixed (byte* pin = rawData) + { + using (var manager = new PointerMemoryManager(pin, rawData.Length)) + { + PfxAsn.Decode(manager.Memory, AsnEncodingRules.BER); + } + + return true; + } + } + } + catch (CryptographicException) + { + } + + return false; + } + + private static bool IsPkcs7Signed(ReadOnlySpan rawData) + { + try + { + unsafe + { + fixed (byte* pin = rawData) + { + using (var manager = new PointerMemoryManager(pin, rawData.Length)) + { + AsnValueReader reader = new AsnValueReader(rawData, AsnEncodingRules.BER); + + ContentInfoAsn.Decode(ref reader, manager.Memory, out ContentInfoAsn contentInfo); + + switch (contentInfo.ContentType) + { + case Oids.Pkcs7Signed: + case Oids.Pkcs7SignedEnveloped: + return true; + } + } + } + } + } + catch (CryptographicException) + { + } + + return false; + } + + internal static X509ContentType GetDerCertContentType(ReadOnlySpan rawData) + { + X509ContentType contentType = Interop.AppleCrypto.X509GetContentType(rawData); + + if (contentType == X509ContentType.Unknown) + { + if (IsPkcs12(rawData)) + { + return X509ContentType.Pkcs12; + } + + if (IsPkcs7Signed(rawData)) + { + return X509ContentType.Pkcs7; + } + } + + return contentType; + } + + internal static ICertificatePal FromDerBlob( + ReadOnlySpan rawData, + X509ContentType contentType, + SafePasswordHandle password, + X509KeyStorageFlags keyStorageFlags) + { + Debug.Assert(password != null); + + if (contentType == X509ContentType.Pkcs7) + { + throw new CryptographicException( + SR.Cryptography_X509_PKCS7_Unsupported, + new PlatformNotSupportedException(SR.Cryptography_X509_PKCS7_Unsupported)); + } + + if (contentType == X509ContentType.Pkcs12) + { + // TODO: + // We ignore keyStorageFlags which is tracked in https://github.com/dotnet/runtime/issues/52434. + // The keys are always imported as ephemeral and never persisted. Exportability is ignored for + // the moment and it needs to be investigated how to map it to iOS keychain primitives. + return ImportPkcs12(rawData, password); + } + + SafeSecIdentityHandle identityHandle; + SafeSecCertificateHandle certHandle = Interop.AppleCrypto.X509ImportCertificate( + rawData, + contentType, + password, + out identityHandle); + + if (identityHandle.IsInvalid) + { + identityHandle.Dispose(); + return new AppleCertificatePal(certHandle); + } + + Debug.Fail("Non-PKCS12 import produced an identity handle"); + + identityHandle.Dispose(); + certHandle.Dispose(); + throw new CryptographicException(); + } + + public static ICertificatePal FromBlob( + ReadOnlySpan rawData, + SafePasswordHandle password, + X509KeyStorageFlags keyStorageFlags) + { + Debug.Assert(password != null); + + ICertificatePal? result = null; + TryDecodePem( + rawData, + (derData, contentType) => + { + result = FromDerBlob(derData, contentType, password, keyStorageFlags); + return false; + }); + + return result ?? FromDerBlob(rawData, GetDerCertContentType(rawData), password, keyStorageFlags); + } + + public void DisposeTempKeychain() + { + // No temporary keychain on iOS + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.Keys.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.Keys.cs new file mode 100644 index 00000000000000..98a8df28cf255d --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.Keys.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Security.Cryptography; + +namespace Internal.Cryptography.Pal +{ + internal sealed partial class AppleCertificatePal : ICertificatePal + { + public DSA? GetDSAPrivateKey() + { + if (_identityHandle == null) + return null; + + throw new PlatformNotSupportedException(); + } + + public ICertificatePal CopyWithPrivateKey(DSA privateKey) + { + throw new PlatformNotSupportedException(); + } + + public ICertificatePal CopyWithPrivateKey(ECDsa privateKey) + { + return ImportPkcs12(new UnixPkcs12Reader.CertAndKey { Cert = this, Key = privateKey }); + } + + public ICertificatePal CopyWithPrivateKey(ECDiffieHellman privateKey) + { + return ImportPkcs12(new UnixPkcs12Reader.CertAndKey { Cert = this, Key = privateKey }); + } + + public ICertificatePal CopyWithPrivateKey(RSA privateKey) + { + return ImportPkcs12(new UnixPkcs12Reader.CertAndKey { Cert = this, Key = privateKey }); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.Pem.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.Pem.cs new file mode 100644 index 00000000000000..254a0dad8ca07e --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.Pem.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Diagnostics; +using System.Text; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.X509Certificates; + +namespace Internal.Cryptography.Pal +{ + internal sealed partial class AppleCertificatePal : ICertificatePal + { + // Byte representation of "-----BEGIN " + private static byte[] pemBegin = new byte[] { 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x42, 0x45, 0x47, 0x49, 0x4E, 0x20 }; + + internal delegate bool DerCallback(ReadOnlySpan derData, X509ContentType contentType); + + internal static bool TryDecodePem(ReadOnlySpan rawData, DerCallback derCallback) + { + // If the character is a control character that isn't whitespace, then we're probably using a DER encoding + // and not using a PEM encoding in ASCII. + if (char.IsControl((char)rawData[0]) && !char.IsWhiteSpace((char)rawData[0])) + { + return false; + } + + // Look for the PEM marker. This doesn't guarantee it will be a valid PEM since we don't check whether + // the marker is at the beginning of line or whether the line is a complete marker. It's just a quick + // check to avoid conversion from bytes to characters if the content is DER encoded. + if (rawData.IndexOf(pemBegin) < 0) + { + return false; + } + + char[] certPem = ArrayPool.Shared.Rent(rawData.Length); + byte[]? certBytes = null; + + try + { + Encoding.ASCII.GetChars(rawData, certPem); + + foreach ((ReadOnlySpan contents, PemFields fields) in new PemEnumerator(certPem.AsSpan(0, rawData.Length))) + { + ReadOnlySpan label = contents[fields.Label]; + + if (label.SequenceEqual(PemLabels.X509Certificate) || label.SequenceEqual(PemLabels.Pkcs7Certificate)) + { + certBytes = CryptoPool.Rent(fields.DecodedDataLength); + + if (!Convert.TryFromBase64Chars(contents[fields.Base64Data], certBytes, out int bytesWritten) + || bytesWritten != fields.DecodedDataLength) + { + Debug.Fail("The contents should have already been validated by the PEM reader."); + throw new CryptographicException(SR.Cryptography_X509_NoPemCertificate); + } + + X509ContentType contentType = + label.SequenceEqual(PemLabels.X509Certificate) ? + X509ContentType.Cert : + X509ContentType.Pkcs7; + bool cont = derCallback(certBytes.AsSpan(0, bytesWritten), contentType); + + CryptoPool.Return(certBytes, clearSize: 0); + certBytes = null; + + if (!cont) + { + return true; + } + } + } + } + finally + { + ArrayPool.Shared.Return(certPem, clearArray: true); + + if (certBytes != null) + { + CryptoPool.Return(certBytes, clearSize: 0); + } + } + + return true; + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.Pkcs12.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.Pkcs12.cs new file mode 100644 index 00000000000000..87e45b117e836f --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.Pkcs12.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Formats.Asn1; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.Asn1.Pkcs12; +using System.Text; +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography.Pal +{ + internal sealed partial class AppleCertificatePal : ICertificatePal + { + private static SafePasswordHandle s_passwordExportHandle = new SafePasswordHandle("DotnetExportPassphrase"); + + private static AppleCertificatePal ImportPkcs12( + ReadOnlySpan rawData, + SafePasswordHandle password) + { + using (ApplePkcs12Reader reader = new ApplePkcs12Reader(rawData)) + { + reader.Decrypt(password); + return ImportPkcs12(reader.GetSingleCert()); + } + } + + internal static AppleCertificatePal ImportPkcs12(UnixPkcs12Reader.CertAndKey certAndKey) + { + AppleCertificatePal pal = (AppleCertificatePal)certAndKey.Cert!; + + if (certAndKey.Key != null) + { + AppleCertificateExporter exporter = new AppleCertificateExporter(new TempExportPal(pal), certAndKey.Key); + byte[] smallPfx = exporter.Export(X509ContentType.Pkcs12, s_passwordExportHandle)!; + + SafeSecIdentityHandle identityHandle; + SafeSecCertificateHandle certHandle = Interop.AppleCrypto.X509ImportCertificate( + smallPfx, + X509ContentType.Pkcs12, + s_passwordExportHandle, + out identityHandle); + + if (identityHandle.IsInvalid) + { + identityHandle.Dispose(); + return new AppleCertificatePal(certHandle); + } + + certHandle.Dispose(); + return new AppleCertificatePal(identityHandle); + } + + return pal; + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/ApplePkcs12CertLoader.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/ApplePkcs12CertLoader.cs new file mode 100644 index 00000000000000..ac420ca1c24234 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/ApplePkcs12CertLoader.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography.Pal +{ + internal sealed class ApplePkcs12CertLoader : ILoaderPal + { + private readonly ApplePkcs12Reader _pkcs12; + private SafePasswordHandle _password; + + public ApplePkcs12CertLoader( + ApplePkcs12Reader pkcs12, + SafePasswordHandle password) + { + _pkcs12 = pkcs12; + + bool addedRef = false; + password.DangerousAddRef(ref addedRef); + _password = password; + } + + public void Dispose() + { + _pkcs12.Dispose(); + + SafePasswordHandle? password = Interlocked.Exchange(ref _password, null!); + password?.DangerousRelease(); + } + + public void MoveTo(X509Certificate2Collection collection) + { + foreach (UnixPkcs12Reader.CertAndKey certAndKey in _pkcs12.EnumerateAll()) + { + AppleCertificatePal pal = (AppleCertificatePal)certAndKey.Cert!; + collection.Add(new X509Certificate2(AppleCertificatePal.ImportPkcs12(certAndKey))); + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/ApplePkcs12Reader.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/ApplePkcs12Reader.cs new file mode 100644 index 00000000000000..bb7acb4e27d9c6 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/ApplePkcs12Reader.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Formats.Asn1; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography.Pal +{ + internal sealed class ApplePkcs12Reader : UnixPkcs12Reader + { + internal ApplePkcs12Reader(ReadOnlySpan data) + { + ParsePkcs12(data); + } + + protected override ICertificatePalCore ReadX509Der(ReadOnlyMemory data) + { + SafeSecCertificateHandle certHandle = Interop.AppleCrypto.X509ImportCertificate( + data.Span, + X509ContentType.Cert, + SafePasswordHandle.InvalidHandle, + out SafeSecIdentityHandle identityHandle); + + if (identityHandle.IsInvalid) + { + identityHandle.Dispose(); + return new AppleCertificatePal(certHandle); + } + + Debug.Fail("Non-PKCS12 import produced an identity handle"); + + identityHandle.Dispose(); + certHandle.Dispose(); + throw new CryptographicException(); + } + + protected override AsymmetricAlgorithm LoadKey(ReadOnlyMemory pkcs8) + { + PrivateKeyInfoAsn privateKeyInfo = PrivateKeyInfoAsn.Decode(pkcs8, AsnEncodingRules.BER); + AsymmetricAlgorithm key; + + switch (privateKeyInfo.PrivateKeyAlgorithm.Algorithm) + { + case Oids.Rsa: + key = new RSAImplementation.RSASecurityTransforms(); + break; + case Oids.EcDiffieHellman: + case Oids.EcPublicKey: + key = new ECDsaImplementation.ECDsaSecurityTransforms(); + break; + default: + throw new CryptographicException( + SR.Cryptography_UnknownAlgorithmIdentifier, + privateKeyInfo.PrivateKeyAlgorithm.Algorithm); + } + + key.ImportPkcs8PrivateKey(pkcs8.Span, out int bytesRead); + + if (bytesRead != pkcs8.Length) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + return key; + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/StorePal.AppleKeychainStore.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/StorePal.AppleKeychainStore.cs new file mode 100644 index 00000000000000..88be19a1e03d57 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/StorePal.AppleKeychainStore.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography.Pal +{ + internal sealed partial class StorePal + { + private sealed class AppleKeychainStore : IStorePal + { + private readonly bool _readonly; + + internal AppleKeychainStore(OpenFlags openFlags) + { + _readonly = (openFlags & (OpenFlags.ReadWrite | OpenFlags.MaxAllowed)) == 0; + } + + public void Dispose() + { + } + + public void CloneTo(X509Certificate2Collection collection) + { + HashSet dedupedCerts = new HashSet(); + + using (SafeCFArrayHandle identities = Interop.AppleCrypto.KeychainEnumerateIdentities()) + { + ReadCollection(identities, dedupedCerts); + } + + using (SafeCFArrayHandle certs = Interop.AppleCrypto.KeychainEnumerateCerts()) + { + ReadCollection(certs, dedupedCerts); + } + + foreach (X509Certificate2 cert in dedupedCerts) + { + collection.Add(cert); + } + } + + public void Add(ICertificatePal cert) + { + if (_readonly) + throw new CryptographicException(SR.Cryptography_X509_StoreReadOnly); + + AppleCertificatePal applePal = (AppleCertificatePal)cert; + + var handle = (SafeHandle?)applePal.IdentityHandle ?? applePal.CertificateHandle; + Interop.AppleCrypto.X509StoreAddCertificate(handle); + } + + public void Remove(ICertificatePal cert) + { + AppleCertificatePal applePal = (AppleCertificatePal)cert; + + var handle = (SafeHandle?)applePal.IdentityHandle ?? applePal.CertificateHandle; + Interop.AppleCrypto.X509StoreRemoveCertificate(handle, _readonly); + } + + public SafeHandle? SafeHandle { get; } + + public static AppleKeychainStore OpenDefaultKeychain(OpenFlags openFlags) + { + return new AppleKeychainStore(openFlags); + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/StorePal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/StorePal.cs new file mode 100644 index 00000000000000..efb7d68349a00c --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/StorePal.cs @@ -0,0 +1,187 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography.Pal +{ + internal sealed partial class StorePal + { + public static IStorePal FromHandle(IntPtr storeHandle) + { + throw new PlatformNotSupportedException($"{nameof(StorePal)}.{nameof(FromHandle)}"); + } + + public static ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) + { + List? certificateList = null; + + AppleCertificatePal.TryDecodePem( + rawData, + (derData, contentType) => + { + certificateList = certificateList ?? new List(); + certificateList.Add(AppleCertificatePal.FromDerBlob(derData, contentType, password, keyStorageFlags)); + return true; + }); + + if (certificateList != null) + { + return new CertCollectionLoader(certificateList); + } + + X509ContentType contentType = AppleCertificatePal.GetDerCertContentType(rawData); + + if (contentType == X509ContentType.Pkcs7) + { + throw new CryptographicException( + SR.Cryptography_X509_PKCS7_Unsupported, + new PlatformNotSupportedException(SR.Cryptography_X509_PKCS7_Unsupported)); + } + + if (contentType == X509ContentType.Pkcs12) + { + ApplePkcs12Reader reader = new ApplePkcs12Reader(rawData); + + try + { + reader.Decrypt(password); + return new ApplePkcs12CertLoader(reader, password); + } + catch + { + reader.Dispose(); + throw; + } + } + + SafeCFArrayHandle certs = Interop.AppleCrypto.X509ImportCollection( + rawData, + contentType, + password); + + using (certs) + { + long longCount = Interop.CoreFoundation.CFArrayGetCount(certs); + + if (longCount > int.MaxValue) + throw new CryptographicException(); + + int count = (int)longCount; + + // Apple returns things in the opposite order from Windows, so read backwards. + certificateList = new List(count); + for (int i = count - 1; i >= 0; i--) + { + IntPtr handle = Interop.CoreFoundation.CFArrayGetValueAtIndex(certs, i); + + if (handle != IntPtr.Zero) + { + ICertificatePal? certPal = CertificatePal.FromHandle(handle, throwOnFail: false); + + if (certPal != null) + { + certificateList.Add(certPal); + } + } + } + } + + return new CertCollectionLoader(certificateList); + } + + public static ILoaderPal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) + { + Debug.Assert(password != null); + + byte[] fileBytes = File.ReadAllBytes(fileName); + return FromBlob(fileBytes, password, keyStorageFlags); + } + + public static IExportPal FromCertificate(ICertificatePalCore cert) + { + return new AppleCertificateExporter(cert); + } + + public static IExportPal LinkFromCertificateCollection(X509Certificate2Collection certificates) + { + return new AppleCertificateExporter(certificates); + } + + public static IStorePal FromSystemStore(string storeName, StoreLocation storeLocation, OpenFlags openFlags) + { + StringComparer ordinalIgnoreCase = StringComparer.OrdinalIgnoreCase; + + if (ordinalIgnoreCase.Equals("Root", storeName)) + { + throw new CryptographicException( + SR.Cryptography_X509_StoreNotFound, + new PlatformNotSupportedException(SR.Cryptography_X509_Store_RootUnsupported)); + } + + if (storeLocation == StoreLocation.CurrentUser) + { + if (ordinalIgnoreCase.Equals("My", storeName)) + return AppleKeychainStore.OpenDefaultKeychain(openFlags); + if (ordinalIgnoreCase.Equals("Disallowed", storeName)) + return new UnsupportedDisallowedStore(openFlags); + } + + if ((openFlags & OpenFlags.OpenExistingOnly) == OpenFlags.OpenExistingOnly) + throw new CryptographicException(SR.Cryptography_X509_StoreNotFound); + + string message = SR.Format( + SR.Cryptography_X509_StoreCannotCreate, + storeName, + storeLocation); + + throw new CryptographicException(message, new PlatformNotSupportedException(message)); + } + + private static void ReadCollection(SafeCFArrayHandle matches, HashSet collection) + { + if (matches.IsInvalid) + { + return; + } + + long count = Interop.CoreFoundation.CFArrayGetCount(matches); + + for (int i = 0; i < count; i++) + { + IntPtr handle = Interop.CoreFoundation.CFArrayGetValueAtIndex(matches, i); + + SafeSecCertificateHandle certHandle; + SafeSecIdentityHandle identityHandle; + + if (Interop.AppleCrypto.X509DemuxAndRetainHandle(handle, out certHandle, out identityHandle)) + { + X509Certificate2 cert; + + if (certHandle.IsInvalid) + { + certHandle.Dispose(); + cert = new X509Certificate2(new AppleCertificatePal(identityHandle)); + } + else + { + identityHandle.Dispose(); + cert = new X509Certificate2(new AppleCertificatePal(certHandle)); + } + + if (!collection.Add(cert)) + { + cert.Dispose(); + } + } + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/X509Pal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/X509Pal.cs new file mode 100644 index 00000000000000..283bd38c066dce --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/X509Pal.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Diagnostics; +using System.Text; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.X509Certificates; + +namespace Internal.Cryptography.Pal +{ + internal sealed partial class X509Pal + { + public static IX509Pal Instance = new AppleX509Pal(); + + private X509Pal() + { + } + + private sealed partial class AppleX509Pal : ManagedX509ExtensionProcessor, IX509Pal + { + public AsymmetricAlgorithm DecodePublicKey(Oid oid, byte[] encodedKeyValue, byte[] encodedParameters, + ICertificatePal? certificatePal) + { + if (oid.Value != Oids.Rsa) + { + throw new NotSupportedException(SR.NotSupported_KeyAlgorithm); + } + + if (certificatePal is AppleCertificatePal applePal) + { + SafeSecKeyRefHandle key = Interop.AppleCrypto.X509GetPublicKey(applePal.CertificateHandle); + Debug.Assert(!key.IsInvalid); + return new RSAImplementation.RSASecurityTransforms(key); + } + else + { + RSA rsa = RSA.Create(); + try + { + rsa.ImportRSAPublicKey(new ReadOnlySpan(encodedKeyValue), out _); + return rsa; + } + catch (Exception) + { + rsa.Dispose(); + throw; + } + } + } + + public X509ContentType GetCertContentType(ReadOnlySpan rawData) + { + const int errSecUnknownFormat = -25257; + + if (rawData == null || rawData.Length == 0) + { + // Throw to match Windows and Unix behavior. + throw Interop.AppleCrypto.CreateExceptionForOSStatus(errSecUnknownFormat); + } + + X509ContentType result = X509ContentType.Unknown; + + AppleCertificatePal.TryDecodePem( + rawData, + (derData, contentType) => + { + result = contentType; + return false; + }); + + if (result == X509ContentType.Unknown) + { + result = AppleCertificatePal.GetDerCertContentType(rawData); + } + + if (result == X509ContentType.Unknown) + { + // Throw to match Windows and Unix behavior. + throw Interop.AppleCrypto.CreateExceptionForOSStatus(errSecUnknownFormat); + } + + return result; + } + + public X509ContentType GetCertContentType(string fileName) + { + return GetCertContentType(System.IO.File.ReadAllBytes(fileName)); + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.X509Certificates/src/Resources/Strings.resx index 97eefea5eff5ba..a5c33a43b40b88 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Resources/Strings.resx @@ -292,6 +292,9 @@ Cannot find the original signer. + + PKCS#7 certificate format is not supported on this platform. + The X509 certificate could not be added to the store. @@ -319,6 +322,9 @@ Failed to enumerate certificates from the store. + + Root certificate store is not supported on this platform. + The certificate contents do not contain a PEM with a CERTIFICATE label, or the content is malformed. diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj b/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj index 44505d275b80e0..58fd7885787218 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj @@ -476,8 +476,6 @@ Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Ecc.cs" /> - - - - Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml.cs Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml + - - + - - - - - - - + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + (() => pubKey.SignData(data, HashAlgorithmName.SHA1)); } } -#endif #if !NO_EPHEMERALKEYSET_AVAILABLE [Fact] diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PublicKeyTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PublicKeyTests.cs index 346cf590026987..0f19b3bd356101 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PublicKeyTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PublicKeyTests.cs @@ -105,6 +105,7 @@ public static void TestPublicKey_Key_RSA() } [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] public static void TestPublicKey_Key_DSA() { PublicKey pk = GetTestDsaKey(); @@ -563,8 +564,8 @@ public static void TestECDsa224PublicKey() } } -#if !NO_DSA_AVAILABLE [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] public static void TestDSAPublicKey() { using (var cert = new X509Certificate2(TestData.DssCer)) @@ -576,6 +577,7 @@ public static void TestDSAPublicKey() } [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] public static void TestDSAPublicKey_VerifiesSignature() { byte[] data = { 1, 2, 3, 4, 5 }; @@ -595,6 +597,7 @@ public static void TestDSAPublicKey_VerifiesSignature() } [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] public static void TestDSAPublicKey_RSACert() { using (var cert = new X509Certificate2(TestData.Rsa384CertificatePemBytes)) @@ -605,6 +608,7 @@ public static void TestDSAPublicKey_RSACert() } [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] public static void TestDSAPublicKey_ECDSACert() { using (var cert = new X509Certificate2(TestData.ECDsa256Certificate)) @@ -613,7 +617,6 @@ public static void TestDSAPublicKey_ECDSACert() Assert.Null(pubKey); } } -#endif [Fact] [PlatformSpecific(TestPlatforms.Windows)] // Uses P/Invokes @@ -666,6 +669,7 @@ public static void ExportSubjectPublicKeyInfo_RSA() } [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] public static void ExportSubjectPublicKeyInfo_DSA() { using DSA dsa = DSA.Create(); @@ -738,6 +742,7 @@ public static void CreateFromSubjectPublicKeyInfo_Roundtrip_RSA() } [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] public static void CreateFromSubjectPublicKeyInfo_Roundtrip_DSA() { using DSA dsa = DSA.Create(); @@ -783,6 +788,7 @@ public static void CreateFromSubjectPublicKeyInfo_Roundtrip_ECDH() } [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] public static void CreateFromSubjectPublicKeyInfo_Roundtrip_DSA_InvalidKey() { // The DSA key is invalid here, but we should be able to round-trip the diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/RSAOther.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/RSAOther.cs index 75505f0ad9258c..860c5507bb172e 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/RSAOther.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/RSAOther.cs @@ -14,6 +14,12 @@ internal RSAOther() _impl = RSA.Create(); } + public override int KeySize + { + get => _impl.KeySize; + set => _impl.KeySize = value; + } + public override KeySizes[] LegalKeySizes => _impl.LegalKeySizes; public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding) => _impl.Decrypt(data, padding); diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.X509Certificates/tests/Resources/Strings.resx index 7c6a6854840825..00e896df64b5b6 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/Resources/Strings.resx @@ -153,4 +153,13 @@ The input to WriteEncodedValue must represent a single encoded value with no trailing data. + + Removing the requested certificate would modify user trust settings, and has been denied. + + + Removing the requested certificate would modify admin trust settings, and has been denied. + + + The X509 certificate store is read-only. + diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/AiaTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/AiaTests.cs index 4dc2a2ae254a06..0a2e8b78cffce9 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/AiaTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/AiaTests.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Security.Cryptography.X509Certificates.Tests.Common; +using Test.Cryptography; using Xunit; namespace System.Security.Cryptography.X509Certificates.Tests.RevocationTests @@ -48,6 +49,7 @@ public static void EmptyAiaResponseIsIgnored() } [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "CA store is not available")] public static void DisableAiaOptionWorks() { CertificateAuthority.BuildPrivatePki( diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/DynamicRevocationTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/DynamicRevocationTests.cs index 95a26ec5ded866..5fc168b03e4a5f 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/DynamicRevocationTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/DynamicRevocationTests.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Security.Cryptography.X509Certificates.Tests.Common; +using Test.Cryptography; using Xunit; namespace System.Security.Cryptography.X509Certificates.Tests.RevocationTests @@ -21,7 +22,7 @@ public static partial class DynamicRevocationTests private static bool SupportsEntireChainCheck => !PlatformDetection.IsAndroid; private static readonly X509ChainStatusFlags ThisOsRevocationStatusUnknown = - PlatformDetection.IsOSX || PlatformDetection.IsAndroid ? + PlatformDetection.IsOSX || PlatformDetection.IsiOS || PlatformDetection.IstvOS || PlatformDetection.IsMacCatalyst || PlatformDetection.IsAndroid ? X509ChainStatusFlags.RevocationStatusUnknown : X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.OfflineRevocation; @@ -76,7 +77,7 @@ public static IEnumerable AllViableRevocation // https://github.com/dotnet/runtime/issues/31249 // not all scenarios are working on macOS. - if (PlatformDetection.IsOSX) + if (PlatformDetection.IsOSX || PlatformDetection.IsiOS || PlatformDetection.IstvOS || PlatformDetection.IsMacCatalyst) { if (!endEntityRevocation.HasFlag(PkiOptions.EndEntityRevocationViaOcsp)) { @@ -122,7 +123,7 @@ public static void NothingRevoked(PkiOptions pkiOptions) [Theory] [MemberData(nameof(AllViableRevocation))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void RevokeIntermediate(PkiOptions pkiOptions) { SimpleTest( @@ -191,7 +192,7 @@ public static void RevokeLeafWithAiaFetchingDisabled(PkiOptions pkiOptions) [Theory] [MemberData(nameof(AllViableRevocation))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void RevokeIntermediateAndEndEntity(PkiOptions pkiOptions) { SimpleTest( @@ -220,7 +221,7 @@ public static void RevokeIntermediateAndEndEntity(PkiOptions pkiOptions) [Theory] [MemberData(nameof(AllViableRevocation))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void RevokeRoot(PkiOptions pkiOptions) { SimpleTest( @@ -257,7 +258,7 @@ public static void RevokeRoot(PkiOptions pkiOptions) [Theory] [MemberData(nameof(AllViableRevocation))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void RevokeRootAndEndEntity(PkiOptions pkiOptions) { SimpleTest( @@ -292,7 +293,7 @@ public static void RevokeRootAndEndEntity(PkiOptions pkiOptions) [Theory] [MemberData(nameof(AllViableRevocation))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void RevokeRootAndIntermediate(PkiOptions pkiOptions) { SimpleTest( @@ -328,7 +329,7 @@ public static void RevokeRootAndIntermediate(PkiOptions pkiOptions) [Theory] [MemberData(nameof(AllViableRevocation))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void RevokeEverything(PkiOptions pkiOptions) { SimpleTest( @@ -451,7 +452,7 @@ public static void RevokeEndEntity_IssuerUnrelatedOcsp(PkiOptions pkiOptions) [Theory] [InlineData(PkiOptions.OcspEverywhere)] [InlineData(PkiOptions.IssuerRevocationViaOcsp | PkiOptions.AllEndEntityRevocation)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void RevokeEndEntity_RootUnrelatedOcsp(PkiOptions pkiOptions) { SimpleTest( @@ -554,7 +555,7 @@ public static IEnumerable PolicyErrorsNotTimeValidData } [Theory] [MemberData(nameof(PolicyErrorsNotTimeValidData))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void RevokeIntermediate_PolicyErrors_NotTimeValid(bool policyErrors, bool notTimeValid) { SimpleTest( @@ -639,7 +640,7 @@ public static void RevokeIntermediate_PolicyErrors_NotTimeValid(bool policyError [Theory] [MemberData(nameof(PolicyErrorsNotTimeValidData))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void RevokeEndEntity_PolicyErrors_NotTimeValid(bool policyErrors, bool notTimeValid) { SimpleTest( @@ -721,7 +722,7 @@ public static void RevokeEndEntity_PolicyErrors_NotTimeValid(bool policyErrors, [Theory] [MemberData(nameof(AllViableRevocation))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void RevokeEndEntity_RootRevocationOffline(PkiOptions pkiOptions) { BuildPrivatePki( @@ -798,7 +799,7 @@ public static void RevokeEndEntity_RootRevocationOffline(PkiOptions pkiOptions) [Theory] [MemberData(nameof(AllViableRevocation))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void NothingRevoked_RootRevocationOffline(PkiOptions pkiOptions) { BuildPrivatePki( @@ -888,7 +889,7 @@ public static void RevokeEndEntityWithInvalidRevocationSignature(PkiOptions pkiO [Theory] [MemberData(nameof(AllViableRevocation))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void RevokeIntermediateWithInvalidRevocationSignature(PkiOptions pkiOptions) { SimpleTest( @@ -917,7 +918,7 @@ public static void RevokeEndEntityWithInvalidRevocationName(PkiOptions pkiOption [Theory] [MemberData(nameof(AllViableRevocation))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void RevokeIntermediateWithInvalidRevocationName(PkiOptions pkiOptions) { SimpleTest( @@ -968,7 +969,7 @@ public static void RevokeEndEntityWithExpiredRevocation(PkiOptions pkiOptions) [Theory] [MemberData(nameof(AllViableRevocation))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void RevokeIntermediateWithExpiredRevocation(PkiOptions pkiOptions) { SimpleTest( @@ -1035,7 +1036,7 @@ public static void CheckEndEntityWithExpiredRevocation(PkiOptions pkiOptions) [Theory] [MemberData(nameof(AllViableRevocation))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void CheckIntermediateWithExpiredRevocation(PkiOptions pkiOptions) { bool usingCrl = pkiOptions.HasFlag(PkiOptions.IssuerRevocationViaCrl) || pkiOptions.HasFlag(PkiOptions.EndEntityRevocationViaCrl); @@ -1061,7 +1062,7 @@ public static void CheckIntermediateWithExpiredRevocation(PkiOptions pkiOptions) } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void TestRevocationWithNoNextUpdate_NotRevoked() { SimpleTest( @@ -1098,7 +1099,7 @@ public static void TestRevocationWithNoNextUpdate_NotRevoked() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/31249", PlatformSupport.AppleCrypto)] public static void TestRevocationWithNoNextUpdate_Revoked() { SimpleTest( @@ -1134,7 +1135,7 @@ public static void TestRevocationWithNoNextUpdate_Revoked() } [Fact] - [SkipOnPlatform(TestPlatforms.Android | TestPlatforms.OSX, "Android and macOS do not support offline revocation chain building.")] + [SkipOnPlatform(TestPlatforms.Android | PlatformSupport.AppleCrypto, "Android and macOS do not support offline revocation chain building.")] public static void TestRevocation_Offline_NotRevoked() { SimpleTest( @@ -1178,7 +1179,7 @@ public static void TestRevocation_Offline_NotRevoked() } [Fact] - [SkipOnPlatform(TestPlatforms.Android | TestPlatforms.OSX, "Android and macOS do not support offline revocation chain building.")] + [SkipOnPlatform(TestPlatforms.Android | PlatformSupport.AppleCrypto, "Android and macOS do not support offline revocation chain building.")] public static void TestRevocation_Offline_Revoked() { SimpleTest( diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj b/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj index 4aebe6daec3aa4..a3ecd6dbeaaf8b 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj @@ -95,7 +95,7 @@ Link="Common\System\IO\PersistedFiles.Names.Unix.cs" /> - + @@ -115,8 +115,8 @@ - - + + - + + + + diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509Certificate2PemTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509Certificate2PemTests.cs index 99d7ab111c0dcb..e8ce8c9869f105 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509Certificate2PemTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509Certificate2PemTests.cs @@ -268,6 +268,7 @@ public static void CreateFromPem_ECDH_Pkcs8_Success() } [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] public static void CreateFromPem_Dsa_Pkcs8_Success() { using (X509Certificate2 cert = X509Certificate2.CreateFromPem(TestData.DsaCertificate, TestData.DsaPkcs8Key)) @@ -278,6 +279,7 @@ public static void CreateFromPem_Dsa_Pkcs8_Success() } [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] public static void CreateFromPem_Dsa_EncryptedPkcs8_Success() { X509Certificate2 cert = X509Certificate2.CreateFromEncryptedPem( diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreMutableTests.iOS.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreMutableTests.iOS.cs new file mode 100644 index 00000000000000..29ae5f8927c1f0 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreMutableTests.iOS.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Security.Cryptography.X509Certificates.Tests +{ + public static partial class X509StoreMutableTests + { + public static bool PermissionsAllowStoreWrite { get; } = true; + + [Theory] + [InlineData(nameof(TestData.RsaCertificate), TestData.RsaCertificate, TestData.RsaPkcs8Key)] + [InlineData(nameof(TestData.EcDhCertificate), TestData.EcDhCertificate, TestData.EcDhPkcs8Key)] + [InlineData(nameof(TestData.ECDsaCertificate), TestData.ECDsaCertificate, TestData.ECDsaPkcs8Key)] + public static void AddRemove_CertWithPrivateKey(string testCase, string certPem, string keyPem) + { + _ = testCase; + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + using (var cert = X509Certificate2.CreateFromPem(certPem, keyPem)) + { + store.Open(OpenFlags.ReadWrite); + + // Make sure cert is not already in the store + store.Remove(cert); + Assert.False(IsCertInStore(cert, store), "Certificate should not be found on pre-condition"); + + // Add + store.Add(cert); + Assert.True(IsCertInStore(cert, store), "Certificate should be found after add"); + Assert.True(StoreHasPrivateKey(store, cert), "Certificate in store should have a private key"); + + // Remove + store.Remove(cert); + Assert.False(IsCertInStore(cert, store), "Certificate should not be found after remove"); + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreTests.cs index ea0906d73e1d28..a484d323d8d1e0 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreTests.cs @@ -405,6 +405,7 @@ public static void OpenMachineMyStore_NotSupported() [Theory] [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.OSX)] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "Root certificate store is not accessible")] [InlineData(OpenFlags.ReadOnly, false)] [InlineData(OpenFlags.MaxAllowed, false)] [InlineData(OpenFlags.ReadWrite, true)] @@ -427,6 +428,7 @@ public static void OpenMachineRootStore_Permissions(OpenFlags permissions, bool } [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "Root certificate store is not accessible")] public static void MachineRootStore_NonEmpty() { // This test will fail on systems where the administrator has gone out of their diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index 21c306e3a9e999..3e5bb373e4ac96 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -139,8 +139,6 @@ - -