From 49711b12dba9941253cda4a0b8a46a778f71a12d Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sat, 5 Sep 2020 21:10:39 -0700 Subject: [PATCH] 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. --- src/LibHac/Crypto/Aes.cs | 83 ++++++++----------- src/LibHac/Crypto/Rsa.cs | 32 +++---- src/LibHac/FsSystem/Save/Header.cs | 6 +- .../FsSystem/Save/SaveDataFileSystem.cs | 2 +- src/LibHac/FsSystem/StorageExtensions.cs | 20 +++-- src/LibHac/Keyset.cs | 2 +- src/hactoolnet/EnumStrings.cs | 17 ++++ src/hactoolnet/ProcessSave.cs | 2 +- tests/LibHac.Tests/AesCmac.cs | 5 +- 9 files changed, 88 insertions(+), 81 deletions(-) diff --git a/src/LibHac/Crypto/Aes.cs b/src/LibHac/Crypto/Aes.cs index e9db32e7..17652cf4 100644 --- a/src/LibHac/Crypto/Aes.cs +++ b/src/LibHac/Crypto/Aes.cs @@ -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); } - /** - * A byte span containing the 128-bit key used in the AES-CBC-128 steps - * A byte span containing the message to be authenticated - * The offset within the byte span at which the message will be read from - * A byte span to output the message authentication code into - * The offset within the byte span at which the authentication code will be written to - * The length of the message - * https://tools.ietf.org/html/rfc4493 - */ - public static void CalculateCmac(Span k, Span m, int mIndex, Span t, int tIndex, int len) + /// + /// Computes the CMAC of the provided data using AES-128. + /// + /// The buffer where the generated MAC will be placed. Must be at least 16 bytes long. + /// The message on which the MAC will be calculated. + /// The 128-bit AES key used to calculate the MAC. + /// https://tools.ietf.org/html/rfc4493 + public static void CalculateCmac(Span mac, ReadOnlySpan data, ReadOnlySpan key) { ReadOnlySpan zero = stackalloc byte[16]; + int len = data.Length; // Step 1, AES-128 with key K is applied to an all-zero input block. Span l = stackalloc byte[16]; - - EncryptCbc128(zero, l, k, zero); + EncryptCbc128(zero, l, key, zero); // Step 2, K1 is derived through the following operation: - Span k1 = LeftShiftBytes(l); + Span 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 k2 = LeftShiftBytes(k1); + Span 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 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 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 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 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 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); } - /** - * A byte span containing the 128-bit key used in the AES-CBC-128 steps - * A byte span containing the message to be authenticated - * A byte span to output the message authentication code into - * https://tools.ietf.org/html/rfc4493 - */ - public static void CalculateCmac(Span k, Span m, Span t) => - CalculateCmac(k, m, 0, t, 0, m.Length); - - private static byte[] LeftShiftBytes(Span bytes) + private static void LeftShiftBytes(ReadOnlySpan input, Span 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; } } } diff --git a/src/LibHac/Crypto/Rsa.cs b/src/LibHac/Crypto/Rsa.cs index 70aaec54..2ecbf826 100644 --- a/src/LibHac/Crypto/Rsa.cs +++ b/src/LibHac/Crypto/Rsa.cs @@ -50,11 +50,9 @@ namespace LibHac.Crypto } } - /** - * The RSA Modulus (n) - * The RSA Public Exponent (e) - * The RSA Private Exponent (d) - */ + /// The RSA Modulus (n) + /// The RSA Public Exponent (e) + /// The RSA Private Exponent (d) 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 }; } - /** - * The RSA Modulus (n) - * The RSA Public Exponent (e) - * The RSA Private Exponent (d) - */ + /// The RSA Modulus (n) + /// The RSA Public Exponent (e) + /// The RSA Private Exponent (d) public static RSAParameters RecoverParameters(ReadOnlySpan n, ReadOnlySpan e, ReadOnlySpan d) => RecoverParameters(n.GetBigInteger(), e.GetBigInteger(), d.GetBigInteger()); - /** - * - * Derive RSA Prime Number Pair (p, q) from RSA Modulus (n), RSA Public Exponent (e) and RSA Private Exponent (d) - * - * The RSA Modulus (n) - * The RSA Public Exponent (e) - * The RSA Private Exponent (d) - * RSA Prime Number Pair - */ + /// + /// Derive RSA Prime Number Pair (p, q) from RSA Modulus (n), RSA Public Exponent (e) and RSA Private Exponent (d) + /// + /// The RSA Modulus (n) + /// The RSA Public Exponent (e) + /// The RSA Private Exponent (d) + /// RSA Prime Number Pair private static (BigInteger p, BigInteger q) DeriveRsaPrimeNumberPair(BigInteger n, BigInteger e, BigInteger d) { BigInteger k = d * e - BigInteger.One; diff --git a/src/LibHac/FsSystem/Save/Header.cs b/src/LibHac/FsSystem/Save/Header.cs index be040695..6c8b4250 100644 --- a/src/LibHac/FsSystem/Save/Header.cs +++ b/src/LibHac/FsSystem/Save/Header.cs @@ -91,11 +91,11 @@ namespace LibHac.FsSystem.Save private Validity ValidateSignature(Keyset keyset) { - var calculatedCmac = new byte[0x10]; + Span 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; } } diff --git a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs index c8db4d54..f6d4a37a 100644 --- a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs @@ -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); diff --git a/src/LibHac/FsSystem/StorageExtensions.cs b/src/LibHac/FsSystem/StorageExtensions.cs index 495d46ce..611cc7b8 100644 --- a/src/LibHac/FsSystem/StorageExtensions.cs +++ b/src/LibHac/FsSystem/StorageExtensions.cs @@ -83,10 +83,12 @@ namespace LibHac.FsSystem long pos = 0; using var buffer = new RentedArray(bufferSize); + int rentedBufferSize = buffer.Array.Length; + while (remaining > 0) { - int toCopy = (int)Math.Min(bufferSize, remaining); - Span buf = buffer.Span.Slice(0, toCopy); + int toCopy = (int)Math.Min(rentedBufferSize, remaining); + Span 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(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 buf = buffer.Span.Slice(0, toFill); + int toFill = (int)Math.Min(rentedBufferSize, remaining); + Span 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(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; diff --git a/src/LibHac/Keyset.cs b/src/LibHac/Keyset.cs index c4b981ac..20006594 100644 --- a/src/LibHac/Keyset.cs +++ b/src/LibHac/Keyset.cs @@ -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)) { diff --git a/src/hactoolnet/EnumStrings.cs b/src/hactoolnet/EnumStrings.cs index bb21681f..77c4d681 100644 --- a/src/hactoolnet/EnumStrings.cs +++ b/src/hactoolnet/EnumStrings.cs @@ -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() + }; + } } } diff --git a/src/hactoolnet/ProcessSave.cs b/src/hactoolnet/ProcessSave.cs index 8d6c928e..0ea5e29c 100644 --- a/src/hactoolnet/ProcessSave.cs +++ b/src/hactoolnet/ProcessSave.cs @@ -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)})"); diff --git a/tests/LibHac.Tests/AesCmac.cs b/tests/LibHac.Tests/AesCmac.cs index d0587a20..52e2dd2d 100644 --- a/tests/LibHac.Tests/AesCmac.cs +++ b/tests/LibHac.Tests/AesCmac.cs @@ -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); }