Tweaks and cleanup for PR #164

- Fully span-ify CalculateCmac and update callers.
- Modify CalculateCmac signature to match Nintendo's.
- Avoid doing stackalloc based on an input length parameter.
- Use ReadOnlySpan instead of Span where appropriate.
- Standardize XML comments to use triple slashes.
- Make use of the entire rented buffer when copying.

Unrelated to the PR
- Slip in a missed enum to string conversion in hactoolnet for CoreRT reflection-free mode.
This commit is contained in:
Alex Barney 2020-09-05 21:10:39 -07:00
parent 265147b678
commit 49711b12db
9 changed files with 88 additions and 81 deletions

View file

@ -1,6 +1,6 @@
// ReSharper disable AssignmentIsFullyDiscarded
using System;
using LibHac.Diag;
#if HAS_INTRINSICS
using LibHac.Crypto.Detail;
@ -266,83 +266,72 @@ namespace LibHac.Crypto
cipher.Transform(input, output);
}
/**
* <param name="k">A byte span containing the 128-bit key used in the AES-CBC-128 steps</param>
* <param name="m">A byte span containing the message to be authenticated</param>
* <param name="mIndex">The offset within the byte span at which the message will be read from</param>
* <param name="t">A byte span to output the message authentication code into</param>
* <param name="tIndex">The offset within the byte span at which the authentication code will be written to</param>
* <param name="len">The length of the message</param>
* <remarks>https://tools.ietf.org/html/rfc4493</remarks>
*/
public static void CalculateCmac(Span<byte> k, Span<byte> m, int mIndex, Span<byte> t, int tIndex, int len)
/// <summary>
/// Computes the CMAC of the provided data using AES-128.
/// </summary>
/// <param name="mac">The buffer where the generated MAC will be placed. Must be at least 16 bytes long.</param>
/// <param name="data">The message on which the MAC will be calculated.</param>
/// <param name="key">The 128-bit AES key used to calculate the MAC.</param>
/// <remarks>https://tools.ietf.org/html/rfc4493</remarks>
public static void CalculateCmac(Span<byte> mac, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
{
ReadOnlySpan<byte> zero = stackalloc byte[16];
int len = data.Length;
// Step 1, AES-128 with key K is applied to an all-zero input block.
Span<byte> l = stackalloc byte[16];
EncryptCbc128(zero, l, k, zero);
EncryptCbc128(zero, l, key, zero);
// Step 2, K1 is derived through the following operation:
Span<byte> k1 = LeftShiftBytes(l);
Span<byte> k1 = stackalloc byte[16];
LeftShiftBytes(l, k1);
if ((l[0] & 0x80) == 0x80) // If the most significant bit of L is equal to 0, K1 is the left-shift of L by 1 bit.
k1[15] ^= 0x87; // Otherwise, K1 is the XOR of const_Rb and the left-shift of L by 1 bit.
// Step 3, K2 is derived through the following operation:
Span<byte> k2 = LeftShiftBytes(k1);
Span<byte> k2 = stackalloc byte[16];
LeftShiftBytes(k1, k2);
if ((k1[0] & 0x80) == 0x80) // If the most significant bit of K1 is equal to 0, K2 is the left-shift of K1 by 1 bit.
k2[15] ^= 0x87; // Otherwise, K2 is the XOR of const_Rb and the left-shift of K1 by 1 bit.
k2[15] ^= 0x87; // Otherwise, K2 is the XOR of const_Rb and the left-shift of K1 by 1 bit.
// ReSharper disable once RedundantAssignment
Span<byte> paddedMessage = l;
if (len != 0 && len % 16 == 0) // If the size of the input message block is equal to a positive multiple of the block size (namely, 128 bits),
{ // the last block shall be XOR'ed with K1 before processing
Span<byte> message = stackalloc byte[len];
m.Slice(mIndex, len).CopyTo(message);
paddedMessage = len < 0x800 ? stackalloc byte[len] : new byte[len];
data.CopyTo(paddedMessage);
for (int j = 0; j < k1.Length; j++)
message[message.Length - 16 + j] ^= k1[j];
Span<byte> encResult = stackalloc byte[message.Length];
EncryptCbc128(message, encResult, k, zero); // The result of the previous process will be the input of the last encryption.
encResult.Slice(message.Length - 0x10).CopyTo(t.Slice(tIndex));
paddedMessage[paddedMessage.Length - 16 + j] ^= k1[j];
}
else // Otherwise, the last block shall be padded with 10^i and XOR'ed with K2.
{
Span<byte> message = stackalloc byte[len + (16 - len % 16)];
message[len] = 0x80;
m.Slice(mIndex, len).CopyTo(message);
int paddedLength = len + (16 - len % 16);
paddedMessage = paddedLength < 0x800 ? stackalloc byte[paddedLength] : new byte[paddedLength];
paddedMessage[len] = 0x80;
data.CopyTo(paddedMessage);
for (int j = 0; j < k2.Length; j++)
message[message.Length - 16 + j] ^= k2[j];
Span<byte> encResult = stackalloc byte[message.Length];
EncryptCbc128(message, encResult, k, zero); // The result of the previous process will be the input of the last encryption.
encResult.Slice(message.Length - 0x10).CopyTo(t.Slice(tIndex));
paddedMessage[paddedMessage.Length - 16 + j] ^= k2[j];
}
EncryptCbc128(paddedMessage, paddedMessage, key, zero); // The result of the previous process will be the input of the last encryption.
paddedMessage.Slice(paddedMessage.Length - 0x10).CopyTo(mac);
}
/**
* <param name="k">A byte span containing the 128-bit key used in the AES-CBC-128 steps</param>
* <param name="m">A byte span containing the message to be authenticated</param>
* <param name="t">A byte span to output the message authentication code into</param>
* <remarks>https://tools.ietf.org/html/rfc4493</remarks>
*/
public static void CalculateCmac(Span<byte> k, Span<byte> m, Span<byte> t) =>
CalculateCmac(k, m, 0, t, 0, m.Length);
private static byte[] LeftShiftBytes(Span<byte> bytes)
private static void LeftShiftBytes(ReadOnlySpan<byte> input, Span<byte> output)
{
var shifted = new byte[bytes.Length];
Assert.AssertTrue(output.Length >= input.Length);
byte carry = 0;
for (var i = bytes.Length - 1; i >= 0; i--)
for (int i = input.Length - 1; i >= 0; i--)
{
var b = (ushort)(bytes[i] << 1);
shifted[i] = (byte)((b & 0xff) + carry);
ushort b = (ushort)(input[i] << 1);
output[i] = (byte)((b & 0xff) + carry);
carry = (byte)((b & 0xff00) >> 8);
}
return shifted;
}
}
}

