From d68afd6d7f71480428eb3e8208d2f8b7c227afe3 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Thu, 22 Apr 2021 11:28:48 +0200 Subject: [PATCH 1/5] [iOS] Implement DSA, RSA, EC key import/export --- .../AsymmetricAlgorithmHelpers.Ansi.cs | 67 +++ .../Interop.Keychain.cs | 8 +- .../Interop.SecKeyRef.cs | 63 +-- .../Interop.SecKeyRef.iOS.cs | 87 ++++ ...f.Export.cs => Interop.SecKeyRef.macOS.cs} | 47 ++ .../Cryptography/DSASecurityTransforms.cs | 465 +++++------------- .../Cryptography/DSASecurityTransforms.iOS.cs | 25 + .../DSASecurityTransforms.macOS.cs | 218 ++++++++ .../ECDiffieHellmanSecurityTransforms.cs | 7 - ...ECDiffieHellmanSecurityTransforms.macOS.cs | 21 + .../Cryptography/EccSecurityTransforms.cs | 174 +------ .../Cryptography/EccSecurityTransforms.iOS.cs | 130 +++++ .../EccSecurityTransforms.macOS.cs | 186 +++++++ .../Cryptography/RSAKeyFormatHelper.cs | 20 + .../Cryptography/RSASecurityTransforms.cs | 193 -------- .../Cryptography/RSASecurityTransforms.iOS.cs | 166 +++++++ .../RSASecurityTransforms.macOS.cs | 215 ++++++++ .../DSA/DSAImportExport.cs | 2 +- .../DSA/DSAKeyFileTests.cs | 2 +- .../DSA/DSAKeyGeneration.cs | 2 +- .../DSA/DSAKeyPemTests.cs | 2 +- .../DSA/DSASignVerify.cs | 8 +- .../DSA/DSASignatureFormatTests.cs | 2 +- .../DSA/DSASignatureFormatter.cs | 2 +- .../AlgorithmImplementations/DSA/DSAXml.cs | 2 +- .../DSA/DsaFamilySignatureFormatTests.cs | 2 +- .../CMakeLists.txt | 2 +- .../pal_seckey.c | 79 +++ .../pal_seckey.h | 39 ++ ...em.Security.Cryptography.Algorithms.csproj | 36 +- .../tests/DSACreateTests.cs | 2 +- .../tests/DSATests.cs | 2 +- .../DefaultECDiffieHellmanProvider.Unix.cs | 2 +- .../tests/SignatureDescriptionTests.cs | 3 + ...urity.Cryptography.X509Certificates.csproj | 29 +- ...Cryptography.X509Certificates.Tests.csproj | 6 +- 36 files changed, 1534 insertions(+), 782 deletions(-) create mode 100644 src/libraries/Common/src/Internal/Cryptography/AsymmetricAlgorithmHelpers.Ansi.cs create mode 100644 src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.iOS.cs rename src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/{Interop.SecKeyRef.Export.cs => Interop.SecKeyRef.macOS.cs} (61%) create mode 100644 src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.iOS.cs create mode 100644 src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.macOS.cs create mode 100644 src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanSecurityTransforms.macOS.cs create mode 100644 src/libraries/Common/src/System/Security/Cryptography/EccSecurityTransforms.iOS.cs create mode 100644 src/libraries/Common/src/System/Security/Cryptography/EccSecurityTransforms.macOS.cs create mode 100644 src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.iOS.cs create mode 100644 src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.macOS.cs diff --git a/src/libraries/Common/src/Internal/Cryptography/AsymmetricAlgorithmHelpers.Ansi.cs b/src/libraries/Common/src/Internal/Cryptography/AsymmetricAlgorithmHelpers.Ansi.cs new file mode 100644 index 0000000000000..0014356185555 --- /dev/null +++ b/src/libraries/Common/src/Internal/Cryptography/AsymmetricAlgorithmHelpers.Ansi.cs @@ -0,0 +1,67 @@ +// 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; + +namespace Internal.Cryptography +{ + internal static partial class AsymmetricAlgorithmHelpers + { + // Encodes a EC key as an uncompressed set of concatenated scalars, + // optionally including the private key. To omit the private parameter, + // "d" must have a length of zero. + public static void EncodeToUncompressedAnsiX963Key( + ReadOnlySpan x, + ReadOnlySpan y, + ReadOnlySpan d, + Span destination) + { + const byte UncompressedKeyPrefix = 0x04; + if (x.Length != y.Length || (d.Length > 0 && d.Length != y.Length)) + throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey); + + int size = 1 + x.Length + y.Length + d.Length; // 0x04 || X || Y { || D } + + if (destination.Length < size) + { + Debug.Fail("destination.Length < size"); + throw new CryptographicException(); + } + + destination[0] = UncompressedKeyPrefix; + x.CopyTo(destination.Slice(1)); + y.CopyTo(destination.Slice(1 + x.Length)); + d.CopyTo(destination.Slice(1 + x.Length + y.Length)); + } + + public static void DecodeFromUncompressedAnsiX963Key( + ReadOnlySpan ansiKey, + bool hasPrivateKey, + out ECParameters ret) + { + ret = default; + + const byte UncompressedKeyPrefix = 0x04; + if (ansiKey.Length < 1 || ansiKey[0] != UncompressedKeyPrefix) + throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey); + + int fieldCount = hasPrivateKey ? 3 : 2; + int fieldSize = (ansiKey.Length - 1) / fieldCount; + + if (ansiKey.Length != 1 + fieldSize * fieldCount) + throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey); + + ret.Q = new ECPoint { + X = ansiKey.Slice(1, fieldSize).ToArray(), + Y = ansiKey.Slice(1 + fieldSize, fieldSize).ToArray() + }; + + if (hasPrivateKey) + { + ret.D = ansiKey.Slice(1 + fieldSize + fieldSize, fieldSize).ToArray(); + } + } + } +} 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.cs index ed03356e1019d..5ba6dda27a2bf 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.cs @@ -66,7 +66,7 @@ private static extern int AppleCryptoNative_SecKeychainEnumerateIdentities( out SafeCFArrayHandle matches, out int pOSStatus); - internal static SafeKeychainHandle SecKeychainItemCopyKeychain(SafeKeychainItemHandle item) + private static SafeKeychainHandle SecKeychainItemCopyKeychain(SafeHandle item) { bool addedRef = false; @@ -85,6 +85,12 @@ internal static SafeKeychainHandle SecKeychainItemCopyKeychain(SafeKeychainItemH } } + internal static SafeKeychainHandle SecKeychainItemCopyKeychain(SafeKeychainItemHandle item) + => SecKeychainItemCopyKeychain((SafeHandle)item); + + internal static SafeKeychainHandle SecKeychainItemCopyKeychain(SafeSecKeyRefHandle item) + => SecKeychainItemCopyKeychain((SafeHandle)item); + internal static SafeKeychainHandle SecKeychainItemCopyKeychain(IntPtr item) { SafeKeychainHandle keychain; diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.cs index c4f7a3b190065..1e83ae065f202 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.cs @@ -16,26 +16,6 @@ internal static partial class AppleCrypto private const int kErrorSeeError = -2; private const int kPlatformNotSupported = -5; - private static int AppleCryptoNative_SecKeyImportEphemeral( - ReadOnlySpan pbKeyBlob, - int isPrivateKey, - out SafeSecKeyRefHandle ppKeyOut, - out int pOSStatus) => - AppleCryptoNative_SecKeyImportEphemeral( - ref MemoryMarshal.GetReference(pbKeyBlob), - pbKeyBlob.Length, - isPrivateKey, - out ppKeyOut, - out pOSStatus); - - [DllImport(Libraries.AppleCryptoNative)] - private static extern int AppleCryptoNative_SecKeyImportEphemeral( - ref byte pbKeyBlob, - int cbKeyBlob, - int isPrivateKey, - out SafeSecKeyRefHandle ppKeyOut, - out int pOSStatus); - [DllImport(Libraries.AppleCryptoNative)] private static extern ulong AppleCryptoNative_SecKeyGetSimpleKeySizeInBytes(SafeSecKeyRefHandle publicKey); @@ -102,40 +82,27 @@ internal static int GetSimpleKeySizeInBits(SafeSecKeyRefHandle publicKey) return (int)(keySizeInBytes * 8); } } - - internal static SafeSecKeyRefHandle ImportEphemeralKey(ReadOnlySpan keyBlob, bool hasPrivateKey) - { - Debug.Assert(keyBlob != null); - - SafeSecKeyRefHandle keyHandle; - int osStatus; - - int ret = AppleCryptoNative_SecKeyImportEphemeral( - keyBlob, - hasPrivateKey ? 1 : 0, - out keyHandle, - out osStatus); - - if (ret == 1 && !keyHandle.IsInvalid) - { - return keyHandle; - } - - if (ret == 0) - { - throw CreateExceptionForOSStatus(osStatus); - } - - Debug.Fail($"SecKeyImportEphemeral returned {ret}"); - throw new CryptographicException(); - } } } namespace System.Security.Cryptography.Apple { - internal sealed class SafeSecKeyRefHandle : SafeKeychainItemHandle + internal sealed class SafeSecKeyRefHandle : SafeHandle { + public SafeSecKeyRefHandle() + : 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; + protected override void Dispose(bool disposing) { if (disposing && SafeHandleCache.IsCachedInvalidHandle(this)) diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.iOS.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.iOS.cs new file mode 100644 index 0000000000000..1c8b2ecbd28cd --- /dev/null +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.iOS.cs @@ -0,0 +1,87 @@ +// 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 Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class AppleCrypto + { + internal enum PAL_KeyAlgorithm : uint + { + Unknown = 0, + EC = 1, + RSA = 2, + } + + internal static unsafe SafeSecKeyRefHandle CreateDataKey( + ReadOnlySpan keyData, + PAL_KeyAlgorithm keyAlgorithm, + bool isPublic) + { + fixed (byte* pKey = keyData) + { + int result = AppleCryptoNative_SecKeyCreateWithData( + pKey, + keyData.Length, + keyAlgorithm, + isPublic ? 1 : 0, + out SafeSecKeyRefHandle dataKey, + out SafeCFErrorHandle errorHandle); + + using (errorHandle) + { + return result switch + { + kSuccess => dataKey, + kErrorSeeError => throw CreateExceptionForCFError(errorHandle), + _ => throw new CryptographicException { HResult = result } + }; + } + } + } + + internal static byte[] SecKeyCopyExternalRepresentation( + SafeSecKeyRefHandle key) + { + int result = AppleCryptoNative_SecKeyCopyExternalRepresentation( + key, + out SafeCFDataHandle data, + out SafeCFErrorHandle errorHandle); + + using (errorHandle) + using (data) + { + return result switch + { + kSuccess => CoreFoundation.CFGetData(data), + kErrorSeeError => throw CreateExceptionForCFError(errorHandle), + _ => throw new CryptographicException { HResult = result } + }; + } + } + + [DllImport(Libraries.AppleCryptoNative)] + private static unsafe extern int AppleCryptoNative_SecKeyCreateWithData( + byte* pKey, + int cbKey, + PAL_KeyAlgorithm keyAlgorithm, + int isPublic, + out SafeSecKeyRefHandle pDataKey, + out SafeCFErrorHandle pErrorOut); + + [DllImport(Libraries.AppleCryptoNative)] + private static unsafe extern int AppleCryptoNative_SecKeyCopyExternalRepresentation( + SafeSecKeyRefHandle key, + out SafeCFDataHandle pDataOut, + out SafeCFErrorHandle pErrorOut); + + [DllImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SecKeyCopyPublicKey")] + internal static unsafe extern SafeSecKeyRefHandle CopyPublicKey(SafeSecKeyRefHandle privateKey); + } +} diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.Export.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.macOS.cs similarity index 61% rename from src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.Export.cs rename to src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.macOS.cs index 1a947e2dd3341..cb78aabd624e3 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.Export.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.macOS.cs @@ -14,6 +14,53 @@ internal static partial class AppleCrypto { private static readonly SafeCreateHandle s_nullExportString = new SafeCreateHandle(); + private static int AppleCryptoNative_SecKeyImportEphemeral( + ReadOnlySpan pbKeyBlob, + int isPrivateKey, + out SafeSecKeyRefHandle ppKeyOut, + out int pOSStatus) => + AppleCryptoNative_SecKeyImportEphemeral( + ref MemoryMarshal.GetReference(pbKeyBlob), + pbKeyBlob.Length, + isPrivateKey, + out ppKeyOut, + out pOSStatus); + + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_SecKeyImportEphemeral( + ref byte pbKeyBlob, + int cbKeyBlob, + int isPrivateKey, + out SafeSecKeyRefHandle ppKeyOut, + out int pOSStatus); + + internal static SafeSecKeyRefHandle ImportEphemeralKey(ReadOnlySpan keyBlob, bool hasPrivateKey) + { + Debug.Assert(keyBlob != null); + + SafeSecKeyRefHandle keyHandle; + int osStatus; + + int ret = AppleCryptoNative_SecKeyImportEphemeral( + keyBlob, + hasPrivateKey ? 1 : 0, + out keyHandle, + out osStatus); + + if (ret == 1 && !keyHandle.IsInvalid) + { + return keyHandle; + } + + if (ret == 0) + { + throw CreateExceptionForOSStatus(osStatus); + } + + Debug.Fail($"SecKeyImportEphemeral returned {ret}"); + throw new CryptographicException(); + } + [DllImport(Libraries.AppleCryptoNative)] private static extern int AppleCryptoNative_SecKeyExport( SafeSecKeyRefHandle? key, diff --git a/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.cs b/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.cs index 8ac8567db366b..d991928f7779d 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.cs @@ -19,397 +19,196 @@ public partial class DSA : AsymmetricAlgorithm { return new DSAImplementation.DSASecurityTransforms(); } + } #endif - internal static partial class DSAImplementation + internal static partial class DSAImplementation + { + public sealed partial class DSASecurityTransforms : DSA { - public sealed partial class DSASecurityTransforms : DSA - { - private SecKeyPair? _keys; - private bool _disposed; + private SecKeyPair? _keys; + private bool _disposed; - public DSASecurityTransforms() - : this(1024) - { - } + public DSASecurityTransforms() + : this(1024) + { + } - public DSASecurityTransforms(int keySize) - { - base.KeySize = keySize; - } + public DSASecurityTransforms(int keySize) + { + base.KeySize = keySize; + } - internal DSASecurityTransforms(SafeSecKeyRefHandle publicKey) - { - SetKey(SecKeyPair.PublicOnly(publicKey)); - } + internal DSASecurityTransforms(SafeSecKeyRefHandle publicKey) + { + SetKey(SecKeyPair.PublicOnly(publicKey)); + } - internal DSASecurityTransforms(SafeSecKeyRefHandle publicKey, SafeSecKeyRefHandle privateKey) - { - SetKey(SecKeyPair.PublicPrivatePair(publicKey, privateKey)); - } + internal DSASecurityTransforms(SafeSecKeyRefHandle publicKey, SafeSecKeyRefHandle privateKey) + { + SetKey(SecKeyPair.PublicPrivatePair(publicKey, privateKey)); + } - public override KeySizes[] LegalKeySizes + public override KeySizes[] LegalKeySizes + { + get { - get - { - return new[] { new KeySizes(minSize: 512, maxSize: 1024, skipSize: 64) }; - } - } - - public override int KeySize - { - get - { - return base.KeySize; - } - set - { - if (KeySize == value) - return; - - // Set the KeySize before freeing the key so that an invalid value doesn't throw away the key - base.KeySize = value; - - ThrowIfDisposed(); - - if (_keys != null) - { - _keys.Dispose(); - _keys = null; - } - } + return new[] { new KeySizes(minSize: 512, maxSize: 1024, skipSize: 64) }; } + } - public override DSAParameters ExportParameters(bool includePrivateParameters) + public override int KeySize + { + get { - // Apple requires all private keys to be exported encrypted, but since we're trying to export - // as parsed structures we will need to decrypt it for the user. - const string ExportPassword = "DotnetExportPassphrase"; - SecKeyPair keys = GetKeys(); - - if (includePrivateParameters && keys.PrivateKey == null) - { - throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); - } - - byte[] keyBlob = Interop.AppleCrypto.SecKeyExport( - includePrivateParameters ? keys.PrivateKey : keys.PublicKey, - exportPrivate: includePrivateParameters, - password: ExportPassword); - - try - { - if (!includePrivateParameters) - { - DSAKeyFormatHelper.ReadSubjectPublicKeyInfo( - keyBlob, - out int localRead, - out DSAParameters key); - Debug.Assert(localRead == keyBlob.Length); - return key; - } - else - { - DSAKeyFormatHelper.ReadEncryptedPkcs8( - keyBlob, - ExportPassword, - out int localRead, - out DSAParameters key); - Debug.Assert(localRead == keyBlob.Length); - return key; - } - } - finally - { - CryptographicOperations.ZeroMemory(keyBlob); - } + return base.KeySize; } - - public override void ImportParameters(DSAParameters parameters) + set { - if (parameters.P == null || parameters.Q == null || parameters.G == null || parameters.Y == null) - throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_MissingFields); - - // J is not required and is not even used on CNG blobs. - // It should, however, be less than P (J == (P-1) / Q). - // This validation check is just to maintain parity with DSACng and DSACryptoServiceProvider, - // which also perform this check. - if (parameters.J != null && parameters.J.Length >= parameters.P.Length) - throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_MismatchedPJ); - - int keySize = parameters.P.Length; - bool hasPrivateKey = parameters.X != null; + if (KeySize == value) + return; - if (parameters.G.Length != keySize || parameters.Y.Length != keySize) - throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_MismatchedPGY); - - if (hasPrivateKey && parameters.X!.Length != parameters.Q.Length) - throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_MismatchedQX); - - if (!(8 * parameters.P.Length).IsLegalSize(LegalKeySizes)) - throw new CryptographicException(SR.Cryptography_InvalidKeySize); - - if (parameters.Q.Length != 20) - throw new CryptographicException(SR.Cryptography_InvalidDsaParameters_QRestriction_ShortKey); + // Set the KeySize before freeing the key so that an invalid value doesn't throw away the key + base.KeySize = value; ThrowIfDisposed(); - if (hasPrivateKey) - { - SafeSecKeyRefHandle privateKey = ImportKey(parameters); - - DSAParameters publicOnly = parameters; - publicOnly.X = null; - - SafeSecKeyRefHandle publicKey; - try - { - publicKey = ImportKey(publicOnly); - } - catch - { - privateKey.Dispose(); - throw; - } - - SetKey(SecKeyPair.PublicPrivatePair(publicKey, privateKey)); - } - else + if (_keys != null) { - SafeSecKeyRefHandle publicKey = ImportKey(parameters); - SetKey(SecKeyPair.PublicOnly(publicKey)); + _keys.Dispose(); + _keys = null; } } + } - public override void ImportEncryptedPkcs8PrivateKey( - ReadOnlySpan passwordBytes, - ReadOnlySpan source, - out int bytesRead) - { - ThrowIfDisposed(); - base.ImportEncryptedPkcs8PrivateKey(passwordBytes, source, out bytesRead); - } + public override byte[] CreateSignature(byte[] rgbHash) + { + if (rgbHash == null) + throw new ArgumentNullException(nameof(rgbHash)); - public override void ImportEncryptedPkcs8PrivateKey( - ReadOnlySpan password, - ReadOnlySpan source, - out int bytesRead) - { - ThrowIfDisposed(); - base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead); - } + SecKeyPair keys = GetKeys(); - private static SafeSecKeyRefHandle ImportKey(DSAParameters parameters) + if (keys.PrivateKey == null) { - AsnWriter keyWriter; - bool hasPrivateKey; - - if (parameters.X != null) - { - // DSAPrivateKey ::= SEQUENCE( - // version INTEGER, - // p INTEGER, - // q INTEGER, - // g INTEGER, - // y INTEGER, - // x INTEGER, - // ) - - keyWriter = new AsnWriter(AsnEncodingRules.DER); - - using (keyWriter.PushSequence()) - { - keyWriter.WriteInteger(0); - keyWriter.WriteKeyParameterInteger(parameters.P); - keyWriter.WriteKeyParameterInteger(parameters.Q); - keyWriter.WriteKeyParameterInteger(parameters.G); - keyWriter.WriteKeyParameterInteger(parameters.Y); - keyWriter.WriteKeyParameterInteger(parameters.X); - } - - hasPrivateKey = true; - } - else - { - keyWriter = DSAKeyFormatHelper.WriteSubjectPublicKeyInfo(parameters); - hasPrivateKey = false; - } - - byte[] rented = CryptoPool.Rent(keyWriter.GetEncodedLength()); - - if (!keyWriter.TryEncode(rented, out int written)) - { - Debug.Fail("TryEncode failed with a pre-allocated buffer"); - throw new InvalidOperationException(); - } - - // Explicitly clear the inner buffer - keyWriter.Reset(); - - try - { - return Interop.AppleCrypto.ImportEphemeralKey(rented.AsSpan(0, written), hasPrivateKey); - } - finally - { - CryptoPool.Return(rented, written); - } + throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); } - public override unsafe void ImportSubjectPublicKeyInfo( - ReadOnlySpan source, - out int bytesRead) - { - ThrowIfDisposed(); + byte[] derFormatSignature = Interop.AppleCrypto.CreateSignature( + keys.PrivateKey, + rgbHash, + Interop.AppleCrypto.PAL_HashAlgorithm.Unknown, + Interop.AppleCrypto.PAL_SignatureAlgorithm.DSA); - fixed (byte* ptr = &MemoryMarshal.GetReference(source)) - { - using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) - { - // Validate the DER value and get the number of bytes. - DSAKeyFormatHelper.ReadSubjectPublicKeyInfo( - manager.Memory, - out int localRead); - - SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.ImportEphemeralKey(source.Slice(0, localRead), false); - SetKey(SecKeyPair.PublicOnly(publicKey)); - - bytesRead = localRead; - } - } - } - - public override byte[] CreateSignature(byte[] rgbHash) - { - if (rgbHash == null) - throw new ArgumentNullException(nameof(rgbHash)); + // Since the AppleCrypto implementation is limited to FIPS 186-2, signature field sizes + // are always 160 bits / 20 bytes (the size of SHA-1, and the only legal length for Q). + byte[] ieeeFormatSignature = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363( + derFormatSignature.AsSpan(0, derFormatSignature.Length), + fieldSizeBits: 160); - SecKeyPair keys = GetKeys(); + return ieeeFormatSignature; + } - if (keys.PrivateKey == null) - { - throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); - } + public override bool VerifySignature(byte[] hash, byte[] signature) + { + if (hash == null) + throw new ArgumentNullException(nameof(hash)); + if (signature == null) + throw new ArgumentNullException(nameof(signature)); - byte[] derFormatSignature = Interop.AppleCrypto.CreateSignature( - keys.PrivateKey, - rgbHash, - Interop.AppleCrypto.PAL_HashAlgorithm.Unknown, - Interop.AppleCrypto.PAL_SignatureAlgorithm.DSA); + return VerifySignature((ReadOnlySpan)hash, (ReadOnlySpan)signature); + } - // Since the AppleCrypto implementation is limited to FIPS 186-2, signature field sizes - // are always 160 bits / 20 bytes (the size of SHA-1, and the only legal length for Q). - byte[] ieeeFormatSignature = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363( - derFormatSignature.AsSpan(0, derFormatSignature.Length), - fieldSizeBits: 160); + public override bool VerifySignature(ReadOnlySpan hash, ReadOnlySpan signature) + { + byte[] derFormatSignature = AsymmetricAlgorithmHelpers.ConvertIeee1363ToDer(signature); + + return Interop.AppleCrypto.VerifySignature( + GetKeys().PublicKey, + hash, + derFormatSignature, + Interop.AppleCrypto.PAL_HashAlgorithm.Unknown, + Interop.AppleCrypto.PAL_SignatureAlgorithm.DSA); + } - return ieeeFormatSignature; + protected override byte[] HashData(byte[] data, int offset, int count, HashAlgorithmName hashAlgorithm) + { + if (hashAlgorithm != HashAlgorithmName.SHA1) + { + // Matching DSACryptoServiceProvider's "I only understand SHA-1/FIPS 186-2" exception + throw new CryptographicException(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithm.Name); } - public override bool VerifySignature(byte[] hash, byte[] signature) - { - if (hash == null) - throw new ArgumentNullException(nameof(hash)); - if (signature == null) - throw new ArgumentNullException(nameof(signature)); + return AsymmetricAlgorithmHelpers.HashData(data, offset, count, hashAlgorithm); + } - return VerifySignature((ReadOnlySpan)hash, (ReadOnlySpan)signature); - } + protected override byte[] HashData(Stream data, HashAlgorithmName hashAlgorithm) => + AsymmetricAlgorithmHelpers.HashData(data, hashAlgorithm); - public override bool VerifySignature(ReadOnlySpan hash, ReadOnlySpan signature) - { - byte[] derFormatSignature = AsymmetricAlgorithmHelpers.ConvertIeee1363ToDer(signature); - - return Interop.AppleCrypto.VerifySignature( - GetKeys().PublicKey, - hash, - derFormatSignature, - Interop.AppleCrypto.PAL_HashAlgorithm.Unknown, - Interop.AppleCrypto.PAL_SignatureAlgorithm.DSA); - } + protected override bool TryHashData(ReadOnlySpan data, Span destination, HashAlgorithmName hashAlgorithm, out int bytesWritten) => + AsymmetricAlgorithmHelpers.TryHashData(data, destination, hashAlgorithm, out bytesWritten); - protected override byte[] HashData(byte[] data, int offset, int count, HashAlgorithmName hashAlgorithm) + protected override void Dispose(bool disposing) + { + if (disposing) { - if (hashAlgorithm != HashAlgorithmName.SHA1) + if (_keys != null) { - // Matching DSACryptoServiceProvider's "I only understand SHA-1/FIPS 186-2" exception - throw new CryptographicException(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithm.Name); + _keys.Dispose(); + _keys = null; } - return AsymmetricAlgorithmHelpers.HashData(data, offset, count, hashAlgorithm); + _disposed = true; } - protected override byte[] HashData(Stream data, HashAlgorithmName hashAlgorithm) => - AsymmetricAlgorithmHelpers.HashData(data, hashAlgorithm); - - protected override bool TryHashData(ReadOnlySpan data, Span destination, HashAlgorithmName hashAlgorithm, out int bytesWritten) => - AsymmetricAlgorithmHelpers.TryHashData(data, destination, hashAlgorithm, out bytesWritten); + base.Dispose(disposing); + } - protected override void Dispose(bool disposing) + private void ThrowIfDisposed() + { + // The other SecurityTransforms types use _keys.PublicKey == null, + // but since Apple doesn't provide DSA key generation we can't easily tell + // if a failed attempt to generate a key happened, or we're in a pristine state. + // + // So this type uses an explicit field, rather than inferred state. + if (_disposed) { - if (disposing) - { - if (_keys != null) - { - _keys.Dispose(); - _keys = null; - } + throw new ObjectDisposedException(nameof(DSA)); + } + } - _disposed = true; - } + internal SecKeyPair GetKeys() + { + ThrowIfDisposed(); - base.Dispose(disposing); - } + SecKeyPair? current = _keys; - private void ThrowIfDisposed() + if (current != null) { - // The other SecurityTransforms types use _keys.PublicKey == null, - // but since Apple doesn't provide DSA key generation we can't easily tell - // if a failed attempt to generate a key happened, or we're in a pristine state. - // - // So this type uses an explicit field, rather than inferred state. - if (_disposed) - { - throw new ObjectDisposedException(nameof(DSA)); - } + return current; } - internal SecKeyPair GetKeys() - { - ThrowIfDisposed(); - - SecKeyPair? current = _keys; + // macOS 10.11 and macOS 10.12 declare DSA invalid for key generation. + // Rather than write code which might or might not work, returning + // (OSStatus)-4 (errSecUnimplemented), just make the exception occur here. + // + // When the native code can be verified, then it can be added. + throw new PlatformNotSupportedException(SR.Cryptography_DSA_KeyGenNotSupported); + } - if (current != null) - { - return current; - } + private void SetKey(SecKeyPair newKeyPair) + { + ThrowIfDisposed(); - // macOS 10.11 and macOS 10.12 declare DSA invalid for key generation. - // Rather than write code which might or might not work, returning - // (OSStatus)-4 (errSecUnimplemented), just make the exception occur here. - // - // When the native code can be verified, then it can be added. - throw new PlatformNotSupportedException(SR.Cryptography_DSA_KeyGenNotSupported); - } + SecKeyPair? current = _keys; + _keys = newKeyPair; + current?.Dispose(); - private void SetKey(SecKeyPair newKeyPair) + if (newKeyPair != null) { - ThrowIfDisposed(); - - SecKeyPair? current = _keys; - _keys = newKeyPair; - current?.Dispose(); - - if (newKeyPair != null) - { - int size = Interop.AppleCrypto.GetSimpleKeySizeInBits(newKeyPair.PublicKey); - KeySizeValue = size; - } + int size = Interop.AppleCrypto.GetSimpleKeySizeInBits(newKeyPair.PublicKey); + KeySizeValue = size; } } } -#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS } -#endif } diff --git a/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.iOS.cs b/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.iOS.cs new file mode 100644 index 0000000000000..2773e65dbc74e --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.iOS.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics; +using System.Formats.Asn1; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Cryptography.Apple; +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + internal static partial class DSAImplementation + { + public sealed partial class DSASecurityTransforms : DSA + { + public override DSAParameters ExportParameters(bool includePrivateParameters) + => throw new PlatformNotSupportedException(); + + public override void ImportParameters(DSAParameters parameters) + => throw new PlatformNotSupportedException(); + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.macOS.cs b/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.macOS.cs new file mode 100644 index 0000000000000..bf5f237c6f610 --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.macOS.cs @@ -0,0 +1,218 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics; +using System.Formats.Asn1; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Cryptography.Apple; +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + internal static partial class DSAImplementation + { + public sealed partial class DSASecurityTransforms : DSA + { + public override DSAParameters ExportParameters(bool includePrivateParameters) + { + // Apple requires all private keys to be exported encrypted, but since we're trying to export + // as parsed structures we will need to decrypt it for the user. + const string ExportPassword = "DotnetExportPassphrase"; + SecKeyPair keys = GetKeys(); + + if (includePrivateParameters && keys.PrivateKey == null) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + + byte[] keyBlob = Interop.AppleCrypto.SecKeyExport( + includePrivateParameters ? keys.PrivateKey : keys.PublicKey, + exportPrivate: includePrivateParameters, + password: ExportPassword); + + try + { + if (!includePrivateParameters) + { + DSAKeyFormatHelper.ReadSubjectPublicKeyInfo( + keyBlob, + out int localRead, + out DSAParameters key); + Debug.Assert(localRead == keyBlob.Length); + return key; + } + else + { + DSAKeyFormatHelper.ReadEncryptedPkcs8( + keyBlob, + ExportPassword, + out int localRead, + out DSAParameters key); + Debug.Assert(localRead == keyBlob.Length); + return key; + } + } + finally + { + CryptographicOperations.ZeroMemory(keyBlob); + } + } + + public override void ImportParameters(DSAParameters parameters) + { + if (parameters.P == null || parameters.Q == null || parameters.G == null || parameters.Y == null) + throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_MissingFields); + + // J is not required and is not even used on CNG blobs. + // It should, however, be less than P (J == (P-1) / Q). + // This validation check is just to maintain parity with DSACng and DSACryptoServiceProvider, + // which also perform this check. + if (parameters.J != null && parameters.J.Length >= parameters.P.Length) + throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_MismatchedPJ); + + int keySize = parameters.P.Length; + bool hasPrivateKey = parameters.X != null; + + if (parameters.G.Length != keySize || parameters.Y.Length != keySize) + throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_MismatchedPGY); + + if (hasPrivateKey && parameters.X!.Length != parameters.Q.Length) + throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_MismatchedQX); + + if (!(8 * parameters.P.Length).IsLegalSize(LegalKeySizes)) + throw new CryptographicException(SR.Cryptography_InvalidKeySize); + + if (parameters.Q.Length != 20) + throw new CryptographicException(SR.Cryptography_InvalidDsaParameters_QRestriction_ShortKey); + + ThrowIfDisposed(); + + if (hasPrivateKey) + { + SafeSecKeyRefHandle privateKey = ImportKey(parameters); + + DSAParameters publicOnly = parameters; + publicOnly.X = null; + + SafeSecKeyRefHandle publicKey; + try + { + publicKey = ImportKey(publicOnly); + } + catch + { + privateKey.Dispose(); + throw; + } + + SetKey(SecKeyPair.PublicPrivatePair(publicKey, privateKey)); + } + else + { + SafeSecKeyRefHandle publicKey = ImportKey(parameters); + SetKey(SecKeyPair.PublicOnly(publicKey)); + } + } + + public override void ImportEncryptedPkcs8PrivateKey( + ReadOnlySpan passwordBytes, + ReadOnlySpan source, + out int bytesRead) + { + ThrowIfDisposed(); + base.ImportEncryptedPkcs8PrivateKey(passwordBytes, source, out bytesRead); + } + + public override void ImportEncryptedPkcs8PrivateKey( + ReadOnlySpan password, + ReadOnlySpan source, + out int bytesRead) + { + ThrowIfDisposed(); + base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead); + } + + private static SafeSecKeyRefHandle ImportKey(DSAParameters parameters) + { + AsnWriter keyWriter; + bool hasPrivateKey; + + if (parameters.X != null) + { + // DSAPrivateKey ::= SEQUENCE( + // version INTEGER, + // p INTEGER, + // q INTEGER, + // g INTEGER, + // y INTEGER, + // x INTEGER, + // ) + + keyWriter = new AsnWriter(AsnEncodingRules.DER); + + using (keyWriter.PushSequence()) + { + keyWriter.WriteInteger(0); + keyWriter.WriteKeyParameterInteger(parameters.P); + keyWriter.WriteKeyParameterInteger(parameters.Q); + keyWriter.WriteKeyParameterInteger(parameters.G); + keyWriter.WriteKeyParameterInteger(parameters.Y); + keyWriter.WriteKeyParameterInteger(parameters.X); + } + + hasPrivateKey = true; + } + else + { + keyWriter = DSAKeyFormatHelper.WriteSubjectPublicKeyInfo(parameters); + hasPrivateKey = false; + } + + byte[] rented = CryptoPool.Rent(keyWriter.GetEncodedLength()); + + if (!keyWriter.TryEncode(rented, out int written)) + { + Debug.Fail("TryEncode failed with a pre-allocated buffer"); + throw new InvalidOperationException(); + } + + // Explicitly clear the inner buffer + keyWriter.Reset(); + + try + { + return Interop.AppleCrypto.ImportEphemeralKey(rented.AsSpan(0, written), hasPrivateKey); + } + finally + { + CryptoPool.Return(rented, written); + } + } + + public override unsafe void ImportSubjectPublicKeyInfo( + ReadOnlySpan source, + out int bytesRead) + { + ThrowIfDisposed(); + + fixed (byte* ptr = &MemoryMarshal.GetReference(source)) + { + using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) + { + // Validate the DER value and get the number of bytes. + DSAKeyFormatHelper.ReadSubjectPublicKeyInfo( + manager.Memory, + out int localRead); + + SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.ImportEphemeralKey(source.Slice(0, localRead), false); + SetKey(SecKeyPair.PublicOnly(publicKey)); + + bytesRead = localRead; + } + } + } + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanSecurityTransforms.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanSecurityTransforms.cs index 19bc218d3d597..5b85e6e5aa014 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanSecurityTransforms.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanSecurityTransforms.cs @@ -84,13 +84,6 @@ public override void ImportParameters(ECParameters parameters) KeySizeValue = _ecc.ImportParameters(parameters); } - public override void ImportSubjectPublicKeyInfo( - ReadOnlySpan source, - out int bytesRead) - { - KeySizeValue = _ecc.ImportSubjectPublicKeyInfo(source, out bytesRead); - } - public override void ImportEncryptedPkcs8PrivateKey( ReadOnlySpan passwordBytes, ReadOnlySpan source, diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanSecurityTransforms.macOS.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanSecurityTransforms.macOS.cs new file mode 100644 index 0000000000000..ae92c6b48f6e2 --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanSecurityTransforms.macOS.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Security.Cryptography.Apple; + +namespace System.Security.Cryptography +{ + internal static partial class ECDiffieHellmanImplementation + { + public sealed partial class ECDiffieHellmanSecurityTransforms : ECDiffieHellman + { + public override void ImportSubjectPublicKeyInfo( + ReadOnlySpan source, + out int bytesRead) + { + KeySizeValue = _ecc.ImportSubjectPublicKeyInfo(source, out bytesRead); + } + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/EccSecurityTransforms.cs b/src/libraries/Common/src/System/Security/Cryptography/EccSecurityTransforms.cs index e31b20527ae61..c6f94fe9e8ac5 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/EccSecurityTransforms.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/EccSecurityTransforms.cs @@ -6,10 +6,11 @@ using System.Formats.Asn1; using System.Runtime.InteropServices; using System.Security.Cryptography.Apple; +using Internal.Cryptography; namespace System.Security.Cryptography { - internal sealed class EccSecurityTransforms : IDisposable + internal sealed partial class EccSecurityTransforms : IDisposable { private SecKeyPair? _keys; private bool _disposed; @@ -115,182 +116,11 @@ private void SetKey(SecKeyPair keyPair) current?.Dispose(); } - internal static ECParameters ExportPublicParametersFromPrivateKey(SafeSecKeyRefHandle handle) - { - const string ExportPassword = "DotnetExportPassphrase"; - byte[] keyBlob = Interop.AppleCrypto.SecKeyExport(handle, exportPrivate: true, password: ExportPassword); - EccKeyFormatHelper.ReadEncryptedPkcs8(keyBlob, ExportPassword, out _, out ECParameters key); - CryptographicOperations.ZeroMemory(key.D); - CryptographicOperations.ZeroMemory(keyBlob); - key.D = null; - return key; - } - - internal ECParameters ExportParameters(bool includePrivateParameters, int keySizeInBits) - { - // Apple requires all private keys to be exported encrypted, but since we're trying to export - // as parsed structures we will need to decrypt it for the user. - const string ExportPassword = "DotnetExportPassphrase"; - SecKeyPair keys = GetOrGenerateKeys(keySizeInBits); - - if (includePrivateParameters && keys.PrivateKey == null) - { - throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); - } - - byte[] keyBlob = Interop.AppleCrypto.SecKeyExport( - includePrivateParameters ? keys.PrivateKey : keys.PublicKey, - exportPrivate: includePrivateParameters, - password: ExportPassword); - - try - { - if (!includePrivateParameters) - { - EccKeyFormatHelper.ReadSubjectPublicKeyInfo( - keyBlob, - out int localRead, - out ECParameters key); - return key; - } - else - { - EccKeyFormatHelper.ReadEncryptedPkcs8( - keyBlob, - ExportPassword, - out int localRead, - out ECParameters key); - return key; - } - } - finally - { - CryptographicOperations.ZeroMemory(keyBlob); - } - } - - internal int ImportParameters(ECParameters parameters) - { - parameters.Validate(); - ThrowIfDisposed(); - - bool isPrivateKey = parameters.D != null; - bool hasPublicParameters = parameters.Q.X != null && parameters.Q.Y != null; - SecKeyPair newKeys; - - if (isPrivateKey) - { - // Start with the private key, in case some of the private key fields don't - // match the public key fields and the system determines an integrity failure. - // - // Public import should go off without a hitch. - SafeSecKeyRefHandle privateKey = ImportKey(parameters); - - ECParameters publicOnly; - - if (hasPublicParameters) - { - publicOnly = parameters; - publicOnly.D = null; - } - else - { - publicOnly = ExportPublicParametersFromPrivateKey(privateKey); - } - - SafeSecKeyRefHandle publicKey; - try - { - publicKey = ImportKey(publicOnly); - } - catch - { - privateKey.Dispose(); - throw; - } - - newKeys = SecKeyPair.PublicPrivatePair(publicKey, privateKey); - } - else - { - SafeSecKeyRefHandle publicKey = ImportKey(parameters); - newKeys = SecKeyPair.PublicOnly(publicKey); - } - - int size = GetKeySize(newKeys); - SetKey(newKeys); - - return size; - } - private static int GetKeySize(SecKeyPair newKeys) { long size = Interop.AppleCrypto.EccGetKeySizeInBits(newKeys.PublicKey); Debug.Assert(size == 256 || size == 384 || size == 521, $"Unknown keysize ({size})"); return (int)size; } - - private static SafeSecKeyRefHandle ImportKey(ECParameters parameters) - { - AsnWriter keyWriter; - bool hasPrivateKey; - - if (parameters.D != null) - { - keyWriter = EccKeyFormatHelper.WriteECPrivateKey(parameters); - hasPrivateKey = true; - } - else - { - keyWriter = EccKeyFormatHelper.WriteSubjectPublicKeyInfo(parameters); - hasPrivateKey = false; - } - - byte[] rented = CryptoPool.Rent(keyWriter.GetEncodedLength()); - - if (!keyWriter.TryEncode(rented, out int written)) - { - Debug.Fail("TryEncode failed with a pre-allocated buffer"); - throw new InvalidOperationException(); - } - - // Explicitly clear the inner buffer - keyWriter.Reset(); - - try - { - return Interop.AppleCrypto.ImportEphemeralKey(rented.AsSpan(0, written), hasPrivateKey); - } - finally - { - CryptoPool.Return(rented, written); - } - } - - internal unsafe int ImportSubjectPublicKeyInfo( - ReadOnlySpan source, - out int bytesRead) - { - ThrowIfDisposed(); - - fixed (byte* ptr = &MemoryMarshal.GetReference(source)) - { - using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) - { - // Validate the DER value and get the number of bytes. - EccKeyFormatHelper.ReadSubjectPublicKeyInfo( - manager.Memory, - out int localRead); - - SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.ImportEphemeralKey(source.Slice(0, localRead), false); - SecKeyPair newKeys = SecKeyPair.PublicOnly(publicKey); - int size = GetKeySize(newKeys); - SetKey(newKeys); - - bytesRead = localRead; - return size; - } - } - } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/EccSecurityTransforms.iOS.cs b/src/libraries/Common/src/System/Security/Cryptography/EccSecurityTransforms.iOS.cs new file mode 100644 index 0000000000000..44f17a8b88304 --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/EccSecurityTransforms.iOS.cs @@ -0,0 +1,130 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics; +using System.Formats.Asn1; +using System.Runtime.InteropServices; +using System.Security.Cryptography.Apple; +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + internal sealed partial class EccSecurityTransforms + { + internal ECParameters ExportParameters(bool includePrivateParameters, int keySizeInBits) + { + SecKeyPair keys = GetOrGenerateKeys(keySizeInBits); + + if (includePrivateParameters && keys.PrivateKey == null) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + + byte[] keyBlob = Interop.AppleCrypto.SecKeyCopyExternalRepresentation( + includePrivateParameters ? keys.PrivateKey! : keys.PublicKey); + + try + { + AsymmetricAlgorithmHelpers.DecodeFromUncompressedAnsiX963Key( + keyBlob, + includePrivateParameters, + out ECParameters key); + + switch (GetKeySize(keys)) + { + case 256: key.Curve = ECCurve.NamedCurves.nistP256; break; + case 384: key.Curve = ECCurve.NamedCurves.nistP384; break; + case 521: key.Curve = ECCurve.NamedCurves.nistP521; break; + } + + return key; + } + finally + { + CryptographicOperations.ZeroMemory(keyBlob); + } + } + + internal int ImportParameters(ECParameters parameters) + { + parameters.Validate(); + ThrowIfDisposed(); + + if (!parameters.Curve.IsNamed) + { + throw new PlatformNotSupportedException(SR.Cryptography_ECC_NamedCurvesOnly); + } + + switch (parameters.Curve.Oid.Value) + { + case Oids.secp256r1: + case Oids.secp384r1: + case Oids.secp521r1: + break; + default: + throw new PlatformNotSupportedException( + SR.Format(SR.Cryptography_CurveNotSupported, parameters.Curve.Oid.Value ?? parameters.Curve.Oid.FriendlyName)); + } + + if (parameters.Q.X == null || parameters.Q.Y == null) + { + throw new PlatformNotSupportedException(SR.Cryptography_NotValidPublicOrPrivateKey); + } + + bool isPrivateKey = parameters.D != null; + SecKeyPair newKeys; + + if (isPrivateKey) + { + // Start with the private key, in case some of the private key fields don't + // match the public key fields and the system determines an integrity failure. + // + // Public import should go off without a hitch. + SafeSecKeyRefHandle privateKey = ImportKey(parameters); + SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.CopyPublicKey(privateKey); + newKeys = SecKeyPair.PublicPrivatePair(publicKey, privateKey); + } + else + { + SafeSecKeyRefHandle publicKey = ImportKey(parameters); + newKeys = SecKeyPair.PublicOnly(publicKey); + } + + int size = GetKeySize(newKeys); + SetKey(newKeys); + + return size; + } + + private static SafeSecKeyRefHandle ImportKey(ECParameters parameters) + { + int fieldSize = parameters.Q!.X!.Length; + + Debug.Assert(parameters.Q.Y != null && parameters.Q.Y.Length == fieldSize); + Debug.Assert(parameters.Q.X != null && parameters.Q.X.Length == fieldSize); + + int keySize = 1 + fieldSize * (parameters.D != null ? 3 : 2); + byte[] dataKeyPool = CryptoPool.Rent(keySize); + Span dataKey = dataKeyPool.AsSpan(0, keySize); + + try + { + AsymmetricAlgorithmHelpers.EncodeToUncompressedAnsiX963Key( + parameters.Q.X, + parameters.Q.Y, + parameters.D, + dataKey); + + return Interop.AppleCrypto.CreateDataKey( + dataKey, + Interop.AppleCrypto.PAL_KeyAlgorithm.EC, + isPublic: parameters.D == null); + } + finally + { + CryptoPool.Return(dataKeyPool, keySize); + } + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/EccSecurityTransforms.macOS.cs b/src/libraries/Common/src/System/Security/Cryptography/EccSecurityTransforms.macOS.cs new file mode 100644 index 0000000000000..a47dcd6593b04 --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/EccSecurityTransforms.macOS.cs @@ -0,0 +1,186 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics; +using System.Formats.Asn1; +using System.Runtime.InteropServices; +using System.Security.Cryptography.Apple; +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + internal sealed partial class EccSecurityTransforms + { + internal static ECParameters ExportPublicParametersFromPrivateKey(SafeSecKeyRefHandle handle) + { + const string ExportPassword = "DotnetExportPassphrase"; + byte[] keyBlob = Interop.AppleCrypto.SecKeyExport(handle, exportPrivate: true, password: ExportPassword); + EccKeyFormatHelper.ReadEncryptedPkcs8(keyBlob, ExportPassword, out _, out ECParameters key); + CryptographicOperations.ZeroMemory(key.D); + CryptographicOperations.ZeroMemory(keyBlob); + key.D = null; + return key; + } + + internal ECParameters ExportParameters(bool includePrivateParameters, int keySizeInBits) + { + // Apple requires all private keys to be exported encrypted, but since we're trying to export + // as parsed structures we will need to decrypt it for the user. + const string ExportPassword = "DotnetExportPassphrase"; + SecKeyPair keys = GetOrGenerateKeys(keySizeInBits); + + if (includePrivateParameters && keys.PrivateKey == null) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + + byte[] keyBlob = Interop.AppleCrypto.SecKeyExport( + includePrivateParameters ? keys.PrivateKey : keys.PublicKey, + exportPrivate: includePrivateParameters, + password: ExportPassword); + + try + { + if (!includePrivateParameters) + { + EccKeyFormatHelper.ReadSubjectPublicKeyInfo( + keyBlob, + out int localRead, + out ECParameters key); + return key; + } + else + { + EccKeyFormatHelper.ReadEncryptedPkcs8( + keyBlob, + ExportPassword, + out int localRead, + out ECParameters key); + return key; + } + } + finally + { + CryptographicOperations.ZeroMemory(keyBlob); + } + } + + internal int ImportParameters(ECParameters parameters) + { + parameters.Validate(); + ThrowIfDisposed(); + + bool isPrivateKey = parameters.D != null; + bool hasPublicParameters = parameters.Q.X != null && parameters.Q.Y != null; + SecKeyPair newKeys; + + if (isPrivateKey) + { + // Start with the private key, in case some of the private key fields don't + // match the public key fields and the system determines an integrity failure. + // + // Public import should go off without a hitch. + SafeSecKeyRefHandle privateKey = ImportKey(parameters); + + ECParameters publicOnly; + + if (hasPublicParameters) + { + publicOnly = parameters; + publicOnly.D = null; + } + else + { + publicOnly = ExportPublicParametersFromPrivateKey(privateKey); + } + + SafeSecKeyRefHandle publicKey; + try + { + publicKey = ImportKey(publicOnly); + } + catch + { + privateKey.Dispose(); + throw; + } + + newKeys = SecKeyPair.PublicPrivatePair(publicKey, privateKey); + } + else + { + SafeSecKeyRefHandle publicKey = ImportKey(parameters); + newKeys = SecKeyPair.PublicOnly(publicKey); + } + + int size = GetKeySize(newKeys); + SetKey(newKeys); + + return size; + } + + private static SafeSecKeyRefHandle ImportKey(ECParameters parameters) + { + AsnWriter keyWriter; + bool hasPrivateKey; + + if (parameters.D != null) + { + keyWriter = EccKeyFormatHelper.WriteECPrivateKey(parameters); + hasPrivateKey = true; + } + else + { + keyWriter = EccKeyFormatHelper.WriteSubjectPublicKeyInfo(parameters); + hasPrivateKey = false; + } + + byte[] rented = CryptoPool.Rent(keyWriter.GetEncodedLength()); + + if (!keyWriter.TryEncode(rented, out int written)) + { + Debug.Fail("TryEncode failed with a pre-allocated buffer"); + throw new InvalidOperationException(); + } + + // Explicitly clear the inner buffer + keyWriter.Reset(); + + try + { + return Interop.AppleCrypto.ImportEphemeralKey(rented.AsSpan(0, written), hasPrivateKey); + } + finally + { + CryptoPool.Return(rented, written); + } + } + + internal unsafe int ImportSubjectPublicKeyInfo( + ReadOnlySpan source, + out int bytesRead) + { + ThrowIfDisposed(); + + fixed (byte* ptr = &MemoryMarshal.GetReference(source)) + { + using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) + { + // Validate the DER value and get the number of bytes. + EccKeyFormatHelper.ReadSubjectPublicKeyInfo( + manager.Memory, + out int localRead); + + SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.ImportEphemeralKey(source.Slice(0, localRead), false); + SecKeyPair newKeys = SecKeyPair.PublicOnly(publicKey); + int size = GetKeySize(newKeys); + SetKey(newKeys); + + bytesRead = localRead; + return size; + } + } + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSAKeyFormatHelper.cs b/src/libraries/Common/src/System/Security/Cryptography/RSAKeyFormatHelper.cs index 70635b38efc9b..e7285c21f94f8 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAKeyFormatHelper.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAKeyFormatHelper.cs @@ -68,6 +68,26 @@ internal static void ReadRsaPublicKey( }; } + internal static void ReadRsaPublicKey( + ReadOnlyMemory keyData, + out int bytesRead) + { + int read; + + try + { + AsnValueReader reader = new AsnValueReader(keyData.Span, AsnEncodingRules.DER); + read = reader.PeekEncodedValue().Length; + RSAPublicKeyAsn.Decode(keyData, AsnEncodingRules.BER); + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + + bytesRead = read; + } + internal static void ReadSubjectPublicKeyInfo( ReadOnlySpan source, out int bytesRead, diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.cs b/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.cs index fef57782675e1..1123b6ce38d4f 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.cs @@ -87,162 +87,6 @@ public override int KeySize } } - public override RSAParameters ExportParameters(bool includePrivateParameters) - { - // Apple requires all private keys to be exported encrypted, but since we're trying to export - // as parsed structures we will need to decrypt it for the user. - const string ExportPassword = "DotnetExportPassphrase"; - SecKeyPair keys = GetKeys(); - - if (includePrivateParameters && keys.PrivateKey == null) - { - throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); - } - - byte[] keyBlob = Interop.AppleCrypto.SecKeyExport( - includePrivateParameters ? keys.PrivateKey : keys.PublicKey, - exportPrivate: includePrivateParameters, - password: ExportPassword); - - try - { - if (!includePrivateParameters) - { - // When exporting a key handle opened from a certificate, it seems to - // export as a PKCS#1 blob instead of an X509 SubjectPublicKeyInfo blob. - // So, check for that. - // NOTE: It doesn't affect macOS Mojave when SecCertificateCopyKey API - // is used. - RSAParameters key; - - AsnReader reader = new AsnReader(keyBlob, AsnEncodingRules.BER); - AsnReader sequenceReader = reader.ReadSequence(); - - if (sequenceReader.PeekTag().Equals(Asn1Tag.Integer)) - { - AlgorithmIdentifierAsn ignored = default; - RSAKeyFormatHelper.ReadRsaPublicKey(keyBlob, ignored, out key); - } - else - { - RSAKeyFormatHelper.ReadSubjectPublicKeyInfo( - keyBlob, - out int localRead, - out key); - Debug.Assert(localRead == keyBlob.Length); - } - return key; - } - else - { - RSAKeyFormatHelper.ReadEncryptedPkcs8( - keyBlob, - ExportPassword, - out int localRead, - out RSAParameters key); - return key; - } - } - finally - { - CryptographicOperations.ZeroMemory(keyBlob); - } - } - - public override void ImportParameters(RSAParameters parameters) - { - ValidateParameters(parameters); - ThrowIfDisposed(); - - bool isPrivateKey = parameters.D != null; - - if (isPrivateKey) - { - // Start with the private key, in case some of the private key fields - // don't match the public key fields. - // - // Public import should go off without a hitch. - SafeSecKeyRefHandle privateKey = ImportKey(parameters); - - RSAParameters publicOnly = new RSAParameters - { - Modulus = parameters.Modulus, - Exponent = parameters.Exponent, - }; - - SafeSecKeyRefHandle publicKey; - try - { - publicKey = ImportKey(publicOnly); - } - catch - { - privateKey.Dispose(); - throw; - } - - SetKey(SecKeyPair.PublicPrivatePair(publicKey, privateKey)); - } - else - { - SafeSecKeyRefHandle publicKey = ImportKey(parameters); - SetKey(SecKeyPair.PublicOnly(publicKey)); - } - } - - public override unsafe void ImportSubjectPublicKeyInfo( - ReadOnlySpan source, - out int bytesRead) - { - ThrowIfDisposed(); - - fixed (byte* ptr = &MemoryMarshal.GetReference(source)) - { - using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) - { - // Validate the DER value and get the number of bytes. - RSAKeyFormatHelper.ReadSubjectPublicKeyInfo( - manager.Memory, - out int localRead); - - SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.ImportEphemeralKey(source.Slice(0, localRead), false); - SetKey(SecKeyPair.PublicOnly(publicKey)); - - bytesRead = localRead; - } - } - } - - public override unsafe void ImportRSAPublicKey(ReadOnlySpan source, out int bytesRead) - { - ThrowIfDisposed(); - - fixed (byte* ptr = &MemoryMarshal.GetReference(source)) - { - using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) - { - AsnReader reader = new AsnReader(manager.Memory, AsnEncodingRules.BER); - ReadOnlyMemory firstElement = reader.PeekEncodedValue(); - - SubjectPublicKeyInfoAsn spki = new SubjectPublicKeyInfoAsn - { - Algorithm = new AlgorithmIdentifierAsn - { - Algorithm = Oids.Rsa, - Parameters = AlgorithmIdentifierAsn.ExplicitDerNull, - }, - SubjectPublicKey = firstElement, - }; - - AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); - spki.Encode(writer); - - ImportSubjectPublicKeyInfo(writer.Encode(), out _); - bytesRead = firstElement.Length; - } - } - } - public override void ImportEncryptedPkcs8PrivateKey( ReadOnlySpan passwordBytes, ReadOnlySpan source, @@ -821,43 +665,6 @@ private void SetKey(SecKeyPair newKeyPair) } } - private static SafeSecKeyRefHandle ImportKey(RSAParameters parameters) - { - AsnWriter keyWriter; - bool hasPrivateKey; - - if (parameters.D != null) - { - keyWriter = RSAKeyFormatHelper.WritePkcs1PrivateKey(parameters); - hasPrivateKey = true; - } - else - { - keyWriter = RSAKeyFormatHelper.WriteSubjectPublicKeyInfo(parameters); - hasPrivateKey = false; - } - - byte[] rented = CryptoPool.Rent(keyWriter.GetEncodedLength()); - - if (!keyWriter.TryEncode(rented, out int written)) - { - Debug.Fail("TryEncode failed with a pre-allocated buffer"); - throw new InvalidOperationException(); - } - - // Explicitly clear the inner buffer - keyWriter.Reset(); - - try - { - return Interop.AppleCrypto.ImportEphemeralKey(rented.AsSpan(0, written), hasPrivateKey); - } - finally - { - CryptoPool.Return(rented, written); - } - } - private static void ValidateParameters(in RSAParameters parameters) { if (parameters.Modulus == null || parameters.Exponent == null) diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.iOS.cs b/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.iOS.cs new file mode 100644 index 0000000000000..db58a5da3d606 --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.iOS.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.Buffers; +using System.Diagnostics; +using System.Formats.Asn1; +using System.IO; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.Asn1; +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + internal static partial class RSAImplementation + { + public sealed partial class RSASecurityTransforms + { + public override RSAParameters ExportParameters(bool includePrivateParameters) + { + SecKeyPair keys = GetKeys(); + + if (includePrivateParameters && keys.PrivateKey == null) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + + byte[] keyBlob = Interop.AppleCrypto.SecKeyCopyExternalRepresentation( + includePrivateParameters ? keys.PrivateKey! : keys.PublicKey); + + try + { + if (!includePrivateParameters) + { + // When exporting a key handle opened from a certificate, it seems to + // export as a PKCS#1 blob instead of an X509 SubjectPublicKeyInfo blob. + // So, check for that. + // NOTE: It doesn't affect macOS Mojave when SecCertificateCopyKey API + // is used. + RSAParameters key; + + AsnReader reader = new AsnReader(keyBlob, AsnEncodingRules.BER); + AsnReader sequenceReader = reader.ReadSequence(); + + if (sequenceReader.PeekTag().Equals(Asn1Tag.Integer)) + { + AlgorithmIdentifierAsn ignored = default; + RSAKeyFormatHelper.ReadRsaPublicKey(keyBlob, ignored, out key); + } + else + { + RSAKeyFormatHelper.ReadSubjectPublicKeyInfo( + keyBlob, + out int localRead, + out key); + Debug.Assert(localRead == keyBlob.Length); + } + return key; + } + else + { + AlgorithmIdentifierAsn ignored = default; + RSAKeyFormatHelper.FromPkcs1PrivateKey( + keyBlob, + ignored, + out RSAParameters key); + return key; + } + } + finally + { + CryptographicOperations.ZeroMemory(keyBlob); + } + } + + public override void ImportParameters(RSAParameters parameters) + { + ValidateParameters(parameters); + ThrowIfDisposed(); + + bool isPrivateKey = parameters.D != null; + + if (isPrivateKey) + { + // Start with the private key, in case some of the private key fields + // don't match the public key fields. + // + // Public import should go off without a hitch. + SafeSecKeyRefHandle privateKey = ImportKey(parameters); + SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.CopyPublicKey(privateKey); + SetKey(SecKeyPair.PublicPrivatePair(publicKey, privateKey)); + } + else + { + SafeSecKeyRefHandle publicKey = ImportKey(parameters); + SetKey(SecKeyPair.PublicOnly(publicKey)); + } + } + + private static SafeSecKeyRefHandle ImportKey(RSAParameters parameters) + { + AsnWriter keyWriter; + bool hasPrivateKey; + + if (parameters.D != null) + { + keyWriter = RSAKeyFormatHelper.WritePkcs1PrivateKey(parameters); + hasPrivateKey = true; + } + else + { + keyWriter = RSAKeyFormatHelper.WritePkcs1PublicKey(parameters); + hasPrivateKey = false; + } + + byte[] rented = CryptoPool.Rent(keyWriter.GetEncodedLength()); + + if (!keyWriter.TryEncode(rented, out int written)) + { + Debug.Fail("TryEncode failed with a pre-allocated buffer"); + throw new InvalidOperationException(); + } + + // Explicitly clear the inner buffer + keyWriter.Reset(); + + try + { + return Interop.AppleCrypto.CreateDataKey( + rented.AsSpan(0, written), + Interop.AppleCrypto.PAL_KeyAlgorithm.RSA, + isPublic: !hasPrivateKey); + } + finally + { + CryptoPool.Return(rented, written); + } + } + + public override unsafe void ImportRSAPublicKey(ReadOnlySpan source, out int bytesRead) + { + ThrowIfDisposed(); + + fixed (byte* ptr = &MemoryMarshal.GetReference(source)) + { + using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) + { + // Validate the DER value and get the number of bytes. + RSAKeyFormatHelper.ReadRsaPublicKey( + manager.Memory, + out int localRead); + + SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.CreateDataKey( + source.Slice(0, localRead), + Interop.AppleCrypto.PAL_KeyAlgorithm.RSA, + isPublic: true); + SetKey(SecKeyPair.PublicOnly(publicKey)); + + bytesRead = localRead; + } + } + } + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.macOS.cs b/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.macOS.cs new file mode 100644 index 0000000000000..cc576f1ac7ae9 --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.macOS.cs @@ -0,0 +1,215 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics; +using System.Formats.Asn1; +using System.IO; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.Asn1; +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + internal static partial class RSAImplementation + { + public sealed partial class RSASecurityTransforms : RSA + { + public override RSAParameters ExportParameters(bool includePrivateParameters) + { + // Apple requires all private keys to be exported encrypted, but since we're trying to export + // as parsed structures we will need to decrypt it for the user. + const string ExportPassword = "DotnetExportPassphrase"; + SecKeyPair keys = GetKeys(); + + if (includePrivateParameters && keys.PrivateKey == null) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + + byte[] keyBlob = Interop.AppleCrypto.SecKeyExport( + includePrivateParameters ? keys.PrivateKey : keys.PublicKey, + exportPrivate: includePrivateParameters, + password: ExportPassword); + + try + { + if (!includePrivateParameters) + { + // When exporting a key handle opened from a certificate, it seems to + // export as a PKCS#1 blob instead of an X509 SubjectPublicKeyInfo blob. + // So, check for that. + // NOTE: It doesn't affect macOS Mojave when SecCertificateCopyKey API + // is used. + RSAParameters key; + + AsnReader reader = new AsnReader(keyBlob, AsnEncodingRules.BER); + AsnReader sequenceReader = reader.ReadSequence(); + + if (sequenceReader.PeekTag().Equals(Asn1Tag.Integer)) + { + AlgorithmIdentifierAsn ignored = default; + RSAKeyFormatHelper.ReadRsaPublicKey(keyBlob, ignored, out key); + } + else + { + RSAKeyFormatHelper.ReadSubjectPublicKeyInfo( + keyBlob, + out int localRead, + out key); + Debug.Assert(localRead == keyBlob.Length); + } + return key; + } + else + { + RSAKeyFormatHelper.ReadEncryptedPkcs8( + keyBlob, + ExportPassword, + out int localRead, + out RSAParameters key); + return key; + } + } + finally + { + CryptographicOperations.ZeroMemory(keyBlob); + } + } + + public override void ImportParameters(RSAParameters parameters) + { + ValidateParameters(parameters); + ThrowIfDisposed(); + + bool isPrivateKey = parameters.D != null; + + if (isPrivateKey) + { + // Start with the private key, in case some of the private key fields + // don't match the public key fields. + // + // Public import should go off without a hitch. + SafeSecKeyRefHandle privateKey = ImportKey(parameters); + + RSAParameters publicOnly = new RSAParameters + { + Modulus = parameters.Modulus, + Exponent = parameters.Exponent, + }; + + SafeSecKeyRefHandle publicKey; + try + { + publicKey = ImportKey(publicOnly); + } + catch + { + privateKey.Dispose(); + throw; + } + + SetKey(SecKeyPair.PublicPrivatePair(publicKey, privateKey)); + } + else + { + SafeSecKeyRefHandle publicKey = ImportKey(parameters); + SetKey(SecKeyPair.PublicOnly(publicKey)); + } + } + + + private static SafeSecKeyRefHandle ImportKey(RSAParameters parameters) + { + AsnWriter keyWriter; + bool hasPrivateKey; + + if (parameters.D != null) + { + keyWriter = RSAKeyFormatHelper.WritePkcs1PrivateKey(parameters); + hasPrivateKey = true; + } + else + { + keyWriter = RSAKeyFormatHelper.WriteSubjectPublicKeyInfo(parameters); + hasPrivateKey = false; + } + + byte[] rented = CryptoPool.Rent(keyWriter.GetEncodedLength()); + + if (!keyWriter.TryEncode(rented, out int written)) + { + Debug.Fail("TryEncode failed with a pre-allocated buffer"); + throw new InvalidOperationException(); + } + + // Explicitly clear the inner buffer + keyWriter.Reset(); + + try + { + return Interop.AppleCrypto.ImportEphemeralKey(rented.AsSpan(0, written), hasPrivateKey); + } + finally + { + CryptoPool.Return(rented, written); + } + } + + public override unsafe void ImportSubjectPublicKeyInfo( + ReadOnlySpan source, + out int bytesRead) + { + ThrowIfDisposed(); + + fixed (byte* ptr = &MemoryMarshal.GetReference(source)) + { + using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) + { + // Validate the DER value and get the number of bytes. + RSAKeyFormatHelper.ReadSubjectPublicKeyInfo( + manager.Memory, + out int localRead); + + SafeSecKeyRefHandle publicKey = Interop.AppleCrypto.ImportEphemeralKey(source.Slice(0, localRead), false); + SetKey(SecKeyPair.PublicOnly(publicKey)); + + bytesRead = localRead; + } + } + } + + public override unsafe void ImportRSAPublicKey(ReadOnlySpan source, out int bytesRead) + { + ThrowIfDisposed(); + + fixed (byte* ptr = &MemoryMarshal.GetReference(source)) + { + using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) + { + AsnReader reader = new AsnReader(manager.Memory, AsnEncodingRules.BER); + ReadOnlyMemory firstElement = reader.PeekEncodedValue(); + + SubjectPublicKeyInfoAsn spki = new SubjectPublicKeyInfoAsn + { + Algorithm = new AlgorithmIdentifierAsn + { + Algorithm = Oids.Rsa, + Parameters = AlgorithmIdentifierAsn.ExplicitDerNull, + }, + SubjectPublicKey = firstElement, + }; + + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + spki.Encode(writer); + + ImportSubjectPublicKeyInfo(writer.Encode(), out _); + bytesRead = firstElement.Length; + } + } + } + } + } +} diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAImportExport.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAImportExport.cs index 6962ab68e6cec..50208a71b86b2 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAImportExport.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAImportExport.cs @@ -5,7 +5,7 @@ namespace System.Security.Cryptography.Dsa.Tests { - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public partial class DSAImportExport { public static bool SupportsFips186_3 => DSAFactory.SupportsFips186_3; diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyFileTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyFileTests.cs index 90a5d0b1c7091..5f3a0f5b82a6d 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyFileTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyFileTests.cs @@ -8,7 +8,7 @@ namespace System.Security.Cryptography.Dsa.Tests { - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public static class DSAKeyFileTests { public static bool SupportsFips186_3 => DSAFactory.SupportsFips186_3; diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyGeneration.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyGeneration.cs index 3d1d3ea6d476c..d26dce7b36712 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyGeneration.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyGeneration.cs @@ -5,7 +5,7 @@ namespace System.Security.Cryptography.Dsa.Tests { - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public partial class DSAKeyGeneration { public static bool SupportsKeyGeneration => DSAFactory.SupportsKeyGeneration; diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyPemTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyPemTests.cs index b862e26a122fd..328e76da0562c 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyPemTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyPemTests.cs @@ -7,7 +7,7 @@ namespace System.Security.Cryptography.Dsa.Tests { - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public static class DSAKeyPemTests { private const string AmbiguousExceptionMarker = "multiple keys"; diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignVerify.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignVerify.cs index 0354fa28f2d10..03a12cbdbcf41 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignVerify.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignVerify.cs @@ -7,7 +7,7 @@ namespace System.Security.Cryptography.Dsa.Tests { - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public sealed class DSASignVerify_Array : DSASignVerify { public override byte[] SignData(DSA dsa, byte[] data, HashAlgorithmName hashAlgorithm) => @@ -54,7 +54,7 @@ public void InvalidStreamArrayArguments_Throws() } } - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public sealed class DSASignVerify_Stream : DSASignVerify { public override byte[] SignData(DSA dsa, byte[] data, HashAlgorithmName hashAlgorithm) => @@ -76,7 +76,7 @@ public void InvalidArrayArguments_Throws() } #if NETCOREAPP - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public sealed class DSASignVerify_Span : DSASignVerify { public override byte[] SignData(DSA dsa, byte[] data, HashAlgorithmName hashAlgorithm) => @@ -109,7 +109,7 @@ private static byte[] TryWithOutputArray(Func func) } } #endif - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public abstract partial class DSASignVerify { public abstract byte[] SignData(DSA dsa, byte[] data, HashAlgorithmName hashAlgorithm); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignatureFormatTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignatureFormatTests.cs index 764254cd31c9e..a1e9c80ad1225 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignatureFormatTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignatureFormatTests.cs @@ -8,7 +8,7 @@ namespace System.Security.Cryptography.Dsa.Tests { - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public abstract class DSASignatureFormatTests : DsaFamilySignatureFormatTests { protected override bool SupportsSha2 => DSAFactory.SupportsFips186_3; diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignatureFormatter.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignatureFormatter.cs index 3aea7c0bcf2db..30d5dd6bb5527 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignatureFormatter.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignatureFormatter.cs @@ -6,7 +6,7 @@ namespace System.Security.Cryptography.Dsa.Tests { - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public partial class DSASignatureFormatterTests : AsymmetricSignatureFormatterTests { [Fact] diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAXml.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAXml.cs index 6f19dd555adcb..9a959df1ada4f 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAXml.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAXml.cs @@ -7,7 +7,7 @@ namespace System.Security.Cryptography.Dsa.Tests { - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public static class DSAXml { [Fact] diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DsaFamilySignatureFormatTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DsaFamilySignatureFormatTests.cs index fde2f717f0be3..b311193948a3a 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DsaFamilySignatureFormatTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DsaFamilySignatureFormatTests.cs @@ -8,7 +8,7 @@ namespace System.Security.Cryptography.Algorithms.Tests { - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public abstract class DsaFamilySignatureFormatTests { protected readonly struct KeyDescription 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 56d07f732e101..ef17af675cbc0 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 @@ -44,7 +44,7 @@ if (GEN_SHARED_LIB) ) endif() -if (NOT GEN_SHARED_LIB AND NOT CLR_CMAKE_TARGET_MACCATALYST AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS AND NOT CLR_CMAKE_TARGET_ANDROID AND NOT CLR_CMAKE_TARGET_BROWSER) +if (NOT GEN_SHARED_LIB AND NOT CLR_CMAKE_TARGET_MACCATALYST AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS) set(NATIVECRYPTO_SOURCES ${NATIVECRYPTO_SOURCES} entrypoints.c) endif() diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.c index 4588ab2278c53..887361ba7d16a 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.c @@ -139,3 +139,82 @@ uint64_t AppleCryptoNative_SecKeyGetSimpleKeySizeInBytes(SecKeyRef publicKey) return SecKeyGetBlockSize(publicKey); } + +#if defined(TARGET_MACCATALYST) || defined(TARGET_IOS) || defined(TARGET_TVOS) +static CFStringRef GetKeyAlgorithmIdentifier(PAL_KeyAlgorithm keyAlgorithm) +{ + if (keyAlgorithm == PAL_KeyAlgorithm_EC) + return kSecAttrKeyTypeECSECPrimeRandom; + if (keyAlgorithm == PAL_KeyAlgorithm_RSA) + return kSecAttrKeyTypeRSA; + + return NULL; +} + +int32_t AppleCryptoNative_SecKeyCreateWithData(uint8_t* pKey, + int32_t cbKey, + PAL_KeyAlgorithm keyAlgorithm, + int32_t isPublic, + SecKeyRef* pKeyOut, + CFErrorRef* pErrorOut) +{ + if (pErrorOut != NULL) + *pErrorOut = NULL; + if (pKeyOut != NULL) + *pKeyOut = NULL; + + if (pKeyOut == NULL || pErrorOut == NULL || cbKey <= 0 || pKey == NULL) + return kErrorBadInput; + + CFMutableDictionaryRef dataAttributes = CFDictionaryCreateMutable( + kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + if (dataAttributes == NULL) + { + return kErrorUnknownState; + } + + CFStringRef keyClass = isPublic == 0 ? kSecAttrKeyClassPrivate : kSecAttrKeyClassPublic; + CFStringRef keyType = GetKeyAlgorithmIdentifier(keyAlgorithm); + + if (keyType == NULL) + { + CFRelease(dataAttributes); + return kErrorBadInput; + } + + CFDictionarySetValue(dataAttributes, kSecAttrKeyType, keyType); + CFDictionarySetValue(dataAttributes, kSecAttrKeyClass, keyClass); + CFDataRef cfData = CFDataCreateWithBytesNoCopy(NULL, pKey, cbKey, kCFAllocatorNull); + + *pKeyOut = SecKeyCreateWithData(cfData, dataAttributes, pErrorOut); + + CFRelease(cfData); + CFRelease(dataAttributes); + + return *pKeyOut != NULL ? 1 : kErrorSeeError; +} + +int32_t AppleCryptoNative_SecKeyCopyExternalRepresentation(SecKeyRef pKey, + CFDataRef* ppDataOut, + CFErrorRef* pErrorOut) +{ + if (ppDataOut != NULL) + *ppDataOut = NULL; + if (pErrorOut != NULL) + *pErrorOut = NULL; + + if (pKey == NULL || ppDataOut == NULL || pErrorOut == NULL) + { + return kErrorBadInput; + } + + *ppDataOut = SecKeyCopyExternalRepresentation(pKey, pErrorOut); + return *ppDataOut == NULL ? kErrorSeeError : 1; +} + +SecKeyRef AppleCryptoNative_SecKeyCopyPublicKey(SecKeyRef privateKey) +{ + return SecKeyCopyPublicKey(privateKey); +} +#endif diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.h index d57ef5ec37763..cfe5cbe1dff7c 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.h @@ -17,6 +17,14 @@ static const int32_t kErrorUnknownAlgorithm = -3; static const int32_t kErrorUnknownState = -4; static const int32_t kPlatformNotSupported = -5; +enum +{ + PAL_KeyAlgorithm_Unknown = 0, + PAL_KeyAlgorithm_EC = 1, + PAL_KeyAlgorithm_RSA = 2, +}; +typedef uint32_t PAL_KeyAlgorithm; + #if !defined(TARGET_MACCATALYST) && !defined(TARGET_IOS) && !defined(TARGET_TVOS) /* Export a key object. @@ -60,3 +68,34 @@ For ECC the value should not be used. 0 is returned for invalid inputs. */ PALEXPORT uint64_t AppleCryptoNative_SecKeyGetSimpleKeySizeInBytes(SecKeyRef publicKey); + +#if defined(TARGET_MACCATALYST) || defined(TARGET_IOS) || defined(TARGET_TVOS) +/* +Create an iOS-style key from raw data. + +Follows pal_seckey return conventions. +*/ +PALEXPORT int32_t AppleCryptoNative_SecKeyCreateWithData(uint8_t* pKey, + int32_t cbKey, + PAL_KeyAlgorithm keyAlgorithm, + int32_t isPublic, + SecKeyRef* pKeyOut, + CFErrorRef* pErrorOut); + +/* +Return an external key data representation. + +For RSA keys this function returns the PKCS#1 public or private key. +For EC keys this function returns the ANSI X.963 representation. + +Follows pal_seckey return conventions. +*/ +PALEXPORT int32_t AppleCryptoNative_SecKeyCopyExternalRepresentation(SecKeyRef pKey, + CFDataRef* ppDataOut, + CFErrorRef* pErrorOut); + +/* +Return a corresponding public key from a private key. +*/ +PALEXPORT SecKeyRef AppleCryptoNative_SecKeyCopyPublicKey(SecKeyRef privateKey); +#endif diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj index c619ee7a887b8..99829b1809776 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj @@ -499,8 +499,6 @@ Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Hmac.cs" /> - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/DSACreateTests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/DSACreateTests.cs index 4ced2c4d87ca1..e84da84a446f8 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/DSACreateTests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/DSACreateTests.cs @@ -6,7 +6,7 @@ namespace System.Security.Cryptography.Algorithms.Tests { - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public static class DSACreateTests { public static bool SupportsKeyGeneration => DSAFactory.SupportsKeyGeneration; diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/DSATests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/DSATests.cs index c350e2d1fdcc9..8b51d34c8b563 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/DSATests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/DSATests.cs @@ -8,7 +8,7 @@ namespace System.Security.Cryptography.Algorithms.Tests { - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on Browser/iOS/tvOS/MacCatalyst")] public class DSATests { public static bool SupportsKeyGeneration => DSAFactory.SupportsKeyGeneration; diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Unix.cs index c8390e2b28e92..6c47439ef9634 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Unix.cs @@ -34,7 +34,7 @@ public bool ExplicitCurvesSupported } } - public bool CanDeriveNewPublicKey => true; + public bool CanDeriveNewPublicKey => !PlatformDetection.IsiOS && !PlatformDetection.IstvOS; private static bool IsValueOrFriendlyNameValid(string friendlyNameOrValue) { diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/SignatureDescriptionTests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/SignatureDescriptionTests.cs index d26384e2108d4..257d5abde8f33 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/SignatureDescriptionTests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/SignatureDescriptionTests.cs @@ -39,6 +39,7 @@ public void Constructor_SecurityElement_Empty() } [Fact] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on iOS/tvOS/MacCatalyst")] public void Constructor_SecurityElement_DSA() { SecurityElement se = new SecurityElement("DSASignature"); @@ -102,6 +103,7 @@ public void Properties() } [Fact] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on iOS/tvOS/MacCatalyst")] public void Deformatter() { AsymmetricSignatureDeformatter def; @@ -173,6 +175,7 @@ public void Digest() } [Fact] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Not supported on iOS/tvOS/MacCatalyst")] public void Formatter() { SignatureDescription sig = new SignatureDescription(); 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 fc887c56b7141..f11a0661e7315 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 @@ -488,8 +488,6 @@ Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.SecErrMessage.cs" /> - + + + + + + + + + + + + + + + + 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 af574bfff240d..4aebe6daec3aa 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" /> - + @@ -116,7 +116,7 @@ Link="Common\Interop\Unix\Interop.Libraries.cs" /> - + + Date: Wed, 28 Apr 2021 00:13:21 +0200 Subject: [PATCH 2/5] Temporary build fix --- .../System.Security.Cryptography.Native.Apple/Interop.X509.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 5b38d0f939c8e..daa1d95f8ccdb 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 @@ -331,7 +331,7 @@ internal static SafeSecKeyRefHandle X509GetPrivateKeyFromIdentity(SafeSecIdentit SafeSecKeyRefHandle key; int osStatus = AppleCryptoNative_X509CopyPrivateKeyFromIdentity(identity, out key); - SafeTemporaryKeychainHandle.TrackItem(key); + //SafeTemporaryKeychainHandle.TrackItem(key); if (osStatus != 0) { @@ -354,7 +354,7 @@ internal static SafeSecKeyRefHandle X509GetPublicKey(SafeSecCertificateHandle ce int osStatus; int ret = AppleCryptoNative_X509GetPublicKey(cert, out publicKey, out osStatus); - SafeTemporaryKeychainHandle.TrackItem(publicKey); + //SafeTemporaryKeychainHandle.TrackItem(publicKey); if (ret == 1) { From 343549128b735966851e64d2a9eebe81179f3262 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 28 Apr 2021 00:16:51 +0200 Subject: [PATCH 3/5] Address feedback --- .../Interop.SecKeyRef.iOS.cs | 28 ++++++++++++------- .../RSASecurityTransforms.macOS.cs | 1 - 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.iOS.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.iOS.cs index 1c8b2ecbd28cd..edaf30646179f 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.iOS.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.iOS.cs @@ -36,12 +36,16 @@ internal static unsafe SafeSecKeyRefHandle CreateDataKey( using (errorHandle) { - return result switch + switch (result) { - kSuccess => dataKey, - kErrorSeeError => throw CreateExceptionForCFError(errorHandle), - _ => throw new CryptographicException { HResult = result } - }; + case kSuccess: + return dataKey; + case kErrorSeeError: + throw CreateExceptionForCFError(errorHandle); + default: + Debug.Fail($"SecKeyCreateWithData returned {result}"); + throw new CryptographicException(); + } } } } @@ -57,12 +61,16 @@ internal static byte[] SecKeyCopyExternalRepresentation( using (errorHandle) using (data) { - return result switch + switch (result) { - kSuccess => CoreFoundation.CFGetData(data), - kErrorSeeError => throw CreateExceptionForCFError(errorHandle), - _ => throw new CryptographicException { HResult = result } - }; + case kSuccess: + return CoreFoundation.CFGetData(data); + case kErrorSeeError: + throw CreateExceptionForCFError(errorHandle); + default: + Debug.Fail($"SecKeyCopyExternalRepresentation returned {result}"); + throw new CryptographicException(); + } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.macOS.cs b/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.macOS.cs index cc576f1ac7ae9..9ac208a8ecbaf 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.macOS.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.macOS.cs @@ -120,7 +120,6 @@ public override void ImportParameters(RSAParameters parameters) } } - private static SafeSecKeyRefHandle ImportKey(RSAParameters parameters) { AsnWriter keyWriter; From 8f4aa7aff815d29217e8711b04845d9afe63abf6 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 28 Apr 2021 07:49:03 +0200 Subject: [PATCH 4/5] Address feedback --- .../entrypoints.c | 3 ++ .../pal_seckey.c | 34 ++++++------------- .../pal_seckey.h | 2 -- .../DefaultECDiffieHellmanProvider.Unix.cs | 2 +- 4 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/entrypoints.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/entrypoints.c index a23999748fbc4..9fa5c5665fef0 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/entrypoints.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/entrypoints.c @@ -62,6 +62,9 @@ static const Entry s_cryptoAppleNative[] = DllImportEntry(AppleCryptoNative_SecKeyGetSimpleKeySizeInBytes) DllImportEntry(AppleCryptoNative_SecKeyCreateSignature) DllImportEntry(AppleCryptoNative_SecKeyVerifySignature) + DllImportEntry(AppleCryptoNative_SecKeyCreateWithData) + DllImportEntry(AppleCryptoNative_SecKeyCopyExternalRepresentation) + DllImportEntry(AppleCryptoNative_SecKeyCopyPublicKey) DllImportEntry(AppleCryptoNative_SslCreateContext) DllImportEntry(AppleCryptoNative_SslSetAcceptClientCert) DllImportEntry(AppleCryptoNative_SslSetMinProtocolVersion) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.c index 887361ba7d16a..1125ba9b989c2 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.c @@ -140,14 +140,12 @@ uint64_t AppleCryptoNative_SecKeyGetSimpleKeySizeInBytes(SecKeyRef publicKey) return SecKeyGetBlockSize(publicKey); } -#if defined(TARGET_MACCATALYST) || defined(TARGET_IOS) || defined(TARGET_TVOS) static CFStringRef GetKeyAlgorithmIdentifier(PAL_KeyAlgorithm keyAlgorithm) { if (keyAlgorithm == PAL_KeyAlgorithm_EC) return kSecAttrKeyTypeECSECPrimeRandom; if (keyAlgorithm == PAL_KeyAlgorithm_RSA) return kSecAttrKeyTypeRSA; - return NULL; } @@ -158,13 +156,10 @@ int32_t AppleCryptoNative_SecKeyCreateWithData(uint8_t* pKey, SecKeyRef* pKeyOut, CFErrorRef* pErrorOut) { - if (pErrorOut != NULL) - *pErrorOut = NULL; - if (pKeyOut != NULL) - *pKeyOut = NULL; - - if (pKeyOut == NULL || pErrorOut == NULL || cbKey <= 0 || pKey == NULL) - return kErrorBadInput; + assert(pKey != NULL); + assert(cbKey > 0); + assert(pKeyOut != NULL); + assert(pErrorOut != NULL); CFMutableDictionaryRef dataAttributes = CFDictionaryCreateMutable( kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); @@ -177,11 +172,7 @@ int32_t AppleCryptoNative_SecKeyCreateWithData(uint8_t* pKey, CFStringRef keyClass = isPublic == 0 ? kSecAttrKeyClassPrivate : kSecAttrKeyClassPublic; CFStringRef keyType = GetKeyAlgorithmIdentifier(keyAlgorithm); - if (keyType == NULL) - { - CFRelease(dataAttributes); - return kErrorBadInput; - } + assert(keyType != NULL); CFDictionarySetValue(dataAttributes, kSecAttrKeyType, keyType); CFDictionarySetValue(dataAttributes, kSecAttrKeyClass, keyClass); @@ -199,15 +190,9 @@ int32_t AppleCryptoNative_SecKeyCopyExternalRepresentation(SecKeyRef pKey, CFDataRef* ppDataOut, CFErrorRef* pErrorOut) { - if (ppDataOut != NULL) - *ppDataOut = NULL; - if (pErrorOut != NULL) - *pErrorOut = NULL; - - if (pKey == NULL || ppDataOut == NULL || pErrorOut == NULL) - { - return kErrorBadInput; - } + assert(pKey != NULL); + assert(ppDataOut != NULL); + assert(pErrorOut != NULL); *ppDataOut = SecKeyCopyExternalRepresentation(pKey, pErrorOut); return *ppDataOut == NULL ? kErrorSeeError : 1; @@ -215,6 +200,7 @@ int32_t AppleCryptoNative_SecKeyCopyExternalRepresentation(SecKeyRef pKey, SecKeyRef AppleCryptoNative_SecKeyCopyPublicKey(SecKeyRef privateKey) { + assert(privateKey != NULL); + return SecKeyCopyPublicKey(privateKey); } -#endif diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.h index cfe5cbe1dff7c..ec99ff93e533f 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.h @@ -69,7 +69,6 @@ For ECC the value should not be used. */ PALEXPORT uint64_t AppleCryptoNative_SecKeyGetSimpleKeySizeInBytes(SecKeyRef publicKey); -#if defined(TARGET_MACCATALYST) || defined(TARGET_IOS) || defined(TARGET_TVOS) /* Create an iOS-style key from raw data. @@ -98,4 +97,3 @@ PALEXPORT int32_t AppleCryptoNative_SecKeyCopyExternalRepresentation(SecKeyRef p Return a corresponding public key from a private key. */ PALEXPORT SecKeyRef AppleCryptoNative_SecKeyCopyPublicKey(SecKeyRef privateKey); -#endif diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Unix.cs index 6c47439ef9634..58b7bcfa00f5f 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Unix.cs @@ -34,7 +34,7 @@ public bool ExplicitCurvesSupported } } - public bool CanDeriveNewPublicKey => !PlatformDetection.IsiOS && !PlatformDetection.IstvOS; + public bool CanDeriveNewPublicKey { get; } = !PlatformDetection.IsiOS && !PlatformDetection.IstvOS && !PlatformDetection.IsMacCatalyst; private static bool IsValueOrFriendlyNameValid(string friendlyNameOrValue) { From 0043eaddea48776e128ce24eeca26014e1a7dce1 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 28 Apr 2021 18:45:21 +0200 Subject: [PATCH 5/5] Address feedback --- .../System.Security.Cryptography.Native.Apple/pal_seckey.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.c index 1125ba9b989c2..78ae149a702af 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_seckey.c @@ -161,6 +161,9 @@ int32_t AppleCryptoNative_SecKeyCreateWithData(uint8_t* pKey, assert(pKeyOut != NULL); assert(pErrorOut != NULL); + *pKeyOut = NULL; + *pErrorOut = NULL; + CFMutableDictionaryRef dataAttributes = CFDictionaryCreateMutable( kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); @@ -194,6 +197,8 @@ int32_t AppleCryptoNative_SecKeyCopyExternalRepresentation(SecKeyRef pKey, assert(ppDataOut != NULL); assert(pErrorOut != NULL); + *pErrorOut = NULL; + *ppDataOut = SecKeyCopyExternalRepresentation(pKey, pErrorOut); return *ppDataOut == NULL ? kErrorSeeError : 1; }