diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.HKDF.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.HKDF.cs new file mode 100644 index 00000000000000..cc826b5a4f6235 --- /dev/null +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.HKDF.cs @@ -0,0 +1,140 @@ +// 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.Runtime.Versioning; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using Swift.Runtime; + +#pragma warning disable CS3016 // Arrays as attribute arguments are not CLS Compliant + +internal static partial class Interop +{ + internal static partial class AppleCrypto + { + internal static unsafe void HkdfExpand( + HashAlgorithmName hashAlgorithm, + ReadOnlySpan prk, + ReadOnlySpan info, + Span destination) + { + Debug.Assert(!destination.IsEmpty); + Debug.Assert(!prk.IsEmpty); + + PAL_HashAlgorithm algorithm = PalAlgorithmFromAlgorithmName(hashAlgorithm); + + int ret = AppleCryptoNative_HKDFExpand( + algorithm, + prk, + prk.Length, + info, + info.Length, + destination, + destination.Length); + + if (ret < 0) + { + throw new CryptographicException(); + } + + Debug.Assert(ret == destination.Length); + } + + internal static unsafe void HKDFExtract( + HashAlgorithmName hashAlgorithm, + ReadOnlySpan ikm, + ReadOnlySpan salt, + Span destination) + { + Debug.Assert(!destination.IsEmpty); + + PAL_HashAlgorithm algorithm = PalAlgorithmFromAlgorithmName(hashAlgorithm); + + int ret = AppleCryptoNative_HKDFExtract( + algorithm, + ikm, + ikm.Length, + salt, + salt.Length, + destination, + destination.Length); + + if (ret < 0) + { + throw new CryptographicException(); + } + + Debug.Assert(ret == destination.Length); + } + + internal static unsafe void HKDFDeriveKey( + HashAlgorithmName hashAlgorithm, + ReadOnlySpan ikm, + ReadOnlySpan salt, + ReadOnlySpan info, + Span destination) + { + Debug.Assert(!destination.IsEmpty); + + PAL_HashAlgorithm algorithm = PalAlgorithmFromAlgorithmName(hashAlgorithm); + + int ret = AppleCryptoNative_HKDFDeriveKey( + algorithm, + ikm, + ikm.Length, + salt, + salt.Length, + info, + info.Length, + destination, + destination.Length); + + if (ret < 0) + { + throw new CryptographicException(); + } + + Debug.Assert(ret == destination.Length); + } + + [LibraryImport(Libraries.AppleCryptoNative)] + [UnmanagedCallConv(CallConvs = [ typeof(CallConvSwift) ])] + private static partial int AppleCryptoNative_HKDFExpand( + PAL_HashAlgorithm hashAlgorithm, + ReadOnlySpan prkPtr, + int prkLength, + ReadOnlySpan infoPtr, + int infoLength, + Span destinationPtr, + int destinationLength); + + [LibraryImport(Libraries.AppleCryptoNative)] + [UnmanagedCallConv(CallConvs = [ typeof(CallConvSwift) ])] + private static partial int AppleCryptoNative_HKDFExtract( + PAL_HashAlgorithm hashAlgorithm, + ReadOnlySpan ikmPtr, + int ikmLength, + ReadOnlySpan saltPtr, + int saltLength, + Span destinationPtr, + int destinationLength); + + [LibraryImport(Libraries.AppleCryptoNative)] + [UnmanagedCallConv(CallConvs = [ typeof(CallConvSwift) ])] + private static partial int AppleCryptoNative_HKDFDeriveKey( + PAL_HashAlgorithm hashAlgorithm, + ReadOnlySpan ikmPtr, + int ikmLength, + ReadOnlySpan saltPtr, + int saltLength, + ReadOnlySpan infoPtr, + int infoLength, + Span destinationPtr, + int destinationLength); + } +} diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.PAL_HashAlgorithm.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.PAL_HashAlgorithm.cs index 8a09c06c2e525c..e25ebf3789815f 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.PAL_HashAlgorithm.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.PAL_HashAlgorithm.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Security.Cryptography; + internal static partial class Interop { internal static partial class AppleCrypto @@ -14,5 +17,13 @@ internal enum PAL_HashAlgorithm Sha384, Sha512, } + + private static PAL_HashAlgorithm PalAlgorithmFromAlgorithmName(HashAlgorithmName hashAlgorithmName) => + hashAlgorithmName == HashAlgorithmName.MD5 ? PAL_HashAlgorithm.Md5 : + hashAlgorithmName == HashAlgorithmName.SHA1 ? PAL_HashAlgorithm.Sha1 : + hashAlgorithmName == HashAlgorithmName.SHA256 ? PAL_HashAlgorithm.Sha256 : + hashAlgorithmName == HashAlgorithmName.SHA384 ? PAL_HashAlgorithm.Sha384 : + hashAlgorithmName == HashAlgorithmName.SHA512 ? PAL_HashAlgorithm.Sha512 : + throw new CryptographicException(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmName.Name); } } diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.RSA.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.RSA.cs index deab51eeb2bbda..95a861db6ebb98 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.RSA.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.RSA.cs @@ -348,13 +348,5 @@ ref MemoryMarshal.GetReference(source), return ProcessPrimitiveResponse(returnValue, cfData, cfError, destination, out bytesWritten); } - - private static PAL_HashAlgorithm PalAlgorithmFromAlgorithmName(HashAlgorithmName hashAlgorithmName) => - hashAlgorithmName == HashAlgorithmName.MD5 ? PAL_HashAlgorithm.Md5 : - hashAlgorithmName == HashAlgorithmName.SHA1 ? PAL_HashAlgorithm.Sha1 : - hashAlgorithmName == HashAlgorithmName.SHA256 ? PAL_HashAlgorithm.Sha256 : - hashAlgorithmName == HashAlgorithmName.SHA384 ? PAL_HashAlgorithm.Sha384 : - hashAlgorithmName == HashAlgorithmName.SHA512 ? PAL_HashAlgorithm.Sha512 : - throw new CryptographicException(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmName.Name); } } diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index 13ae5bdccba4f2..e83a0f09a8c95f 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -1334,6 +1334,8 @@ Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Ecc.cs" /> + - + diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HKDF.Apple.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HKDF.Apple.cs new file mode 100644 index 00000000000000..00f6d36469124e --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HKDF.Apple.cs @@ -0,0 +1,86 @@ +// 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; + +namespace System.Security.Cryptography +{ + public static partial class HKDF + { + private static readonly bool s_hasCryptoKitImplementation = + OperatingSystem.IsMacOS() || + OperatingSystem.IsMacCatalyst() || + OperatingSystem.IsIOSVersionAtLeast(14) || + OperatingSystem.IsTvOSVersionAtLeast(14); + + private static void Extract( + HashAlgorithmName hashAlgorithmName, + int hashLength, + ReadOnlySpan ikm, + ReadOnlySpan salt, + Span prk) + { + ThrowForUnsupportedHashAlgorithm(hashAlgorithmName); + + if (s_hasCryptoKitImplementation) + { + Interop.AppleCrypto.HKDFExtract(hashAlgorithmName, ikm, salt, prk); + } + else + { + HKDFManagedImplementation.Extract(hashAlgorithmName, hashLength, ikm, salt, prk); + } + } + + private static void Expand( + HashAlgorithmName hashAlgorithmName, + int hashLength, + ReadOnlySpan prk, + Span output, + ReadOnlySpan info) + { + ThrowForUnsupportedHashAlgorithm(hashAlgorithmName); + + if (s_hasCryptoKitImplementation) + { + Interop.AppleCrypto.HkdfExpand(hashAlgorithmName, prk, info, output); + } + else + { + HKDFManagedImplementation.Expand(hashAlgorithmName, hashLength, prk, output, info); + } + } + + private static void DeriveKeyCore( + HashAlgorithmName hashAlgorithmName, + int hashLength, + ReadOnlySpan ikm, + Span output, + ReadOnlySpan salt, + ReadOnlySpan info) + { + ThrowForUnsupportedHashAlgorithm(hashAlgorithmName); + + if (s_hasCryptoKitImplementation) + { + Interop.AppleCrypto.HKDFDeriveKey(hashAlgorithmName, ikm, salt, info, output); + } + else + { + HKDFManagedImplementation.DeriveKey(hashAlgorithmName, hashLength, ikm, output, salt, info); + } + } + + private static void ThrowForUnsupportedHashAlgorithm(HashAlgorithmName hashAlgorithmName) + { + if (hashAlgorithmName == HashAlgorithmName.SHA3_256 || hashAlgorithmName == HashAlgorithmName.SHA3_384 || + hashAlgorithmName == HashAlgorithmName.SHA3_512) + { + throw new PlatformNotSupportedException(); + } + + // Unknown algorithms are handled outside of this as a CryptographicException. SHA-3 is known, it's just + // not supported. + } + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HKDF.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HKDF.cs index f167f18afc2466..454e0a77303477 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HKDF.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HKDF.cs @@ -171,7 +171,7 @@ public static byte[] DeriveKey(HashAlgorithmName hashAlgorithmName, byte[] ikm, /// The output buffer representing output keying material. /// The salt value (a non-secret random value). /// The context and application specific information (can be an empty span). - /// is empty, or is larger than the maximum allowed length. + /// is empty, or is larger than the maximum allowed length. public static void DeriveKey(HashAlgorithmName hashAlgorithmName, ReadOnlySpan ikm, Span output, ReadOnlySpan salt, ReadOnlySpan info) { int hashLength = Helpers.HashLength(hashAlgorithmName); diff --git a/src/libraries/System.Security.Cryptography/tests/HKDFTests.cs b/src/libraries/System.Security.Cryptography/tests/HKDFTests.cs index 460b81c9435727..a656ec50c052de 100644 --- a/src/libraries/System.Security.Cryptography/tests/HKDFTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/HKDFTests.cs @@ -19,6 +19,7 @@ public abstract class HKDFTests [Theory] [MemberData(nameof(GetHkdfTestCases))] + [MemberData(nameof(SupplementalTestCases))] public void ExtractTests(HkdfTestCase test) { byte[] prk = Extract(test.Hash, test.Prk.Length, test.Ikm, test.Salt); @@ -95,6 +96,7 @@ public void ExtractEmptySalt() [Theory] [MemberData(nameof(GetHkdfTestCases))] + [MemberData(nameof(SupplementalTestCases))] public void ExpandTests(HkdfTestCase test) { byte[] okm = Expand(test.Hash, test.Prk, test.Okm.Length, test.Info); @@ -151,6 +153,7 @@ public void ExpandOkmMaxSize() [Theory] [MemberData(nameof(GetHkdfTestCases))] + [MemberData(nameof(SupplementalTestCases))] public void DeriveKeyTests(HkdfTestCase test) { byte[] okm = DeriveKey(test.Hash, test.Ikm, test.Okm.Length, test.Salt, test.Info); @@ -409,6 +412,80 @@ public static IEnumerable GetPrkTooShortTestCases() }, }; + public static IEnumerable SupplementalTestCases + { + get + { + if (MD5Supported) + { + yield return new object[] + { + // Generated + // openssl kdf -keylen 16 -kdfopt digest:MD5 -kdfopt hexkey:000102030405060708090A0B0C0D0E0F \ + // -kdfopt hexsalt:101112131415161718191A1B1C1D1E1F -kdfopt hexinfo:202122232425262728292A2B2C2D2E2F \ + // -binary HKDF | xxd -p + new HkdfTestCase() + { + Name = "Test with MD5, salt, and info", + Hash = HashAlgorithmName.MD5, + Ikm = "000102030405060708090A0B0C0D0E0F".HexToByteArray(), + Salt = "101112131415161718191A1B1C1D1E1F".HexToByteArray(), + Info = "202122232425262728292A2B2C2D2E2F".HexToByteArray(), + Okm = "5a25e9d9d27578f28a79a680fd9ce780".HexToByteArray(), + + // Add -kdfopt mode:EXTRACT_ONLY to derive the PRK. + Prk = "2d2d573fd48c9ad0be5e8214af0d7d64".HexToByteArray(), + + } + }; + } + + yield return new object[] + { + // Generated + // openssl kdf -keylen 16 -kdfopt digest:SHA384 -kdfopt hexkey:000102030405060708090A0B0C0D0E0F \ + // -kdfopt hexsalt:101112131415161718191A1B1C1D1E1F -kdfopt hexinfo:202122232425262728292A2B2C2D2E2F \ + // -binary HKDF | xxd -p + new HkdfTestCase() + { + Name = "Test with SHA-2-384, salt, and info", + Hash = HashAlgorithmName.SHA384, + Ikm = "000102030405060708090A0B0C0D0E0F".HexToByteArray(), + Salt = "101112131415161718191A1B1C1D1E1F".HexToByteArray(), + Info = "202122232425262728292A2B2C2D2E2F".HexToByteArray(), + Okm = "5b3a502e2ce5d366479afa17a4fe4aaa".HexToByteArray(), + + // Add -kdfopt mode:EXTRACT_ONLY to derive the PRK. + Prk = ("31ca88a527220f8271d78df4ce6c4d973f135ad37973b966" + + "44b4d52d499d0a2b03d53c875b1176b089e1e6161ab6d92b").HexToByteArray(), + + } + }; + + yield return new object[] + { + // Generated + // openssl kdf -keylen 16 -kdfopt digest:SHA512 -kdfopt hexkey:000102030405060708090A0B0C0D0E0F \ + // -kdfopt hexsalt:101112131415161718191A1B1C1D1E1F -kdfopt hexinfo:202122232425262728292A2B2C2D2E2F \ + // -binary HKDF | xxd -p + new HkdfTestCase() + { + Name = "Test with SHA-2-512, salt, and info", + Hash = HashAlgorithmName.SHA512, + Ikm = "000102030405060708090A0B0C0D0E0F".HexToByteArray(), + Salt = "101112131415161718191A1B1C1D1E1F".HexToByteArray(), + Info = "202122232425262728292A2B2C2D2E2F".HexToByteArray(), + Okm = "270b6ba8a5989e4c26c8d116930c14ab".HexToByteArray(), + + // Add -kdfopt mode:EXTRACT_ONLY to derive the PRK. + Prk = ("f6e6b1ddb24ea0f0ede0f533d1f350c86bf78966b0e5fd2af34dd00dae3901d6" + + "279fe8111d6572e3cd05f2f0eeabb9144dc0da9437cdf37b0c6d7f3b1064ab2b").HexToByteArray(), + + } + }; + } + } + public static IEnumerable Sha3TestCases { // These cases were generated from the openssl kdf command. 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 d968ed2e127cf3..1958ac1ca51f56 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c @@ -43,6 +43,9 @@ static const Entry s_cryptoAppleNative[] = DllImportEntry(AppleCryptoNative_EccGetKeySizeInBits) DllImportEntry(AppleCryptoNative_EcdhKeyAgree) DllImportEntry(AppleCryptoNative_GetRandomBytes) + DllImportEntry(AppleCryptoNative_HKDFDeriveKey) + DllImportEntry(AppleCryptoNative_HKDFExpand) + DllImportEntry(AppleCryptoNative_HKDFExtract) DllImportEntry(AppleCryptoNative_HmacClone) DllImportEntry(AppleCryptoNative_HmacCreate) DllImportEntry(AppleCryptoNative_HmacCurrent) 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 058c94869b57db..40f9ac92b8db78 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 @@ -11,3 +11,7 @@ EXTERN_C void* AppleCryptoNative_ChaCha20Poly1305Decrypt; EXTERN_C void* AppleCryptoNative_AesGcmEncrypt; EXTERN_C void* AppleCryptoNative_AesGcmDecrypt; EXTERN_C void* AppleCryptoNative_IsAuthenticationFailure; + +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 70a7176c7d7790..0915b647c33f71 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 @@ -118,7 +118,7 @@ public func AppleCryptoNative_ChaCha20Poly1305Encrypt( cipherText: cipherText, tag: tag, aad: aad) - } +} @_silgen_name("AppleCryptoNative_ChaCha20Poly1305Decrypt") @available(iOS 13, tvOS 13, *) @@ -158,7 +158,7 @@ public func AppleCryptoNative_AesGcmEncrypt( cipherText: cipherText, tag: tag, aad: aad) - } +} @_silgen_name("AppleCryptoNative_AesGcmDecrypt") @available(iOS 13, tvOS 13, *) @@ -193,3 +193,160 @@ public func AppleCryptoNative_IsAuthenticationFailure(error: Error) -> Bool { } return false } + +// Must remain in sync with PAL_HashAlgorithm from managed side. +enum PAL_HashAlgorithm: Int32 { + case unknown = 0 + case md5 = 1 + case sha1 = 2 + case sha256 = 3 + case sha384 = 4 + case sha512 = 5 +} + +enum HKDFError: Error { + case unknownHashAlgorithm +} + +@_silgen_name("AppleCryptoNative_HKDFExpand") +@available(iOS 14, tvOS 14, *) +public func AppleCryptoNative_HKDFExpand( + hashAlgorithm: Int32, + prkPtr: UnsafeMutableRawPointer, + prkLength: Int32, + infoPtr: UnsafeMutableRawPointer, + infoLength: Int32, + destinationPtr: UnsafeMutablePointer, + destinationLength: Int32) -> Int32 { + + let prk = Data(bytesNoCopy: prkPtr, count: Int(prkLength), deallocator: Data.Deallocator.none) + let info = Data(bytesNoCopy: infoPtr, count: Int(infoLength), deallocator: Data.Deallocator.none) + let destinationLengthInt = Int(destinationLength) + + guard let algorithm = PAL_HashAlgorithm(rawValue: hashAlgorithm) else { + return -2 + } + + let keyFactory : () throws -> ContiguousBytes = { + switch algorithm { + case .unknown: + throw HKDFError.unknownHashAlgorithm + case .md5: + return HKDF.expand(pseudoRandomKey: prk, info: info, outputByteCount: destinationLengthInt) + case .sha1: + return HKDF.expand(pseudoRandomKey: prk, info: info, outputByteCount: destinationLengthInt) + case .sha256: + return HKDF.expand(pseudoRandomKey: prk, info: info, outputByteCount: destinationLengthInt) + case .sha384: + return HKDF.expand(pseudoRandomKey: prk, info: info, outputByteCount: destinationLengthInt) + case .sha512: + return HKDF.expand(pseudoRandomKey: prk, info: info, outputByteCount: destinationLengthInt) + } + } + + guard let key = try? keyFactory() else { + return -1 + } + + return key.withUnsafeBytes { keyBytes in + let destination = UnsafeMutableRawBufferPointer(start: destinationPtr, count: destinationLengthInt) + return Int32(keyBytes.copyBytes(to: destination)) + } +} + +@_silgen_name("AppleCryptoNative_HKDFExtract") +@available(iOS 14, tvOS 14, *) +public func AppleCryptoNative_HKDFExtract( + hashAlgorithm: Int32, + ikmPtr: UnsafeMutableRawPointer, + ikmLength: Int32, + saltPtr: UnsafeMutableRawPointer, + saltLength: Int32, + destinationPtr: UnsafeMutablePointer, + destinationLength: Int32) -> Int32 { + + let ikm = Data(bytesNoCopy: ikmPtr, count: Int(ikmLength), deallocator: Data.Deallocator.none) + let salt = Data(bytesNoCopy: saltPtr, count: Int(saltLength), deallocator: Data.Deallocator.none) + let destinationLengthInt = Int(destinationLength) + let key = SymmetricKey(data: ikm) + + guard let algorithm = PAL_HashAlgorithm(rawValue: hashAlgorithm) else { + return -2 + } + + let prkFactory : () throws -> ContiguousBytes = { + switch algorithm { + case .unknown: + throw HKDFError.unknownHashAlgorithm + case .md5: + return HKDF.extract(inputKeyMaterial: key, salt: salt) + case .sha1: + return HKDF.extract(inputKeyMaterial: key, salt: salt) + case .sha256: + return HKDF.extract(inputKeyMaterial: key, salt: salt) + case .sha384: + return HKDF.extract(inputKeyMaterial: key, salt: salt) + case .sha512: + return HKDF.extract(inputKeyMaterial: key, salt: salt) + } + } + + guard let prk = try? prkFactory() else { + return -1 + } + + return prk.withUnsafeBytes { prkBytes in + let destination = UnsafeMutableRawBufferPointer(start: destinationPtr, count: destinationLengthInt) + return Int32(prkBytes.copyBytes(to: destination)) + } +} + +@_silgen_name("AppleCryptoNative_HKDFDeriveKey") +@available(iOS 14, tvOS 14, *) +public func AppleCryptoNative_HKDFDeriveKey( + hashAlgorithm: Int32, + ikmPtr: UnsafeMutableRawPointer, + ikmLength: Int32, + saltPtr: UnsafeMutableRawPointer, + saltLength: Int32, + infoPtr: UnsafeMutableRawPointer, + infoLength: Int32, + destinationPtr: UnsafeMutablePointer, + destinationLength: Int32) -> Int32 { + + let ikm = Data(bytesNoCopy: ikmPtr, count: Int(ikmLength), deallocator: Data.Deallocator.none) + let salt = Data(bytesNoCopy: saltPtr, count: Int(saltLength), deallocator: Data.Deallocator.none) + let info = Data(bytesNoCopy: infoPtr, count: Int(infoLength), deallocator: Data.Deallocator.none) + let destinationLengthInt = Int(destinationLength) + let key = SymmetricKey(data: ikm) + + guard let algorithm = PAL_HashAlgorithm(rawValue: hashAlgorithm) else { + return -2 + } + + let derivedKeyFactory : () throws -> ContiguousBytes = { + switch algorithm { + case .unknown: + throw HKDFError.unknownHashAlgorithm + case .md5: + return HKDF.deriveKey(inputKeyMaterial: key, salt: salt, info: info, outputByteCount: destinationLengthInt) + case .sha1: + return HKDF.deriveKey(inputKeyMaterial: key, salt: salt, info: info, outputByteCount: destinationLengthInt) + case .sha256: + return HKDF.deriveKey(inputKeyMaterial: key, salt: salt, info: info, outputByteCount: destinationLengthInt) + case .sha384: + return HKDF.deriveKey(inputKeyMaterial: key, salt: salt, info: info, outputByteCount: destinationLengthInt) + case .sha512: + return HKDF.deriveKey(inputKeyMaterial: key, salt: salt, info: info, outputByteCount: destinationLengthInt) + } + } + + guard let derivedKey = try? derivedKeyFactory() else { + return -1 + } + + return derivedKey.withUnsafeBytes { keyBytes in + let destination = UnsafeMutableRawBufferPointer(start: destinationPtr, count: destinationLengthInt) + return Int32(keyBytes.copyBytes(to: destination)) + } +}