View file

@ -50,11 +50,9 @@ namespace LibHac.Crypto
}
}
/**
* <param name="n">The RSA Modulus (n)</param>
* <param name="e">The RSA Public Exponent (e)</param>
* <param name="d">The RSA Private Exponent (d)</param>
*/
/// <param name="n">The RSA Modulus (n)</param>
/// <param name="e">The RSA Public Exponent (e)</param>
/// <param name="d">The RSA Private Exponent (d)</param>
public static RSAParameters RecoverParameters(BigInteger n, BigInteger e, BigInteger d)
{
(BigInteger p, BigInteger q) = DeriveRsaPrimeNumberPair(n, e, d);
@ -79,23 +77,19 @@ namespace LibHac.Crypto
};
}
/**
* <param name="n">The RSA Modulus (n)</param>
* <param name="e">The RSA Public Exponent (e)</param>
* <param name="d">The RSA Private Exponent (d)</param>
*/
/// <param name="n">The RSA Modulus (n)</param>
/// <param name="e">The RSA Public Exponent (e)</param>
/// <param name="d">The RSA Private Exponent (d)</param>
public static RSAParameters RecoverParameters(ReadOnlySpan<byte> n, ReadOnlySpan<byte> e, ReadOnlySpan<byte> d) =>
RecoverParameters(n.GetBigInteger(), e.GetBigInteger(), d.GetBigInteger());
/**
* <summary>
* Derive RSA Prime Number Pair (p, q) from RSA Modulus (n), RSA Public Exponent (e) and RSA Private Exponent (d)
* </summary>
* <param name="n">The RSA Modulus (n)</param>
* <param name="e">The RSA Public Exponent (e)</param>
* <param name="d">The RSA Private Exponent (d)</param>
* <returns>RSA Prime Number Pair</returns>
*/
/// <summary>
/// Derive RSA Prime Number Pair (p, q) from RSA Modulus (n), RSA Public Exponent (e) and RSA Private Exponent (d)
/// </summary>
/// <param name="n">The RSA Modulus (n)</param>
/// <param name="e">The RSA Public Exponent (e)</param>
/// <param name="d">The RSA Private Exponent (d)</param>
/// <returns>RSA Prime Number Pair</returns>
private static (BigInteger p, BigInteger q) DeriveRsaPrimeNumberPair(BigInteger n, BigInteger e, BigInteger d)
{
BigInteger k = d * e - BigInteger.One;

View file

@ -91,11 +91,11 @@ namespace LibHac.FsSystem.Save
private Validity ValidateSignature(Keyset keyset)
{
var calculatedCmac = new byte[0x10];
Span<byte> calculatedCmac = stackalloc byte[0x10];
Aes.CalculateCmac(keyset.SaveMacKey, Data, 0x100, calculatedCmac, 0, 0x200);
Aes.CalculateCmac(calculatedCmac, Data.AsSpan(0x100, 0x200), keyset.SaveMacKey);
return Utilities.ArraysEqual(calculatedCmac, Cmac) ? Validity.Valid : Validity.Invalid;
return CryptoUtil.IsSameBytes(calculatedCmac, Cmac, Aes.BlockSize) ? Validity.Valid : Validity.Invalid;
}
}

