Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -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<byte> privateKey,
UnsafeMutableBufferPointer<byte> destination);

internal static void EccExportPublicKeyFromPrivateKey(
int keySizeInBits,
ReadOnlySpan<byte> privateKey,
Span<byte> 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<byte>(privateKeyPtr, privateKey.Length),
new UnsafeMutableBufferPointer<byte>(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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte> 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();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<UInt8>,
destination: UnsafeMutableBufferPointer<UInt8>) -> 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
Expand Down
Loading