diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ecc.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ecc.cs index fa96f3c3577aab..a5431a324677bf 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ecc.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ecc.cs @@ -1,11 +1,17 @@ // 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.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Swift; using System.Security.Cryptography; using System.Security.Cryptography.Apple; using Microsoft.Win32.SafeHandles; +using Swift.Runtime; + +#pragma warning disable CS3016 // Arrays as attribute arguments are not CLS Compliant internal static partial class Interop { @@ -21,6 +27,51 @@ private static partial int AppleCryptoNative_EccGenerateKey( [LibraryImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_EccGetKeySizeInBits")] internal static partial int EccGetKeySizeInBits(SafeSecKeyRefHandle publicKey); + [LibraryImport(Libraries.AppleCryptoNative)] + [UnmanagedCallConv(CallConvs = [ typeof(CallConvSwift) ])] + private static unsafe partial int AppleCryptoNative_EccExportPublicKeyFromPrivateKey( + int keySizeInBits, + UnsafeBufferPointer privateKey, + UnsafeMutableBufferPointer destination); + + internal static void EccExportPublicKeyFromPrivateKey( + int keySizeInBits, + ReadOnlySpan privateKey, + Span destination) + { + Debug.Assert(!privateKey.IsEmpty); + Debug.Assert(!destination.IsEmpty); + + const int Success = 1; + const int InvalidKey = 0; + + int result; + + unsafe + { + fixed (byte* privateKeyPtr = privateKey) + fixed (byte* destinationPtr = destination) + { + result = AppleCryptoNative_EccExportPublicKeyFromPrivateKey( + keySizeInBits, + new UnsafeBufferPointer(privateKeyPtr, privateKey.Length), + new UnsafeMutableBufferPointer(destinationPtr, destination.Length)); + } + } + + switch (result) + { + case Success: + return; + case InvalidKey: + throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey); + default: + Debug.Fail( + $"Unexpected result from {nameof(AppleCryptoNative_EccExportPublicKeyFromPrivateKey)}: {result}"); + throw new CryptographicException(); + } + } + internal static void EccGenerateKey( int keySizeInBits, out SafeSecKeyRefHandle pPublicKey, diff --git a/src/libraries/Common/src/System/Security/Cryptography/EccAppleCrypto.iOS.cs b/src/libraries/Common/src/System/Security/Cryptography/EccAppleCrypto.iOS.cs index 16785944f39ad6..0ba8cacc98aeef 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/EccAppleCrypto.iOS.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/EccAppleCrypto.iOS.cs @@ -15,9 +15,36 @@ internal sealed partial class EccAppleCrypto #pragma warning disable IDE0060 private static ECParameters ExportParametersFromLegacyKey(SecKeyPair keys, bool includePrivateParameters) => throw new CryptographicException(); +#pragma warning restore IDE0060 private static void ExtractPublicKeyFromPrivateKey(ref ECParameters ecParameters) - => throw new PlatformNotSupportedException(SR.Cryptography_NotValidPublicOrPrivateKey); -#pragma warning restore IDE0060 + { + int keySizeInBits = ecParameters.Curve.Oid.Value switch + { + Oids.secp256r1 => 256, + Oids.secp384r1 => 384, + Oids.secp521r1 => 521, + _ => throw DebugFail(), // Apple only supports NIST curves. + }; + + byte[] privateKey = ecParameters.D!; + int fieldSize = (keySizeInBits + 7) / 8; + Debug.Assert(privateKey.Length == fieldSize); + + const int MaxPublicKeySize = 136; // P-521 is 133 bytes, round this off to 136. + Span publicKey = (stackalloc byte[MaxPublicKeySize]).Slice(0, 1 + 2 * fieldSize); + Interop.AppleCrypto.EccExportPublicKeyFromPrivateKey(keySizeInBits, privateKey, publicKey); + AsymmetricAlgorithmHelpers.DecodeFromUncompressedAnsiX963Key( + publicKey, + hasPrivateKey: false, + out ECParameters publicParameters); + ecParameters.Q = publicParameters.Q; + + static Exception DebugFail() + { + Debug.Fail($"Unexpected curve with OID."); + return new CryptographicException(); + } + } } } diff --git a/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Unix.cs b/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Unix.cs index 7a79ec911cf1b4..980e52de11e504 100644 --- a/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Unix.cs +++ b/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Unix.cs @@ -32,7 +32,7 @@ public override bool ExplicitCurvesSupported } } - public override bool CanDeriveNewPublicKey => !PlatformDetection.IsiOS && !PlatformDetection.IstvOS && !PlatformDetection.IsMacCatalyst; + public override bool CanDeriveNewPublicKey => true; public override bool SupportsRawDerivation => true; public override bool SupportsSha3 => PlatformDetection.SupportsSha3; diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c index 65996b5471eaab..b94700bda7289f 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c @@ -41,6 +41,7 @@ static const Entry s_cryptoAppleNative[] = DllImportEntry(AppleCryptoNative_DigestOneShot) DllImportEntry(AppleCryptoNative_DigestReset) DllImportEntry(AppleCryptoNative_DigestUpdate) + DllImportEntry(AppleCryptoNative_EccExportPublicKeyFromPrivateKey) DllImportEntry(AppleCryptoNative_EccGenerateKey) DllImportEntry(AppleCryptoNative_EccGetKeySizeInBits) DllImportEntry(AppleCryptoNative_EcdhKeyAgree) diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.h b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.h index 1200ceeb13b10f..8de19d512dfe10 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.h +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.h @@ -14,6 +14,8 @@ EXTERN_C void* AppleCryptoNative_AesKeyWrapEncrypt; EXTERN_C void* AppleCryptoNative_AesKeyWrapDecrypt; EXTERN_C void* AppleCryptoNative_IsAuthenticationFailure; +EXTERN_C void* AppleCryptoNative_EccExportPublicKeyFromPrivateKey; + EXTERN_C void* AppleCryptoNative_HKDFDeriveKey; EXTERN_C void* AppleCryptoNative_HKDFExpand; EXTERN_C void* AppleCryptoNative_HKDFExtract; diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.swift b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.swift index 28a3da7e14aeee..fc056d05d0d6d7 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.swift +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.swift @@ -595,6 +595,49 @@ public func AppleCryptoNative_DigestCurrent(ctx: UnsafeMutableRawPointer?, pOutp return 1 } +@_silgen_name("AppleCryptoNative_EccExportPublicKeyFromPrivateKey") +public func AppleCryptoNative_EccExportPublicKeyFromPrivateKey( + keySizeInBits: Int32, + privateKey: UnsafeBufferPointer, + destination: UnsafeMutableBufferPointer) -> Int32 { + guard !privateKey.isEmpty, !destination.isEmpty else { + return -1 + } + + // The purpose of this method is to take an EC private scalar D and compute the public value, Q. For this + // limited purpose it doesn't matter if we use KeyAgreement or Signing because the result will be the same. + // This implementation just uses KeyAgreement. + let publicKey: Data + + switch keySizeInBits { + case 256: + guard let key = try? P256.KeyAgreement.PrivateKey(rawRepresentation: privateKey) else { + return 0 + } + publicKey = key.publicKey.x963Representation + case 384: + guard let key = try? P384.KeyAgreement.PrivateKey(rawRepresentation: privateKey) else { + return 0 + } + publicKey = key.publicKey.x963Representation + case 521: + guard let key = try? P521.KeyAgreement.PrivateKey(rawRepresentation: privateKey) else { + return 0 + } + publicKey = key.publicKey.x963Representation + default: + return -1 + } + + guard publicKey.count == destination.count else { + return -1 + } + + let copied = publicKey.copyBytes(to: destination) == publicKey.count + + return copied ? 1 : -1 +} + // Return values: // 1: success // 0: key agreement failed (e.g. peer is a low-order point and the shared