View file

@ -269,7 +269,7 @@ namespace LibHac.FsSystem.Save
headerStream.Position = 0x100;
headerStream.Read(cmacData, 0, 0x200);
Aes.CalculateCmac(keyset.SaveMacKey, cmacData, 0, cmac, 0, 0x200);
Aes.CalculateCmac(cmac, cmacData, keyset.SaveMacKey);
headerStream.Position = 0;
headerStream.Write(cmac, 0, 0x10);

View file

@ -83,10 +83,12 @@ namespace LibHac.FsSystem
long pos = 0;
using var buffer = new RentedArray<byte>(bufferSize);
int rentedBufferSize = buffer.Array.Length;
while (remaining > 0)
{
int toCopy = (int)Math.Min(bufferSize, remaining);
Span<byte> buf = buffer.Span.Slice(0, toCopy);
int toCopy = (int)Math.Min(rentedBufferSize, remaining);
Span<byte> buf = buffer.Array.AsSpan(0, toCopy);
input.Read(pos, buf);
output.Write(pos, buf);
@ -132,12 +134,14 @@ namespace LibHac.FsSystem
long pos = offset;
using var buffer = new RentedArray<byte>(bufferSize);
buffer.Span.Slice(0, (int)Math.Min(remaining, bufferSize)).Fill(value);
int rentedBufferSize = buffer.Array.Length;
buffer.Array.AsSpan(0, (int)Math.Min(remaining, rentedBufferSize)).Fill(value);
while (remaining > 0)
{
int toFill = (int)Math.Min(bufferSize, remaining);
Span<byte> buf = buffer.Span.Slice(0, toFill);
int toFill = (int)Math.Min(rentedBufferSize, remaining);
Span<byte> buf = buffer.Array.AsSpan(0, toFill);
input.Write(pos, buf);
@ -188,14 +192,16 @@ namespace LibHac.FsSystem
{
long remaining = length;
long inOffset = 0;
using var buffer = new RentedArray<byte>(bufferSize);
int rentedBufferSize = buffer.Array.Length;
progress?.SetTotal(length);
while (remaining > 0)
{
int toWrite = (int)Math.Min(bufferSize, remaining);
input.Read(inOffset, buffer.Span.Slice(0, toWrite));
int toWrite = (int)Math.Min(rentedBufferSize, remaining);
input.Read(inOffset, buffer.Array.AsSpan(0, toWrite));
output.Write(buffer.Array, 0, toWrite);
remaining -= toWrite;

View file

@ -350,7 +350,7 @@ namespace LibHac
}
Array.Copy(EncryptedKeyblobs[i], expectedCmac, 0x10);
Aes.CalculateCmac(KeyblobMacKeys[i], EncryptedKeyblobs[i], 0x10, cmac, 0, 0xa0);
Aes.CalculateCmac(cmac, EncryptedKeyblobs[i].AsSpan(0x10, 0xA0), KeyblobMacKeys[i]);
if (!Utilities.ArraysEqual(cmac, expectedCmac))
{

View file

@ -1,6 +1,8 @@
using LibHac;
using LibHac.Fs;
using LibHac.FsSystem.NcaUtils;
using LibHac.Ncm;
using ContentType = LibHac.Ncm.ContentType;
namespace hactoolnet
{
@ -96,5 +98,20 @@ namespace hactoolnet
_ => value.ToString()
};
}
public static string Print(this SaveDataType value)
{
return value switch
{
SaveDataType.System => nameof(SaveDataType.System),
SaveDataType.Account => nameof(SaveDataType.Account),
SaveDataType.Bcat => nameof(SaveDataType.Bcat),
SaveDataType.Device => nameof(SaveDataType.Device),
SaveDataType.Temporary => nameof(SaveDataType.Temporary),
SaveDataType.Cache => nameof(SaveDataType.Cache),
SaveDataType.SystemBcat => nameof(SaveDataType.SystemBcat),
_ => value.ToString()
};
}
}
}

View file

@ -330,7 +330,7 @@ namespace hactoolnet
PrintItem(sb, colLen, "Title ID:", $"{save.Header.ExtraData.TitleId:x16}");
PrintItem(sb, colLen, "User ID:", save.Header.ExtraData.UserId);
PrintItem(sb, colLen, "Save ID:", $"{save.Header.ExtraData.SaveId:x16}");
PrintItem(sb, colLen, "Save Type:", $"{save.Header.ExtraData.Type}");
PrintItem(sb, colLen, "Save Type:", $"{save.Header.ExtraData.Type.Print()}");
PrintItem(sb, colLen, "Owner ID:", $"{save.Header.ExtraData.SaveOwnerId:x16}");
PrintItem(sb, colLen, "Timestamp:", $"{DateTimeOffset.FromUnixTimeSeconds(save.Header.ExtraData.Timestamp):yyyy-MM-dd HH:mm:ss} UTC");
PrintItem(sb, colLen, "Save Data Size:", $"0x{save.Header.ExtraData.DataSize:x16} ({Utilities.GetBytesReadable(save.Header.ExtraData.DataSize)})");

View file

@ -1,4 +1,5 @@
using LibHac.Crypto;
using System;
using LibHac.Crypto;
using Xunit;
namespace LibHac.Tests
@ -58,7 +59,7 @@ namespace LibHac.Tests
{
var actual = new byte[0x10];
Aes.CalculateCmac(data.Key, data.Message, data.Start, actual, 0, data.Length);
Aes.CalculateCmac(actual, data.Message.AsSpan(data.Start, data.Length), data.Key);
Assert.Equal(data.Expected, actual);
}