diff --git a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.cs b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.cs index ef6814819b28..f0903605821c 100644 --- a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.cs +++ b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.cs @@ -9,7 +9,7 @@ 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 diff --git a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Trust.cs b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Trust.cs index da0f10f748d3..b3bfa24c4121 100644 --- a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Trust.cs +++ b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Trust.cs @@ -22,7 +22,38 @@ private static extern int AppleCryptoNative_StoreEnumerateMachineRoot( out SafeCFArrayHandle pCertsOut, out int pOSStatusOut); + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_StoreEnumerateUserDisallowed( + out SafeCFArrayHandle pCertsOut, + out int pOSStatusOut); + + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_StoreEnumerateMachineDisallowed( + out SafeCFArrayHandle pCertsOut, + out int pOSStatusOut); + + private delegate int StoreEnumerator(out SafeCFArrayHandle pCertsOut, out int pOSStatusOut); + + internal static SafeCFArrayHandle StoreEnumerateDisallowed(StoreLocation location) + { + return EnumerateStore( + location, + AppleCryptoNative_StoreEnumerateUserDisallowed, + AppleCryptoNative_StoreEnumerateMachineDisallowed); + } + internal static SafeCFArrayHandle StoreEnumerateRoot(StoreLocation location) + { + return EnumerateStore( + location, + AppleCryptoNative_StoreEnumerateUserRoot, + AppleCryptoNative_StoreEnumerateMachineRoot); + } + + private static SafeCFArrayHandle EnumerateStore( + StoreLocation location, + StoreEnumerator userEnumerator, + StoreEnumerator machineEnumerator) { int result; SafeCFArrayHandle matches; @@ -30,11 +61,11 @@ internal static SafeCFArrayHandle StoreEnumerateRoot(StoreLocation location) if (location == StoreLocation.CurrentUser) { - result = AppleCryptoNative_StoreEnumerateUserRoot(out matches, out osStatus); + result = userEnumerator(out matches, out osStatus); } else if (location == StoreLocation.LocalMachine) { - result = AppleCryptoNative_StoreEnumerateMachineRoot(out matches, out osStatus); + result = machineEnumerator(out matches, out osStatus); } else { @@ -52,7 +83,7 @@ internal static SafeCFArrayHandle StoreEnumerateRoot(StoreLocation location) if (result == 0) throw CreateExceptionForOSStatus(osStatus); - Debug.Fail($"Unexpected result from AppleCryptoNative_StoreEnumerateRoot ({location}): {result}"); + Debug.Fail($"Unexpected result from {location} trust store enumeration: {result}"); throw new CryptographicException(); } } diff --git a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs index a15a3b613d6e..4bf5a41f0d16 100644 --- a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs +++ b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs @@ -21,7 +21,8 @@ private static extern int AppleCryptoNative_X509ImportCertificate( int cbKeyBlob, X509ContentType contentType, SafeCreateHandle cfPfxPassphrase, - SafeTemporaryKeychainHandle tmpKeychain, + SafeKeychainHandle tmpKeychain, + int exportable, out SafeSecCertificateHandle pCertOut, out SafeSecIdentityHandle pPrivateKeyOut, out int pOSStatus); @@ -32,7 +33,8 @@ private static extern int AppleCryptoNative_X509ImportCollection( int cbKeyBlob, X509ContentType contentType, SafeCreateHandle cfPfxPassphrase, - SafeTemporaryKeychainHandle tmpKeychain, + SafeKeychainHandle tmpKeychain, + int exportable, out SafeCFArrayHandle pCollectionOut, out int pOSStatus); @@ -100,7 +102,8 @@ internal static SafeSecCertificateHandle X509ImportCertificate( byte[] bytes, X509ContentType contentType, SafePasswordHandle importPassword, - SafeTemporaryKeychainHandle tmpKeychain, + SafeKeychainHandle keychain, + bool exportable, out SafeSecIdentityHandle identityHandle) { SafeSecCertificateHandle certHandle; @@ -128,7 +131,8 @@ internal static SafeSecCertificateHandle X509ImportCertificate( bytes.Length, contentType, cfPassphrase, - tmpKeychain, + keychain, + exportable ? 1 : 0, out certHandle, out identityHandle, out osStatus); @@ -178,7 +182,8 @@ internal static SafeCFArrayHandle X509ImportCollection( byte[] bytes, X509ContentType contentType, SafePasswordHandle importPassword, - SafeTemporaryKeychainHandle tmpKeychain) + SafeKeychainHandle keychain, + bool exportable) { SafeCreateHandle cfPassphrase = s_nullExportString; bool releasePassword = false; @@ -205,7 +210,8 @@ internal static SafeCFArrayHandle X509ImportCollection( bytes.Length, contentType, cfPassphrase, - tmpKeychain, + keychain, + exportable ? 1 : 0, out collectionHandle, out osStatus); diff --git a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509Store.cs b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509Store.cs new file mode 100644 index 000000000000..4dd78e34a1f1 --- /dev/null +++ b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509Store.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. +// See the LICENSE file in the project root for more information. + +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( + SafeSecCertificateHandle cert, + SafeKeychainHandle keychain, + 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(SafeSecCertificateHandle certHandle, SafeKeychainHandle keychain) + { + int osStatus; + int ret = AppleCryptoNative_X509StoreRemoveCertificate(certHandle, keychain, out osStatus); + + if (ret == 0) + { + throw CreateExceptionForOSStatus(osStatus); + } + + const int SuccessOrNoMatch = 1; + const int UserTrustExists = 2; + const int AdminTrustExists = 3; + + switch (ret) + { + case SuccessOrNoMatch: + break; + case UserTrustExists: + throw new CryptographicException(SR.Cryptography_X509Store_WouldModifyUserTrust); + case AdminTrustExists: + throw new CryptographicException(SR.Cryptography_X509Store_WouldModifyAdminTrust); + default: + Debug.Fail($"Unexpected result from AppleCryptoNative_X509StoreRemoveCertificate: {ret}"); + throw new CryptographicException(); + } + } + } +} \ No newline at end of file diff --git a/src/Common/src/System/Security/Cryptography/DSASecurityTransforms.cs b/src/Common/src/System/Security/Cryptography/DSASecurityTransforms.cs index f5af7e10b887..09d8a64d7916 100644 --- a/src/Common/src/System/Security/Cryptography/DSASecurityTransforms.cs +++ b/src/Common/src/System/Security/Cryptography/DSASecurityTransforms.cs @@ -9,12 +9,15 @@ namespace System.Security.Cryptography { +#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS + public partial class DSA : AsymmetricAlgorithm { public static new DSA Create() { return new DSAImplementation.DSASecurityTransforms(); } +#endif internal static partial class DSAImplementation { @@ -32,6 +35,11 @@ public DSASecurityTransforms(int keySize) KeySize = keySize; } + internal DSASecurityTransforms(SafeSecKeyRefHandle publicKey, SafeSecKeyRefHandle privateKey) + { + SetKey(SecKeyPair.PublicPrivatePair(publicKey, privateKey)); + } + public override KeySizes[] LegalKeySizes { get @@ -258,7 +266,41 @@ private void SetKey(SecKeyPair newKeyPair) } } } +#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS + } +#else + internal static class KeySizeHelpers + { + + public static bool IsLegalSize(this int size, KeySizes[] legalSizes) + { + for (int i = 0; i < legalSizes.Length; i++) + { + KeySizes currentSizes = legalSizes[i]; + + // If a cipher has only one valid key size, MinSize == MaxSize and SkipSize will be 0 + if (currentSizes.SkipSize == 0) + { + if (currentSizes.MinSize == size) + return true; + } + else if (size >= currentSizes.MinSize && size <= currentSizes.MaxSize) + { + // If the number is in range, check to see if it's a legal increment above MinSize + int delta = size - currentSizes.MinSize; + + // While it would be unusual to see KeySizes { 10, 20, 5 } and { 11, 14, 1 }, it could happen. + // So don't return false just because this one doesn't match. + if (delta % currentSizes.SkipSize == 0) + { + return true; + } + } + } + return false; + } } +#endif internal static class DsaKeyBlobHelpers { diff --git a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain.cpp b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain.cpp index 99d332c73c04..9a4eed3304d0 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain.cpp +++ b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain.cpp @@ -150,3 +150,132 @@ extern "C" int32_t AppleCryptoNative_SecKeychainEnumerateIdentities(SecKeychainR { return EnumerateKeychain(keychain, kSecClassIdentity, pIdentitiesOut, pOSStatus); } + +static OSStatus DeleteInKeychain(CFTypeRef needle, SecKeychainRef haystack) +{ + CFMutableDictionaryRef query = CFDictionaryCreateMutable( + kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + if (query == nullptr) + return errSecAllocate; + + CFArrayRef searchList = CFArrayCreate( + nullptr, const_cast(reinterpret_cast(&haystack)), 1, &kCFTypeArrayCallBacks); + + if (searchList == nullptr) + { + CFRelease(query); + return errSecAllocate; + } + + CFArrayRef itemMatch = CFArrayCreate(nullptr, reinterpret_cast(&needle), 1, &kCFTypeArrayCallBacks); + + if (itemMatch == nullptr) + { + CFRelease(searchList); + CFRelease(query); + return errSecAllocate; + } + + CFDictionarySetValue(query, kSecReturnRef, kCFBooleanTrue); + CFDictionarySetValue(query, kSecMatchSearchList, searchList); + CFDictionarySetValue(query, kSecMatchItemList, itemMatch); + CFDictionarySetValue(query, kSecClass, kSecClassIdentity); + + OSStatus status = SecItemDelete(query); + + if (status == errSecItemNotFound) + { + status = noErr; + } + + if (status == noErr) + { + CFDictionarySetValue(query, kSecClass, kSecClassCertificate); + status = SecItemDelete(query); + } + + if (status == errSecItemNotFound) + { + status = noErr; + } + + CFRelease(itemMatch); + CFRelease(searchList); + CFRelease(query); + + return status; +} + +extern "C" int32_t +AppleCryptoNative_X509StoreRemoveCertificate(CFTypeRef certOrIdentity, SecKeychainRef keychain, int32_t* pOSStatus) +{ + if (certOrIdentity == nullptr || keychain == nullptr) + return -1; + + SecCertificateRef cert = nullptr; + SecIdentityRef identity = nullptr; + + auto inputType = CFGetTypeID(certOrIdentity); + OSStatus status = noErr; + + if (inputType == SecCertificateGetTypeID()) + { + cert = reinterpret_cast(const_cast(certOrIdentity)); + CFRetain(cert); + } + else if (inputType == SecIdentityGetTypeID()) + { + identity = reinterpret_cast(const_cast(certOrIdentity)); + status = SecIdentityCopyCertificate(identity, &cert); + + if (status != noErr) + { + *pOSStatus = status; + return 0; + } + } + else + { + return -1; + } + + const int32_t kErrorUserTrust = 2; + const int32_t kErrorAdminTrust = 3; + + CFArrayRef settings = nullptr; + + if (status == noErr) + { + status = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainUser, &settings); + } + + if (settings != nullptr) + { + CFRelease(settings); + settings = nullptr; + } + + if (status == noErr) + { + CFRelease(cert); + return kErrorUserTrust; + } + + status = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainAdmin, &settings); + + if (settings != nullptr) + { + CFRelease(settings); + settings = nullptr; + } + + if (status == noErr) + { + CFRelease(cert); + return kErrorAdminTrust; + } + + *pOSStatus = DeleteInKeychain(cert, keychain); + return *pOSStatus == noErr; +} diff --git a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain.h b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain.h index e3a96e2803a8..0ee62c356c22 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain.h +++ b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keychain.h @@ -89,3 +89,19 @@ pOSStatus: Receives the last OSStatus value. extern "C" int32_t AppleCryptoNative_SecKeychainEnumerateIdentities(SecKeychainRef keychain, CFArrayRef* pIdentitiesOut, int32_t* pOSStatus); + +/* +Remove a certificate from the specified keychain. + +Returns +0 on failure -> see OSStatus +1 on success (including no item to delete), +2 on blocking user trust modification, +3 on blocking system trust modification, +any other value is invalid + +Output: +pOSStatus: Receives the last OSStatus value.. +*/ +extern "C" int32_t +AppleCryptoNative_X509StoreRemoveCertificate(CFTypeRef certOrIdentity, SecKeychainRef keychain, int32_t* pOSStatus); diff --git a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_trust.cpp b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_trust.cpp index 2716c0c759c2..e100eac40610 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_trust.cpp +++ b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_trust.cpp @@ -188,3 +188,31 @@ extern "C" int32_t AppleCryptoNative_StoreEnumerateMachineRoot(CFArrayRef* pCert return ret; } + +extern "C" int32_t AppleCryptoNative_StoreEnumerateUserDisallowed(CFArrayRef* pCertsOut, int32_t* pOSStatusOut) +{ + if (pCertsOut != nullptr) + *pCertsOut = nullptr; + + return EnumerateTrust(kSecTrustSettingsDomainUser, + kSecTrustSettingsResultDeny, + const_cast(pCertsOut), + pOSStatusOut); +} + +extern "C" int32_t AppleCryptoNative_StoreEnumerateMachineDisallowed(CFArrayRef* pCertsOut, int32_t* pOSStatusOut) +{ + if (pCertsOut != nullptr) + *pCertsOut = nullptr; + + CFMutableArrayRef* pCertsRef = const_cast(pCertsOut); + + int32_t ret = EnumerateTrust(kSecTrustSettingsDomainAdmin, kSecTrustSettingsResultDeny, pCertsRef, pOSStatusOut); + + if (ret == 1) + { + ret = EnumerateTrust(kSecTrustSettingsDomainSystem, kSecTrustSettingsResultDeny, pCertsRef, pOSStatusOut); + } + + return ret; +} diff --git a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_trust.h b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_trust.h index f68df2e3c65c..3ffb8f586c7e 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_trust.h +++ b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_trust.h @@ -34,3 +34,30 @@ pCertsOut: When the return value is not 1, NULL. Otherwise NULL on "no certs fou pOSStatus: Receives the last OSStatus value. */ extern "C" int32_t AppleCryptoNative_StoreEnumerateMachineRoot(CFArrayRef* pCertsOut, int32_t* pOSStatusOut); + +/* +Enumerate the certificates which are disallowed by the user. + +Returns 1 on success (including "no certs found"), 0 on failure, any other value for invalid state. + +Output: +pCertsOut: When the return value is not 1, NULL. Otherwise NULL on "no certs found", or a CFArrayRef for the matches +(including a single match). +pOSStatus: Receives the last OSStatus value. +*/ +extern "C" int32_t AppleCryptoNative_StoreEnumerateUserRoot(CFArrayRef* pCertsOut, int32_t* pOSStatusOut); + +/* +Enumerate the certificates which are disallowed by the machine ("admin" and "system" domains). + +Returns 1 on success (including "no certs found"), 0 on failure, any other value for invalid state. + +Duplicate certificates may be reported by this function, if they are disallowed at both the admin +and system levels. De-duplication is the responsibility of the caller. + +Output: +pCertsOut: When the return value is not 1, NULL. Otherwise NULL on "no certs found", or a CFArrayRef for the matches +(including a single match). +pOSStatus: Receives the last OSStatus value. +*/ +extern "C" int32_t AppleCryptoNative_StoreEnumerateMachineRoot(CFArrayRef* pCertsOut, int32_t* pOSStatusOut); diff --git a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.cpp b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.cpp index feba98eb93c8..31942b193da7 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.cpp +++ b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.cpp @@ -227,7 +227,8 @@ static int32_t ReadX509(uint8_t* pbData, int32_t cbData, PAL_X509ContentType contentType, CFStringRef cfPfxPassphrase, - SecKeychainRef tmpKeychain, + SecKeychainRef keychain, + bool exportable, SecCertificateRef* pCertOut, SecIdentityRef* pIdentityOut, CFArrayRef* pCollectionOut, @@ -242,6 +243,7 @@ static int32_t ReadX509(uint8_t* pbData, SecExternalItemType itemType; int32_t ret = 0; CFArrayRef outItems = nullptr; + CFMutableArrayRef keyAttributes = nullptr; SecKeychainRef importKeychain = nullptr; SecItemImportExportKeyParameters importParams = {}; @@ -263,12 +265,40 @@ static int32_t ReadX509(uint8_t* pbData, itemType = kSecItemTypeAggregate; importParams.passphrase = cfPfxPassphrase; - importKeychain = tmpKeychain; + importKeychain = keychain; - if (tmpKeychain == nullptr) + if (keychain == nullptr) { return kErrorBadInput; } + + // if keyAttributes is nullptr then it uses SENSITIVE | EXTRACTABLE + // so if !exportable was requested, assert SENSITIVE. + if (!exportable) + { + keyAttributes = CFArrayCreateMutable(nullptr, 9, &kCFTypeArrayCallBacks); + + if (keyAttributes == nullptr) + { + *pOSStatus = errSecAllocate; + return 0; + } + + int32_t sensitiveValue = CSSM_KEYATTR_SENSITIVE; + CFNumberRef sensitive = CFNumberCreate(nullptr, kCFNumberSInt32Type, &sensitiveValue); + + if (sensitive == nullptr) + { + CFRelease(keyAttributes); + *pOSStatus = errSecAllocate; + return 0; + } + + CFArrayAppendValue(keyAttributes, sensitive); + CFRelease(sensitive); + + importParams.keyAttributes = keyAttributes; + } } else { @@ -280,10 +310,13 @@ static int32_t ReadX509(uint8_t* pbData, if (cfData == nullptr) { - return kErrorUnknownState; + *pOSStatus = errSecAllocate; } - *pOSStatus = SecItemImport(cfData, nullptr, &dataFormat, &itemType, 0, &importParams, tmpKeychain, &outItems); + if (*pOSStatus == noErr) + { + *pOSStatus = SecItemImport(cfData, nullptr, &dataFormat, &itemType, 0, &importParams, keychain, &outItems); + } if (contentType == PAL_Pkcs12 && *pOSStatus == errSecPassphraseRequired && cfPfxPassphrase == nullptr) { @@ -296,7 +329,7 @@ static int32_t ReadX509(uint8_t* pbData, // Try again with the empty string passphrase. importParams.passphrase = CFSTR(""); - *pOSStatus = SecItemImport(cfData, nullptr, &dataFormat, &itemType, 0, &importParams, tmpKeychain, &outItems); + *pOSStatus = SecItemImport(cfData, nullptr, &dataFormat, &itemType, 0, &importParams, keychain, &outItems); CFRelease(importParams.passphrase); importParams.passphrase = nullptr; @@ -315,13 +348,10 @@ static int32_t ReadX509(uint8_t* pbData, ret = ProcessCertificateTypeReturn(outItems, pCertOut, pIdentityOut); } } - else + + if (keyAttributes != nullptr) { - if (outItems != nullptr) - { - CFRelease(outItems); - outItems = nullptr; - } + CFRelease(keyAttributes); } if (outItems != nullptr) @@ -339,7 +369,8 @@ extern "C" int32_t AppleCryptoNative_X509ImportCollection(uint8_t* pbData, int32_t cbData, PAL_X509ContentType contentType, CFStringRef cfPfxPassphrase, - SecKeychainRef tmpKeychain, + SecKeychainRef keychain, + int32_t exportable, CFArrayRef* pCollectionOut, int32_t* pOSStatus) { @@ -348,20 +379,30 @@ extern "C" int32_t AppleCryptoNative_X509ImportCollection(uint8_t* pbData, if (pOSStatus != nullptr) *pOSStatus = noErr; - if (pbData == nullptr || cbData < 0 || pCollectionOut == nullptr || pOSStatus == nullptr) + if (pbData == nullptr || cbData < 0 || pCollectionOut == nullptr || pOSStatus == nullptr || + exportable != !!exportable) { return kErrorBadInput; } - return ReadX509( - pbData, cbData, contentType, cfPfxPassphrase, tmpKeychain, nullptr, nullptr, pCollectionOut, pOSStatus); + return ReadX509(pbData, + cbData, + contentType, + cfPfxPassphrase, + keychain, + static_cast(exportable), + nullptr, + nullptr, + pCollectionOut, + pOSStatus); } extern "C" int32_t AppleCryptoNative_X509ImportCertificate(uint8_t* pbData, int32_t cbData, PAL_X509ContentType contentType, CFStringRef cfPfxPassphrase, - SecKeychainRef tmpKeychain, + SecKeychainRef keychain, + int32_t exportable, SecCertificateRef* pCertOut, SecIdentityRef* pIdentityOut, int32_t* pOSStatus) @@ -373,13 +414,22 @@ extern "C" int32_t AppleCryptoNative_X509ImportCertificate(uint8_t* pbData, if (pOSStatus != nullptr) *pOSStatus = noErr; - if (pbData == nullptr || cbData < 0 || pCertOut == nullptr || pIdentityOut == nullptr || pOSStatus == nullptr) + if (pbData == nullptr || cbData < 0 || pCertOut == nullptr || pIdentityOut == nullptr || pOSStatus == nullptr || + exportable != !!exportable) { return kErrorBadInput; } - return ReadX509( - pbData, cbData, contentType, cfPfxPassphrase, tmpKeychain, pCertOut, pIdentityOut, nullptr, pOSStatus); + return ReadX509(pbData, + cbData, + contentType, + cfPfxPassphrase, + keychain, + static_cast(exportable), + pCertOut, + pIdentityOut, + nullptr, + pOSStatus); } extern "C" int32_t AppleCryptoNative_X509ExportData(CFArrayRef data, diff --git a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.h b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.h index e672ffba8721..ecd30c395d67 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.h +++ b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.h @@ -93,7 +93,8 @@ extern "C" int32_t AppleCryptoNative_X509ImportCollection(uint8_t* pbData, int32_t cbData, PAL_X509ContentType contentType, CFStringRef cfPfxPassphrase, - SecKeychainRef tmpKeychain, + SecKeychainRef keychain, + int32_t exportable, CFArrayRef* pCollectionOut, int32_t* pOSStatus); @@ -122,7 +123,8 @@ extern "C" int32_t AppleCryptoNative_X509ImportCertificate(uint8_t* pbData, int32_t cbData, PAL_X509ContentType contentType, CFStringRef cfPfxPassphrase, - SecKeychainRef tmpKeychain, + SecKeychainRef keychain, + int32_t exportable, SecCertificateRef* pCertOut, SecIdentityRef* pIdentityOut, int32_t* pOSStatus); diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/CertificatePal.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/CertificatePal.cs index 8870d1c4b53a..f2a383a0525e 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/CertificatePal.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/CertificatePal.cs @@ -83,26 +83,41 @@ public static ICertificatePal FromBlob( throw new CryptographicException(SR.Cryptography_X509_PKCS7_NoSigner); } - SafeTemporaryKeychainHandle tmpKeychain; + bool exportable = true; + + SafeKeychainHandle keychain; if (contentType == X509ContentType.Pkcs12) { - tmpKeychain = Interop.AppleCrypto.CreateTemporaryKeychain(); + if ((keyStorageFlags & X509KeyStorageFlags.EphemeralKeySet) == X509KeyStorageFlags.EphemeralKeySet) + { + throw new PlatformNotSupportedException(SR.Cryptography_X509_NoEphemeralPfx); + } + + exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable; + + bool persist = + (keyStorageFlags & X509KeyStorageFlags.PersistKeySet) == X509KeyStorageFlags.PersistKeySet; + + keychain = persist + ? Interop.AppleCrypto.SecKeychainCopyDefault() + : Interop.AppleCrypto.CreateTemporaryKeychain(); } else { - tmpKeychain = SafeTemporaryKeychainHandle.InvalidHandle; + keychain = SafeTemporaryKeychainHandle.InvalidHandle; password = SafePasswordHandle.InvalidHandle; } - using (tmpKeychain) + using (keychain) { SafeSecIdentityHandle identityHandle; SafeSecCertificateHandle certHandle = Interop.AppleCrypto.X509ImportCertificate( rawData, contentType, password, - tmpKeychain, + keychain, + exportable, out identityHandle); if (identityHandle.IsInvalid) @@ -167,6 +182,7 @@ public void Dispose() } internal SafeSecCertificateHandle CertificateHandle => _certHandle; + internal SafeSecIdentityHandle IdentityHandle => _identityHandle; public bool HasPrivateKey => !(_identityHandle?.IsInvalid ?? true); @@ -375,7 +391,10 @@ public DSA GetDSAPrivateKey() return null; Debug.Assert(!_identityHandle.IsInvalid); - throw new NotImplementedException(); + SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.X509GetPublicKey(_certHandle); + SafeSecKeyRefHandle privateKey = Interop.AppleCrypto.X509GetPrivateKeyFromIdentity(_identityHandle); + + return new DSAImplementation.DSASecurityTransforms(publicKey, privateKey); } public ECDsa GetECDsaPrivateKey() diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.AppleKeychainStore.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.AppleKeychainStore.cs index 291eea57b9c8..980ddb0488f1 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.AppleKeychainStore.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.AppleKeychainStore.cs @@ -60,7 +60,10 @@ public void Add(ICertificatePal cert) if (_readonly) throw new CryptographicException(SR.Cryptography_X509_StoreReadOnly); - throw new NotImplementedException(); + AppleCertificatePal applePal = (AppleCertificatePal)cert; + + var handle = (SafeKeychainItemHandle)applePal.IdentityHandle ?? applePal.CertificateHandle; + Interop.AppleCrypto.X509StoreAddCertificate(handle, _keychainHandle); } public void Remove(ICertificatePal cert) @@ -68,7 +71,9 @@ public void Remove(ICertificatePal cert) if (_readonly) throw new CryptographicException(SR.Cryptography_X509_StoreReadOnly); - throw new NotImplementedException(); + AppleCertificatePal applePal = (AppleCertificatePal)cert; + + Interop.AppleCrypto.X509StoreRemoveCertificate(applePal.CertificateHandle, _keychainHandle); } public SafeHandle SafeHandle => _keychainHandle; diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.AppleTrustStore.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.AppleTrustStore.cs index fadd26ac6edc..b66896784011 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.AppleTrustStore.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.AppleTrustStore.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; @@ -15,10 +16,14 @@ internal sealed partial class StorePal { private sealed class AppleTrustStore : IStorePal { + private readonly StoreName _storeName; private readonly StoreLocation _location; - private AppleTrustStore(StoreLocation location) + private AppleTrustStore(StoreName storeName, StoreLocation location) { + Debug.Assert(storeName == StoreName.Root || storeName == StoreName.Disallowed); + + _storeName = storeName; _location = location; } @@ -31,9 +36,23 @@ public void CloneTo(X509Certificate2Collection collection) { HashSet dedupedCerts = new HashSet(); - using (SafeCFArrayHandle certs = Interop.AppleCrypto.StoreEnumerateRoot(_location)) + if (_storeName == StoreName.Root) + { + using (SafeCFArrayHandle certs = Interop.AppleCrypto.StoreEnumerateRoot(_location)) + { + ReadCollection(certs, dedupedCerts); + } + } + else if (_storeName == StoreName.Disallowed) + { + using (SafeCFArrayHandle certs = Interop.AppleCrypto.StoreEnumerateDisallowed(_location)) + { + ReadCollection(certs, dedupedCerts); + } + } + else { - ReadCollection(certs, dedupedCerts); + Debug.Fail($"No handler for trust store {_storeName}"); } foreach (X509Certificate2 cert in dedupedCerts) @@ -54,12 +73,12 @@ public void Remove(ICertificatePal cert) public SafeHandle SafeHandle => null; - internal static AppleTrustStore OpenStore(StoreLocation location, OpenFlags openFlags) + internal static AppleTrustStore OpenStore(StoreName storeName, StoreLocation location, OpenFlags openFlags) { if ((openFlags & OpenFlags.ReadWrite) == OpenFlags.ReadWrite) throw new CryptographicException(SR.Security_AccessDenied); - return new AppleTrustStore(location); + return new AppleTrustStore(storeName, location); } } } diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.LoaderPal.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.LoaderPal.cs index c3e5dce1c64a..0657ce677aa9 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.LoaderPal.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.LoaderPal.cs @@ -26,7 +26,7 @@ public AppleCertLoader(SafeCFArrayHandle collectionHandle, SafeTemporaryKeychain public void Dispose() { _collectionHandle.Dispose(); - _tmpKeychain.Dispose(); + _tmpKeychain?.Dispose(); } public void MoveTo(X509Certificate2Collection collection) diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.cs index f156e836c0d4..86ce9cd7b28f 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.cs @@ -32,27 +32,47 @@ public static ILoaderPal FromBlob(byte[] rawData, SafePasswordHandle password, X X509ContentType contentType = X509Certificate2.GetCertContentType(rawData); - SafeTemporaryKeychainHandle tmpKeychain; + SafeKeychainHandle keychain; + bool exportable = true; if (contentType == X509ContentType.Pkcs12) { - tmpKeychain = Interop.AppleCrypto.CreateTemporaryKeychain(); + if ((keyStorageFlags & X509KeyStorageFlags.EphemeralKeySet) == X509KeyStorageFlags.EphemeralKeySet) + { + throw new PlatformNotSupportedException(SR.Cryptography_X509_NoEphemeralPfx); + } + + exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable; + + bool persist = + (keyStorageFlags & X509KeyStorageFlags.PersistKeySet) == X509KeyStorageFlags.PersistKeySet; + + keychain = persist + ? Interop.AppleCrypto.SecKeychainCopyDefault() + : Interop.AppleCrypto.CreateTemporaryKeychain(); } else { - tmpKeychain = SafeTemporaryKeychainHandle.InvalidHandle; + keychain = SafeTemporaryKeychainHandle.InvalidHandle; password = SafePasswordHandle.InvalidHandle; } // Only dispose tmpKeychain on the exception path, otherwise it's managed by AppleCertLoader. try { - SafeCFArrayHandle certs = Interop.AppleCrypto.X509ImportCollection(rawData, contentType, password, tmpKeychain); - return new AppleCertLoader(certs, tmpKeychain); + SafeCFArrayHandle certs = Interop.AppleCrypto.X509ImportCollection( + rawData, + contentType, + password, + keychain, + exportable); + + // If the default keychain was used, null will be passed to the loader. + return new AppleCertLoader(certs, keychain as SafeTemporaryKeychainHandle); } catch { - tmpKeychain.Dispose(); + keychain.Dispose(); throw; } } @@ -85,15 +105,18 @@ public static IStorePal FromSystemStore(string storeName, StoreLocation storeLoc if (ordinalIgnoreCase.Equals("My", storeName)) return AppleKeychainStore.OpenDefaultKeychain(openFlags); if (ordinalIgnoreCase.Equals("Root", storeName)) - return AppleTrustStore.OpenStore(storeLocation, openFlags); + return AppleTrustStore.OpenStore(StoreName.Root, storeLocation, openFlags); + if (ordinalIgnoreCase.Equals("Disallowed", storeName)) + return AppleTrustStore.OpenStore(StoreName.Disallowed, storeLocation, openFlags); break; case StoreLocation.LocalMachine: if (ordinalIgnoreCase.Equals("My", storeName)) return AppleKeychainStore.OpenSystemSharedKeychain(openFlags); if (ordinalIgnoreCase.Equals("Root", storeName)) - return AppleTrustStore.OpenStore(storeLocation, openFlags); - + return AppleTrustStore.OpenStore(StoreName.Root, storeLocation, openFlags); + if (ordinalIgnoreCase.Equals("Disallowed", storeName)) + return AppleTrustStore.OpenStore(StoreName.Disallowed, storeLocation, openFlags); break; } diff --git a/src/System.Security.Cryptography.X509Certificates/src/Resources/Strings.resx b/src/System.Security.Cryptography.X509Certificates/src/Resources/Strings.resx index 4da3b51f81c2..4134b0af69b4 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Resources/Strings.resx +++ b/src/System.Security.Cryptography.X509Certificates/src/Resources/Strings.resx @@ -303,4 +303,40 @@ The string contains a character not in the 7 bit ASCII character set. + + This platform does not support loading with EphemeralKeySet. Remove the flag to allow keys to be temporarily created on disk. + + + 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. + + + Specified key is not a valid size for this algorithm. + + + DSA keys can be imported, but new key generation is not supported on this platform. + + + The specified DSA parameters are not valid; P, Q, G and Y are all required. + + + The specified DSA parameters are not valid; P, G and Y must be the same length (the key size). + + + The specified DSA parameters are not valid; Q and X (if present) must be the same length. + + + The specified DSA parameters are not valid; J (if present) must be shorter than P. + + + The specified DSA parameters are not valid; Seed, if present, must be 20 bytes long for keys shorter than 1024 bits. + + + The specified DSA parameters are not valid; Q must be 20 bytes long for keys shorter than 1024 bits. + + + The specified DSA parameters are not valid; Q's length must be one of 20, 32 or 64 bytes. + diff --git a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj index 173efdf76448..8b9df58097fb 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj +++ b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj @@ -341,9 +341,15 @@ Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.X509Chain.cs + + Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.X509Store.cs + Common\Microsoft\Win32\SafeHandles\SafeCreateHandle.OSX.cs + + Common\System\Security\Cryptography\DSASecurityTransforms.cs + Common\System\Security\Cryptography\ECDsaSecurityTransforms.cs diff --git a/src/System.Security.Cryptography.X509Certificates/tests/Cert.cs b/src/System.Security.Cryptography.X509Certificates/tests/Cert.cs index 9a7095b55422..5493086834de 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/Cert.cs +++ b/src/System.Security.Cryptography.X509Certificates/tests/Cert.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Runtime.InteropServices; + namespace System.Security.Cryptography.X509Certificates.Tests { // @@ -11,12 +13,14 @@ namespace System.Security.Cryptography.X509Certificates.Tests // internal static class Cert { - internal const X509KeyStorageFlags EphemeralIfPossible = + // netstandard: DefaultKeySet + // netcoreapp-OSX: DefaultKeySet + // netcoreapp-other: EphemeralKeySet + internal static readonly X509KeyStorageFlags EphemeralIfPossible = #if netcoreapp11 - X509KeyStorageFlags.EphemeralKeySet; -#else - X509KeyStorageFlags.DefaultKeySet; + !RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? X509KeyStorageFlags.EphemeralKeySet : #endif + X509KeyStorageFlags.DefaultKeySet; // // The Import() methods have an overload for each X509Certificate2Collection.Import() overload. // diff --git a/src/System.Security.Cryptography.X509Certificates/tests/CollectionImportTests.cs b/src/System.Security.Cryptography.X509Certificates/tests/CollectionImportTests.cs index c02a90a5defb..c8fc99e07c30 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/CollectionImportTests.cs +++ b/src/System.Security.Cryptography.X509Certificates/tests/CollectionImportTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using Xunit; namespace System.Security.Cryptography.X509Certificates.Tests @@ -309,6 +310,21 @@ public static void ImportPkcs12File_Chain_VerifyContents(X509KeyStorageFlags key } } +#if netcoreapp11 + [Fact] + [PlatformSpecific(TestPlatforms.OSX)] + public static void EphemeralKeySet_OSX() + { + // EphemeralKeySet fails when loading a PFX, and is ignored otherwise. + using (ImportedCollection coll = Cert.Import(TestData.Pkcs7ChainDerBytes, null, X509KeyStorageFlags.EphemeralKeySet)) + { + Assert.Equal(3, coll.Collection.Count); + } + + Assert.Throws( + () => new X509Certificate2(TestData.EmptyPfx, string.Empty, X509KeyStorageFlags.EphemeralKeySet)); + } +#endif [Fact] public static void InvalidStorageFlags() @@ -355,7 +371,8 @@ public static IEnumerable StorageFlags yield return new object[] { X509KeyStorageFlags.DefaultKeySet }; #if netcoreapp11 - yield return new object[] { X509KeyStorageFlags.EphemeralKeySet }; + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + yield return new object[] { X509KeyStorageFlags.EphemeralKeySet }; #endif } } diff --git a/src/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs b/src/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs index 01e6ed89777d..34a13d0385dd 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs +++ b/src/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs @@ -735,7 +735,7 @@ public static void ExportEmpty_Pkcs12() } [Fact] - [ActiveIssue(-1, TestPlatforms.OSX)] + [ActiveIssue(16705, TestPlatforms.OSX)] public static void ExportUnrelatedPfx() { // Export multiple certificates which are not part of any kind of certificate chain. @@ -1485,17 +1485,7 @@ private static void TestExportStore(X509ContentType ct) } } - public static IEnumerable StorageFlags - { - get - { - yield return new object[] { X509KeyStorageFlags.DefaultKeySet }; - -#if netcoreapp11 - yield return new object[] { X509KeyStorageFlags.EphemeralKeySet }; -#endif - } - } + public static IEnumerable StorageFlags => CollectionImportTests.StorageFlags; private static X509Certificate2[] ToArray(this X509Certificate2Collection col) { diff --git a/src/System.Security.Cryptography.X509Certificates/tests/ExportTests.cs b/src/System.Security.Cryptography.X509Certificates/tests/ExportTests.cs index 02fef4049a74..a4bebe1de373 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/ExportTests.cs +++ b/src/System.Security.Cryptography.X509Certificates/tests/ExportTests.cs @@ -48,7 +48,7 @@ public static void ExportAsSerializedCert_Unix() } [Fact] - [ActiveIssue(-1, TestPlatforms.OSX)] + [ActiveIssue(16705, TestPlatforms.OSX)] public static void ExportAsPfx() { using (X509Certificate2 c1 = new X509Certificate2(TestData.MsCertificate)) @@ -65,7 +65,7 @@ public static void ExportAsPfx() } [Fact] - [ActiveIssue(-1, TestPlatforms.OSX)] + [ActiveIssue(16705, TestPlatforms.OSX)] public static void ExportAsPfxWithPassword() { const string password = "Cotton"; @@ -84,7 +84,7 @@ public static void ExportAsPfxWithPassword() } [Fact] - [ActiveIssue(-1, TestPlatforms.OSX)] + [ActiveIssue(16705, TestPlatforms.OSX)] public static void ExportAsPfxVerifyPassword() { const string password = "Cotton"; diff --git a/src/System.Security.Cryptography.X509Certificates/tests/PfxTests.cs b/src/System.Security.Cryptography.X509Certificates/tests/PfxTests.cs index 591e9db62500..729fee4cd799 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/PfxTests.cs +++ b/src/System.Security.Cryptography.X509Certificates/tests/PfxTests.cs @@ -124,7 +124,7 @@ public static void TestPrivateKey(X509KeyStorageFlags keyStorageFlags) [Fact] public static void TestPrivateKeyProperty() { - using (var c = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.EphemeralKeySet)) + using (var c = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, Cert.EphemeralIfPossible)) { bool hasPrivateKey = c.HasPrivateKey; Assert.True(hasPrivateKey); @@ -184,7 +184,7 @@ public static void ReadECDsaPrivateKey_WindowsPfx(X509KeyStorageFlags keyStorage [Fact] public static void ECDsaPrivateKeyProperty_WindowsPfx() { - using (var cert = new X509Certificate2(TestData.ECDsaP256_DigitalSignature_Pfx_Windows, "Test", X509KeyStorageFlags.EphemeralKeySet)) + using (var cert = new X509Certificate2(TestData.ECDsaP256_DigitalSignature_Pfx_Windows, "Test", Cert.EphemeralIfPossible)) { AsymmetricAlgorithm alg = cert.PrivateKey; Assert.NotNull(alg); @@ -197,6 +197,28 @@ public static void ECDsaPrivateKeyProperty_WindowsPfx() Assert.Throws(() => cert.PrivateKey = alg); } } + + [Fact] + public static void DsaPrivateKeyProperty() + { + using (var cert = new X509Certificate2(TestData.Dsa1024Pfx, TestData.Dsa1024PfxPassword, Cert.EphemeralIfPossible)) + { + AsymmetricAlgorithm alg = cert.PrivateKey; + Assert.NotNull(alg); + Assert.Same(alg, cert.PrivateKey); + Assert.IsAssignableFrom(alg); + + DSA dsa = (DSA)alg; + byte[] data = { 1, 2, 3, 4, 5 }; + byte[] sig = dsa.SignData(data, HashAlgorithmName.SHA1); + + Assert.True(dsa.VerifyData(data, sig, HashAlgorithmName.SHA1), "Key verifies signature"); + + data[0] ^= 0xFF; + + Assert.False(dsa.VerifyData(data, sig, HashAlgorithmName.SHA1), "Key verifies tampered data signature"); + } + } #endif private static void Verify_ECDsaPrivateKey_WindowsPfx(ECDsa ecdsa) @@ -373,17 +395,7 @@ private static void AssertEccAlgorithm(ECDsa ecdsa, string algorithmId) } } - public static IEnumerable StorageFlags - { - get - { - yield return new object[] { X509KeyStorageFlags.DefaultKeySet }; - -#if netcoreapp11 - yield return new object[] { X509KeyStorageFlags.EphemeralKeySet }; -#endif - } - } + public static IEnumerable StorageFlags => CollectionImportTests.StorageFlags; private static X509Certificate2 Rewrap(this X509Certificate2 c) { diff --git a/src/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj b/src/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj index b17d6bf08d53..c189fea0beab 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj +++ b/src/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj @@ -36,6 +36,7 @@ + CommonTest\System\Security\Cryptography\ByteUtils.cs diff --git a/src/System.Security.Cryptography.X509Certificates/tests/TestData.cs b/src/System.Security.Cryptography.X509Certificates/tests/TestData.cs index 6aeecd7900f0..24d457e982be 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/TestData.cs +++ b/src/System.Security.Cryptography.X509Certificates/tests/TestData.cs @@ -347,6 +347,97 @@ public static SecureString CreatePfxDataPasswordSecureString() "0ef9848b7f83eacbf83ffd021420e2ac47e656874633e01b0d207a99280c1127" + "01").HexToByteArray(); + public static byte[] Dsa1024Pfx = ( + "308206EE020103308206B406092A864886F70D010701A08206A5048206A13082" + + "069D3082043706092A864886F70D010706A0820428308204240201003082041D" + + "06092A864886F70D010701301C060A2A864886F70D010C0106300E04084AF212" + + "89D5D7E2E702020800808203F0DECCF218AC91F26BAB026998AB77C7629D20DB" + + "E2FB7022A3C4A1CECD743C0F932E944AE229DAFB61AD76C4DEB6995DF4F4BA01" + + "2DBAD5C63A4C846E0807FCA0BC4A162CDFBAB4B3C4D304F473B3ACC1D268436E" + + "F537DAE97ECC3C634C8DF2A294CC23E904A169F369021A0C024A03DE98A65B0F" + + "3F14D6910525D76AD98B91E67BB7398E245CF48A4D2A5603CFCCF4E547D7EDAB" + + "669D9A8597C6839119EB9FD932D1E4BA8B45D3317186CDA2EFF247BCFD64A5CA" + + "ED604BF7033E423CC21CEC6454FE3B74E03A26C51A1C3519CE339FBE9F10B81D" + + "DF6A0AAB4F8166D90B6F52B3439AB4B5273D0A506E3E01869F8FEBD1521EF8E5" + + "BFB357FA630E3C988926EF3ACC0A0F4176FE8A93337C1A5C6DEAB5758EC2F07C" + + "11E8B2495ECDE58D12312CCCA2E8B2EE8564B533D18C7A26A9290394C2A9942C" + + "295EBB0317F5695103627519567960908323FFE6560AD054C97800218A52F37A" + + "DDE4E7F18EF3BF3718A9D7BF57B700DBEB5AB86598C9604A4546995E34DBABBB" + + "6A9FB483A3C2DFE6046DFD54F2D7AC61C062AF04B7FBAC395C5DD19408D6926A" + + "93B896BFB92DA6F7F5A4E54EDBE2CFBB56576878150676ADB0D37E0177B91E0D" + + "F09D7B37769E66842DD40C7B1422127F152A165BC9669168885BA0243C9641B4" + + "48F68575AA6AB9247A49A61AC3C683EE057B7676B9610CF9100096FC46BDC8B9" + + "BAA03535815D5E98BA3ABC1E18E39B50A8AF8D81E30F2DFD6AF5D0F9FC3636AB" + + "69E128C793571723A79E42FC7C1BD7F39BD45FBE9C39EEB010005435BEC19844" + + "2058033D2601B83124BD369DADB831317E0B2C28CE7535A2E89D8A0E5E34E252" + + "3B0FCEC34FF26A2B80566F4D86F958F70106BF3322FA70A3312E48EAA130246A" + + "07412E93FDE91F633F758BC49311F6CBBAEC5D2F22AFCD696F72BC22E7DE6C00" + + "3275DFEC47E3848226FE9DBA184EA711E051B267C584749F897EFE7EAFD02F1D" + + "BF3FD8E882474CA1F45509EF2E7B82F35B677CB88ED42AF729848EE2B424B0CE" + + "2E9AAC945BABA550C20D5B25075A30FE70D8CAA5A527A35F1DF17BCCB91930C1" + + "7120C625667120E0806C2B51EDFF540F928BD555FB48DBCB83CCCE0C385E78C8" + + "65BE715AE6F8BE472E5FC187EBE3FEFD8D7FE62D4DB2EE61F42D24D81FAA9179" + + "0FB17E8EBC8E219B6F9E039F5AB3BC4870821D474B36C8F8D0583D9DC06E4383" + + "D03424420B8C8B26276877166A0F51E22F0D8FA60A070CFBD47EAFBC717C879C" + + "B5A1EA69C4C2A38F26A1EEF96A0C32BFCECCE4EA97E90A425066B1DD0891353F" + + "766EB9F2BFA2563A815DAF3639EBB147E1E8757A6BFAB902C4A8F037AD47E03F" + + "AF2E019FCF6CA7430BDFEA4B45B28ED746BB90E09BEF7B370A75E7924BBA0920" + + "25FE654A9A197A5B8BBBE43DC7C892FF14E75A37EB97FC489AB121A43E308202" + + "5E06092A864886F70D010701A082024F0482024B3082024730820243060B2A86" + + "4886F70D010C0A0102A082017630820172301C060A2A864886F70D010C010330" + + "0E0408ECB4D1550DA52C6302020800048201509322DC0193DD9E79ADAFD38827" + + "AD6DE9299327DDDF6E9DF4FB70D53A64951E4B814E90D2A19B3F4B8E39A2F851" + + "A3E5E9B9EB947DD248A3E5F5EB458F3323D4656709E97C6BD59238C4D1F26AB6" + + "7D73235FAE7780D98705957B6650AC0DE3E2D46E22455D0A105D138F16A84839" + + "14EDDF5C518B748558704ED3AE4A8C4914F667BBDE07978E4A4FC66194F6B86B" + + "AB9F558EDE890C25DFB97C59653906CC573B5DEB62165CFF8A5F4F8059A478EB" + + "F6FED75F1DACDC612C2E271E25A7083E15D33697270FD442D79FFCB25DB135F9" + + "8E580DC9CE14F73C3B847931AF821C77718455F595CA15B86386F3FCC5962262" + + "5FC916DDB4A08479DCB49FF7444333FA99FBB22F1AEC1876CF1E099F7A4ECA85" + + "A325A8623E071EEA9359194EEE712F73076C5EB72AA243D0C0978B934BC8596F" + + "8353FD3CA859EEA457C6175E82AE5854CC7B6598A1E980332F56AB1EE1208277" + + "4A91A63181B9302306092A864886F70D01091531160414E6335FA7097AB6DE4A" + + "1CDB0C678D7A929883FB6430819106092B06010401823711013181831E818000" + + "4D006900630072006F0073006F0066007400200045006E00680061006E006300" + + "650064002000440053005300200061006E006400200044006900660066006900" + + "65002D00480065006C006C006D0061006E002000430072007900700074006F00" + + "67007200610070006800690063002000500072006F0076006900640065007230" + + "313021300906052B0E03021A0500041466FD3518CEBBD69877BA663C9E8D7092" + + "8E8A98F30408DFB5AE610308BCF802020800").HexToByteArray(); + + public const string Dsa1024PfxPassword = "1234"; + + public static byte[] Dsa1024Cert = ( + "3082038D3082034AA003020102020900AB740A714AA83C92300B060960864801" + + "650304030230818D310B3009060355040613025553311330110603550408130A" + + "57617368696E67746F6E3110300E060355040713075265646D6F6E64311E301C" + + "060355040A13154D6963726F736F667420436F72706F726174696F6E3120301E" + + "060355040B13172E4E4554204672616D65776F726B2028436F72654658293115" + + "30130603550403130C313032342D62697420445341301E170D31353131323531" + + "34343030335A170D3135313232353134343030335A30818D310B300906035504" + + "0613025553311330110603550408130A57617368696E67746F6E3110300E0603" + + "55040713075265646D6F6E64311E301C060355040A13154D6963726F736F6674" + + "20436F72706F726174696F6E3120301E060355040B13172E4E4554204672616D" + + "65776F726B2028436F7265465829311530130603550403130C313032342D6269" + + "7420445341308201B73082012C06072A8648CE3804013082011F02818100AEE3" + + "309FC7C9DB750D4C3797D333B3B9B234B462868DB6FFBDED790B7FC8DDD574C2" + + "BD6F5E749622507AB2C09DF5EAAD84859FC0706A70BB8C9C8BE22B4890EF2325" + + "280E3A7F9A3CE341DBABEF6058D063EA6783478FF8B3B7A45E0CA3F7BAC9995D" + + "CFDDD56DF168E91349130F719A4E717351FAAD1A77EAC043611DC5CC5A7F0215" + + "00D23428A76743EA3B49C62EF0AA17314A85415F0902818100853F830BDAA738" + + "465300CFEE02418E6B07965658EAFDA7E338A2EB1531C0E0CA5EF1A12D9DDC7B" + + "550A5A205D1FF87F69500A4E4AF5759F3F6E7F0C48C55396B738164D9E35FB50" + + "6BD50E090F6A497C70E7E868C61BD4477C1D62922B3DBB40B688DE7C175447E2" + + "E826901A109FAD624F1481B276BF63A665D99C87CEE9FD063303818400028180" + + "25B8E7078E149BAC352667623620029F5E4A5D4126E336D56F1189F9FF71EA67" + + "1B844EBD351514F27B69685DDF716B32F102D60EA520D56F544D19B2F08F5D9B" + + "DDA3CBA3A73287E21E559E6A07586194AFAC4F6E721EDCE49DE0029627626D7B" + + "D30EEB337311DB4FF62D7608997B6CC32E9C42859820CA7EF399590D5A388C48" + + "A330302E302C0603551D110425302387047F0000018710000000000000000000" + + "0000000000000182096C6F63616C686F7374300B060960864801650304030203" + + "3000302D021500B9316CC7E05C9F79197E0B41F6FD4E3FCEB72A8A0214075505" + + "CCAECB18B7EF4C00F9C069FA3BC78014DE").HexToByteArray(); + public static byte[] CertWithPolicies = ( "308201f33082015ca0030201020210134fb7082cf69bbb4930bfc8e1ca446130" + "0d06092a864886f70d0101050500300e310c300a06035504031303466f6f301e" + diff --git a/src/System.Security.Cryptography.X509Certificates/tests/X509StoreMutableTests.OSX.cs b/src/System.Security.Cryptography.X509Certificates/tests/X509StoreMutableTests.OSX.cs new file mode 100644 index 000000000000..ba17176a812b --- /dev/null +++ b/src/System.Security.Cryptography.X509Certificates/tests/X509StoreMutableTests.OSX.cs @@ -0,0 +1,249 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace System.Security.Cryptography.X509Certificates.Tests +{ + [OuterLoop("Modifies system state")] + [PlatformSpecific(TestPlatforms.OSX)] + public static class X509StoreMutableTests_OSX + { + [Fact] + public static void PersistKeySet_OSX() + { + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.DefaultKeySet)) + { + store.Open(OpenFlags.ReadWrite); + + // Defensive removal. + store.Remove(cert); + + Assert.False(IsCertInStore(cert, store), "PtxData certificate was found on pre-condition"); + + // Opening this as persisted has now added it to login.keychain, aka CU\My. + using (var persistedCert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.PersistKeySet)) + { + Assert.True(IsCertInStore(cert, store), "PtxData certificate was found upon PersistKeySet import"); + } + + // And ensure it didn't get removed when the certificate got disposed. + Assert.True(IsCertInStore(cert, store), "PtxData certificate was found after PersistKeySet Dispose"); + + // Cleanup. + store.Remove(cert); + } + } + + [Fact] + public static void AddToStore_NonExportable_OSX() + { + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.DefaultKeySet)) + { + store.Open(OpenFlags.ReadWrite); + + int countBefore = GetStoreCertificateCount(store); + + // Because this has to export the key from the temporary keychain to the permanent one, + // a non-exportable PFX load will fail. + Assert.ThrowsAny(() => store.Add(cert)); + + int countAfter = GetStoreCertificateCount(store); + + Assert.Equal(countBefore, countAfter); + } + } + + [Fact] + public static void AddToStore_Exportable() + { + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable)) + using (var certOnly = new X509Certificate2(cert.RawData)) + { + store.Open(OpenFlags.ReadWrite); + + // Defensive removal. + store.Remove(certOnly); + Assert.False(IsCertInStore(cert, store), "PtxData certificate was found on pre-condition"); + + store.Add(cert); + Assert.True(IsCertInStore(certOnly, store), "PtxData certificate was found after add"); + + // Cleanup + store.Remove(certOnly); + } + } + + [Fact] + public static void AddToStoreTwice() + { + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable)) + using (var certOnly = new X509Certificate2(cert.RawData)) + { + store.Open(OpenFlags.ReadWrite); + + // Defensive removal. + store.Remove(certOnly); + Assert.False(IsCertInStore(cert, store), "PtxData certificate was found on pre-condition"); + + store.Add(cert); + Assert.True(IsCertInStore(certOnly, store), "PtxData certificate was found after add"); + + // No exception for duplicate item. + store.Add(cert); + + // Cleanup + store.Remove(certOnly); + } + } + + [Fact] + public static void AddPrivateAfterPublic() + { + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable)) + using (var certOnly = new X509Certificate2(cert.RawData)) + { + store.Open(OpenFlags.ReadWrite); + + // Defensive removal. + store.Remove(certOnly); + Assert.False(IsCertInStore(cert, store), "PtxData certificate was found on pre-condition"); + + store.Add(certOnly); + Assert.True(IsCertInStore(certOnly, store), "PtxData certificate was found after add"); + Assert.False(StoreHasPrivateKey(store, certOnly), "Store has a private key for PfxData after public-only add"); + + // Add the private key + store.Add(cert); + Assert.True(StoreHasPrivateKey(store, certOnly), "Store has a private key for PfxData after PFX add"); + + // Cleanup + store.Remove(certOnly); + } + } + + [Fact] + public static void AddPublicAfterPrivate() + { + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable)) + using (var certOnly = new X509Certificate2(cert.RawData)) + { + store.Open(OpenFlags.ReadWrite); + + // Defensive removal. + store.Remove(certOnly); + Assert.False(IsCertInStore(cert, store), "PtxData certificate was found on pre-condition"); + + // Add the private key + store.Add(cert); + Assert.True(IsCertInStore(certOnly, store), "PtxData certificate was found after add"); + Assert.True(StoreHasPrivateKey(store, certOnly), "Store has a private key for PfxData after PFX add"); + + // Add the public key with no private key + store.Add(certOnly); + Assert.True(StoreHasPrivateKey(store, certOnly), "Store has a private key for PfxData after public-only add"); + + // Cleanup + store.Remove(certOnly); + } + } + + [Fact] + public static void VerifyRemove() + { + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable)) + { + store.Open(OpenFlags.ReadWrite); + + // Defensive removal. Sort of circular, but it's the best we can do. + store.Remove(cert); + Assert.False(IsCertInStore(cert, store), "PtxData certificate was found on pre-condition"); + + store.Add(cert); + Assert.True(IsCertInStore(cert, store), "PtxData certificate was found after add"); + + store.Remove(cert); + Assert.False(IsCertInStore(cert, store), "PtxData certificate was found after remove"); + } + } + + [Fact] + public static void RemovePublicDeletesPrivateKey() + { + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable)) + using (var certOnly = new X509Certificate2(cert.RawData)) + { + store.Open(OpenFlags.ReadWrite); + + // Defensive removal. + store.Remove(cert); + Assert.False(IsCertInStore(cert, store), "PtxData certificate was found on pre-condition"); + + // Add the private key + store.Add(cert); + Assert.True(IsCertInStore(cert, store), "PtxData certificate was found after add"); + + store.Remove(certOnly); + Assert.False(IsCertInStore(cert, store), "PtxData certificate was found after remove"); + + // Add back the public key only + store.Add(certOnly); + Assert.True(IsCertInStore(cert, store), "PtxData certificate was found after public-only add"); + Assert.False(StoreHasPrivateKey(store, cert), "Store has a private key for cert after public-only add"); + + // Cleanup + store.Remove(certOnly); + } + } + + private static bool StoreHasPrivateKey(X509Store store, X509Certificate2 forCert) + { + using (ImportedCollection coll = new ImportedCollection(store.Certificates)) + { + foreach (X509Certificate2 storeCert in coll.Collection) + { + if (forCert.Equals(storeCert)) + { + return storeCert.HasPrivateKey; + } + } + } + + Assert.True(false, $"Certificate ({forCert.Subject}) exists in the store"); + return false; + } + + private static bool IsCertInStore(X509Certificate2 cert, X509Store store) + { + using (ImportedCollection coll = new ImportedCollection(store.Certificates)) + { + foreach (X509Certificate2 storeCert in coll.Collection) + { + if (cert.Equals(storeCert)) + { + return true; + } + } + } + + return false; + } + + private static int GetStoreCertificateCount(X509Store store) + { + using (var coll = new ImportedCollection(store.Certificates)) + { + return coll.Collection.Count; + } + } + } +} diff --git a/src/System.Security.Cryptography.X509Certificates/tests/X509StoreTests.cs b/src/System.Security.Cryptography.X509Certificates/tests/X509StoreTests.cs index 946d096964d5..1bf9cc395f05 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/X509StoreTests.cs +++ b/src/System.Security.Cryptography.X509Certificates/tests/X509StoreTests.cs @@ -80,7 +80,7 @@ public static void Constructor_StoreHandle_Unix() Assert.Throws(() => new X509Chain(IntPtr.Zero)); } - [PlatformSpecific(TestPlatforms.Windows)] + [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.OSX)] [Fact] public static void TestDispose() { @@ -114,7 +114,6 @@ public static void ReadMyCertificates() } [Fact] - [ActiveIssue(-1, TestPlatforms.OSX)] public static void OpenNotExistant() { using (X509Store store = new X509Store(Guid.NewGuid().ToString("N"), StoreLocation.CurrentUser)) @@ -124,7 +123,6 @@ public static void OpenNotExistant() } [Fact] - [ActiveIssue(-1, TestPlatforms.OSX)] public static void AddReadOnlyThrows() { using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) @@ -146,7 +144,6 @@ public static void AddReadOnlyThrows() } [Fact] - [ActiveIssue(-1, TestPlatforms.OSX)] public static void AddReadOnlyThrowsWhenCertificateExists() { using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) @@ -178,7 +175,6 @@ public static void AddReadOnlyThrowsWhenCertificateExists() } [Fact] - [ActiveIssue(-1, TestPlatforms.OSX)] public static void RemoveReadOnlyThrowsWhenFound() { // This test is unfortunate, in that it will mostly never test. @@ -276,11 +272,10 @@ public static void OpenMachineMyStore_NotSupported() } [Theory] - [PlatformSpecific(TestPlatforms.AnyUnix)] + [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.OSX)] [InlineData(OpenFlags.ReadOnly, false)] [InlineData(OpenFlags.MaxAllowed, false)] [InlineData(OpenFlags.ReadWrite, true)] - [ActiveIssue(-1, TestPlatforms.OSX)] public static void OpenMachineRootStore_Permissions(OpenFlags permissions, bool shouldThrow) { using (X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine)) @@ -320,5 +315,23 @@ public static void MachineRootStore_NonEmpty() } } } + + [Theory] + [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.OSX)] + [InlineData(StoreLocation.CurrentUser)] + [InlineData(StoreLocation.LocalMachine)] + public static void EnumerateDisallowedStore(StoreLocation location) + { + using (X509Store store = new X509Store(StoreName.Disallowed, location)) + { + store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + + using (var storeCerts = new ImportedCollection(store.Certificates)) + { + // That's all. We enumerated it. + // There might not even be data in it. + } + } + } } }