Skip to content
Merged
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
@@ -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<byte> prk,
ReadOnlySpan<byte> info,
Span<byte> 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<byte> ikm,
ReadOnlySpan<byte> salt,
Span<byte> 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<byte> ikm,
ReadOnlySpan<byte> salt,
ReadOnlySpan<byte> info,
Span<byte> 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<byte> prkPtr,
int prkLength,
ReadOnlySpan<byte> infoPtr,
int infoLength,
Span<byte> destinationPtr,
int destinationLength);

[LibraryImport(Libraries.AppleCryptoNative)]
[UnmanagedCallConv(CallConvs = [ typeof(CallConvSwift) ])]
private static partial int AppleCryptoNative_HKDFExtract(
PAL_HashAlgorithm hashAlgorithm,
ReadOnlySpan<byte> ikmPtr,
int ikmLength,
ReadOnlySpan<byte> saltPtr,
int saltLength,
Span<byte> destinationPtr,
int destinationLength);

[LibraryImport(Libraries.AppleCryptoNative)]
[UnmanagedCallConv(CallConvs = [ typeof(CallConvSwift) ])]
private static partial int AppleCryptoNative_HKDFDeriveKey(
PAL_HashAlgorithm hashAlgorithm,
ReadOnlySpan<byte> ikmPtr,
int ikmLength,
ReadOnlySpan<byte> saltPtr,
int saltLength,
ReadOnlySpan<byte> infoPtr,
int infoLength,
Span<byte> destinationPtr,
int destinationLength);
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,8 @@
Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Ecc.cs" />
<Compile Include="$(CommonPath)Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Err.cs"
Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Err.cs" />
<Compile Include="$(CommonPath)Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.HKDF.cs"
Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.HKDF.cs" />
<Compile Include="$(CommonPath)Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Hmac.cs"
Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Hmac.cs" />
<Compile Include="$(CommonPath)Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.KeyAgree.cs"
Expand Down Expand Up @@ -1406,7 +1408,7 @@
<Compile Include="System\Security\Cryptography\ECDsa.Create.AppleCrypto.cs" />
<Compile Include="System\Security\Cryptography\HashAlgorithmNames.Apple.cs" />
<Compile Include="System\Security\Cryptography\HashProviderDispenser.Apple.cs" />
<Compile Include="System\Security\Cryptography\HKDF.Managed.cs" />
<Compile Include="System\Security\Cryptography\HKDF.Apple.cs" />
<Compile Include="System\Security\Cryptography\LiteHash.Apple.cs" />
<Compile Include="System\Security\Cryptography\LiteHash.Kmac.Unsupported.cs" />
<Compile Include="System\Security\Cryptography\MLDsaOpenSsl.NotSupported.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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<byte> ikm,
ReadOnlySpan<byte> salt,
Span<byte> 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<byte> prk,
Span<byte> output,
ReadOnlySpan<byte> 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<byte> ikm,
Span<byte> output,
ReadOnlySpan<byte> salt,
ReadOnlySpan<byte> 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.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public static byte[] DeriveKey(HashAlgorithmName hashAlgorithmName, byte[] ikm,
/// <param name="output">The output buffer representing output keying material.</param>
/// <param name="salt">The salt value (a non-secret random value).</param>
/// <param name="info">The context and application specific information (can be an empty span).</param>
/// <exception cref="ArgumentException"><paramref name="ikm"/> is empty, or is larger than the maximum allowed length.</exception>
/// <exception cref="ArgumentException"><paramref name="output"/> is empty, or is larger than the maximum allowed length.</exception>
public static void DeriveKey(HashAlgorithmName hashAlgorithmName, ReadOnlySpan<byte> ikm, Span<byte> output, ReadOnlySpan<byte> salt, ReadOnlySpan<byte> info)
{
int hashLength = Helpers.HashLength(hashAlgorithmName);
Expand Down
77 changes: 77 additions & 0 deletions src/libraries/System.Security.Cryptography/tests/HKDFTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -409,6 +412,80 @@ public static IEnumerable<object[]> GetPrkTooShortTestCases()
},
};

public static IEnumerable<object[]> 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<object[]> Sha3TestCases
{
// These cases were generated from the openssl kdf command.
Expand Down
Loading
Loading