diff --git a/src/LibHac/Crypto/Aes.cs b/src/LibHac/Crypto/Aes.cs index f24cb4ef..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; @@ -265,5 +265,73 @@ namespace LibHac.Crypto cipher.Transform(input, output); } + + /// + /// 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, key, zero); + + // Step 2, K1 is derived through the following operation: + 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 = 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. + + // 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 + paddedMessage = len < 0x800 ? stackalloc byte[len] : new byte[len]; + data.CopyTo(paddedMessage); + + for (int j = 0; j < k1.Length; j++) + paddedMessage[paddedMessage.Length - 16 + j] ^= k1[j]; + } + else // Otherwise, the last block shall be padded with 10^i and XOR'ed with K2. + { + 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++) + 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); + } + + private static void LeftShiftBytes(ReadOnlySpan input, Span output) + { + Assert.AssertTrue(output.Length >= input.Length); + + byte carry = 0; + + for (int i = input.Length - 1; i >= 0; i--) + { + ushort b = (ushort)(input[i] << 1); + output[i] = (byte)((b & 0xff) + carry); + carry = (byte)((b & 0xff00) >> 8); + } + } } } diff --git a/src/LibHac/Crypto/Rsa.cs b/src/LibHac/Crypto/Rsa.cs index 7370998f..2ecbf826 100644 --- a/src/LibHac/Crypto/Rsa.cs +++ b/src/LibHac/Crypto/Rsa.cs @@ -1,4 +1,5 @@ using System; +using System.Numerics; using System.Security.Cryptography; namespace LibHac.Crypto @@ -6,7 +7,15 @@ namespace LibHac.Crypto public static class Rsa { public static bool VerifyRsa2048PssSha256(ReadOnlySpan signature, ReadOnlySpan modulus, - ReadOnlySpan exponent, ReadOnlySpan message) + ReadOnlySpan exponent, ReadOnlySpan message) => + VerifyRsa2048Sha256(signature, modulus, exponent, message, RSASignaturePadding.Pss); + + public static bool VerifyRsa2048Pkcs1Sha256(ReadOnlySpan signature, ReadOnlySpan modulus, + ReadOnlySpan exponent, ReadOnlySpan message) => + VerifyRsa2048Sha256(signature, modulus, exponent, message, RSASignaturePadding.Pkcs1); + + private static bool VerifyRsa2048Sha256(ReadOnlySpan signature, ReadOnlySpan modulus, + ReadOnlySpan exponent, ReadOnlySpan message, RSASignaturePadding padding) { try { @@ -14,7 +23,7 @@ namespace LibHac.Crypto using (var rsa = RSA.Create(param)) { - return rsa.VerifyData(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + return rsa.VerifyData(message, signature, HashAlgorithmName.SHA256, padding); } } catch (CryptographicException) @@ -40,5 +49,129 @@ namespace LibHac.Crypto return false; } } + + /// 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); + + BigInteger dp = d % (p - BigInteger.One); + BigInteger dq = d % (q - BigInteger.One); + BigInteger inverseQ = Utilities.ModInverse(q, p); + + int modLen = n.ToByteArray().Length; + int halfModLen = (modLen + 1) / 2; + + return new RSAParameters + { + Modulus = n.GetBytes(modLen), + Exponent = e.GetBytes(-1), + D = d.GetBytes(modLen), + P = p.GetBytes(halfModLen), + Q = q.GetBytes(halfModLen), + DP = dp.GetBytes(halfModLen), + DQ = dq.GetBytes(halfModLen), + InverseQ = inverseQ.GetBytes(halfModLen) + }; + } + + /// 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 + private static (BigInteger p, BigInteger q) DeriveRsaPrimeNumberPair(BigInteger n, BigInteger e, BigInteger d) + { + BigInteger k = d * e - BigInteger.One; + + if (!k.IsEven) + { + throw new InvalidOperationException("d*e - 1 is odd"); + } + + BigInteger two = BigInteger.One + BigInteger.One; + BigInteger t = BigInteger.One; + + BigInteger r = k / two; + + while (r.IsEven) + { + t++; + r /= two; + } + + byte[] rndBuf = n.ToByteArray(); + + if (rndBuf[^1] == 0) + { + rndBuf = new byte[rndBuf.Length - 1]; + } + + BigInteger nMinusOne = n - BigInteger.One; + + bool cracked = false; + BigInteger y = BigInteger.Zero; + + using (var rng = RandomNumberGenerator.Create()) + { + for (int i = 0; i < 100 && !cracked; i++) + { + BigInteger g; + + do + { + rng.GetBytes(rndBuf); + g = Utilities.GetBigInteger(rndBuf); + } + while (g >= n); + + y = BigInteger.ModPow(g, r, n); + + if (y.IsOne || y == nMinusOne) + { + i--; + continue; + } + + for (BigInteger j = BigInteger.One; j < t; j++) + { + BigInteger x = BigInteger.ModPow(y, two, n); + + if (x.IsOne) + { + cracked = true; + break; + } + + if (x == nMinusOne) + { + break; + } + + y = x; + } + } + } + + if (!cracked) + { + throw new InvalidOperationException("Prime factors not found"); + } + + BigInteger p = BigInteger.GreatestCommonDivisor(y - BigInteger.One, n); + BigInteger q = n / p; + + return (p, q); + } } } diff --git a/src/LibHac/CryptoOld.cs b/src/LibHac/CryptoOld.cs index 956441c4..82ca8f90 100644 --- a/src/LibHac/CryptoOld.cs +++ b/src/LibHac/CryptoOld.cs @@ -1,103 +1,33 @@ using System; using System.IO; -using System.Numerics; using System.Security.Cryptography; +using LibHac.Crypto; using LibHac.FsSystem; +using Aes = LibHac.Crypto.Aes; + namespace LibHac { public static class CryptoOld { - internal const int Aes128Size = 0x10; - - public static void DecryptEcb(byte[] key, byte[] src, int srcIndex, byte[] dest, int destIndex, int length) - { - using (var aes = Aes.Create()) - { - if (aes == null) throw new CryptographicException("Unable to create AES object"); - aes.Key = key; - aes.Mode = CipherMode.ECB; - aes.Padding = PaddingMode.None; - Array.Copy(aes.CreateDecryptor().TransformFinalBlock(src, srcIndex, length), 0, dest, destIndex, length); - } - } - - public static void DecryptEcb(byte[] key, byte[] src, byte[] dest, int length) => - DecryptEcb(key, src, 0, dest, 0, length); - - public static void EncryptEcb(byte[] key, byte[] src, int srcIndex, byte[] dest, int destIndex, int length) - { - using (var aes = Aes.Create()) - { - if (aes == null) throw new CryptographicException("Unable to create AES object"); - aes.Key = key; - aes.Mode = CipherMode.ECB; - aes.Padding = PaddingMode.None; - Array.Copy(aes.CreateEncryptor().TransformFinalBlock(src, srcIndex, length), 0, dest, destIndex, length); - } - } - - public static void EncryptEcb(byte[] key, byte[] src, byte[] dest, int length) => - EncryptEcb(key, src, 0, dest, 0, length); - - public static void DecryptCbc(byte[] key, byte[] iv, byte[] src, int srcIndex, byte[] dest, int destIndex, int length) - { - using (var aes = Aes.Create()) - { - if (aes == null) throw new CryptographicException("Unable to create AES object"); - aes.Key = key; - aes.IV = iv; - aes.Mode = CipherMode.CBC; - aes.Padding = PaddingMode.None; - Array.Copy(aes.CreateDecryptor().TransformFinalBlock(src, srcIndex, length), 0, dest, destIndex, length); - } - } - - public static void DecryptCbc(byte[] key, byte[] iv, byte[] src, byte[] dest, int length) => - DecryptCbc(key, iv, src, 0, dest, 0, length); - - public static void EncryptCbc(byte[] key, byte[] iv, byte[] src, int srcIndex, byte[] dest, int destIndex, int length) - { - using (var aes = Aes.Create()) - { - if (aes == null) throw new CryptographicException("Unable to create AES object"); - aes.Key = key; - aes.IV = iv; - aes.Mode = CipherMode.CBC; - aes.Padding = PaddingMode.None; - Array.Copy(aes.CreateEncryptor().TransformFinalBlock(src, srcIndex, length), 0, dest, destIndex, length); - } - } - - public static void EncryptCbc(byte[] key, byte[] iv, byte[] src, byte[] dest, int length) => - EncryptCbc(key, iv, src, 0, dest, 0, length); - public static void GenerateKek(byte[] key, byte[] src, byte[] dest, byte[] kekSeed, byte[] keySeed) { - var kek = new byte[Aes128Size]; - var srcKek = new byte[Aes128Size]; + var kek = new byte[Aes.KeySize128]; + var srcKek = new byte[Aes.KeySize128]; - DecryptEcb(key, kekSeed, kek, Aes128Size); - DecryptEcb(kek, src, srcKek, Aes128Size); + Aes.DecryptEcb128(kekSeed, kek, key); + Aes.DecryptEcb128(src, srcKek, kek); if (keySeed != null) { - DecryptEcb(srcKek, keySeed, dest, Aes128Size); + Aes.DecryptEcb128(keySeed, dest, srcKek); } else { - Array.Copy(srcKek, dest, Aes128Size); + Array.Copy(srcKek, dest, Aes.KeySize128); } } - internal static BigInteger GetBigInteger(ReadOnlySpan bytes) - { - var signPadded = new byte[bytes.Length + 1]; - bytes.CopyTo(signPadded.AsSpan(1)); - Array.Reverse(signPadded); - return new BigInteger(signPadded); - } - public static RSAParameters DecryptRsaKey(byte[] encryptedKey, byte[] kek) { var counter = new byte[0x10]; @@ -114,11 +44,7 @@ namespace LibHac Array.Copy(key, 0x100, n, 0, 0x100); Array.Copy(key, 0x200, e, 0, 4); - BigInteger dInt = GetBigInteger(d); - BigInteger nInt = GetBigInteger(n); - BigInteger eInt = GetBigInteger(e); - - RSAParameters rsaParams = RecoverRsaParameters(nInt, eInt, dInt); + RSAParameters rsaParams = Rsa.RecoverParameters(n, e, d); TestRsaKey(rsaParams); return rsaParams; } @@ -138,43 +64,15 @@ namespace LibHac } } - public static Validity Rsa2048Pkcs1Verify(byte[] data, byte[] signature, byte[] modulus) - { - using (var rsa = RSA.Create()) - { - try - { - rsa.ImportParameters(new RSAParameters { Exponent = new byte[] { 1, 0, 1 }, Modulus = modulus }); + public static Validity Rsa2048Pkcs1Verify(byte[] data, byte[] signature, byte[] modulus) => + Rsa.VerifyRsa2048Pkcs1Sha256(signature, modulus, new byte[] { 1, 0, 1 }, data) + ? Validity.Valid + : Validity.Invalid; - return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1) - ? Validity.Valid - : Validity.Invalid; - } - catch (CryptographicException) - { - return Validity.Invalid; - } - } - } - - public static Validity Rsa2048PssVerify(byte[] data, byte[] signature, byte[] modulus) - { - using (var rsa = RSA.Create()) - { - try - { - rsa.ImportParameters(new RSAParameters { Exponent = new byte[] { 1, 0, 1 }, Modulus = modulus }); - - return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss) - ? Validity.Valid - : Validity.Invalid; - } - catch (CryptographicException) - { - return Validity.Invalid; - } - } - } + public static Validity Rsa2048PssVerify(byte[] data, byte[] signature, byte[] modulus) => + Rsa.VerifyRsa2048PssSha256(signature, modulus, new byte[] { 1, 0, 1 }, data) + ? Validity.Valid + : Validity.Invalid; public static byte[] DecryptRsaOaep(byte[] data, RSAParameters rsaParams) { @@ -201,222 +99,5 @@ namespace LibHac } } } - - public static RSAParameters RecoverRsaParameters(ReadOnlySpan modulus, ReadOnlySpan exponent) - { - BigInteger dInt = GetBigInteger(exponent); - BigInteger nInt = GetBigInteger(modulus); - BigInteger eInt = GetBigInteger(new byte[] { 1, 0, 1 }); - - return RecoverRsaParameters(nInt, eInt, dInt); - } - - private static RSAParameters RecoverRsaParameters(BigInteger n, BigInteger e, BigInteger d) - { - using (var rng = RandomNumberGenerator.Create()) - { - BigInteger k = d * e - 1; - - if (!k.IsEven) - { - throw new InvalidOperationException("d*e - 1 is odd"); - } - - BigInteger two = 2; - BigInteger t = BigInteger.One; - - BigInteger r = k / two; - - while (r.IsEven) - { - t++; - r /= two; - } - - byte[] rndBuf = n.ToByteArray(); - - if (rndBuf[rndBuf.Length - 1] == 0) - { - rndBuf = new byte[rndBuf.Length - 1]; - } - - BigInteger nMinusOne = n - BigInteger.One; - - bool cracked = false; - BigInteger y = BigInteger.Zero; - - for (int i = 0; i < 100 && !cracked; i++) - { - BigInteger g; - - do - { - rng.GetBytes(rndBuf); - g = GetBigInteger(rndBuf); - } - while (g >= n); - - y = BigInteger.ModPow(g, r, n); - - if (y.IsOne || y == nMinusOne) - { - i--; - continue; - } - - for (BigInteger j = BigInteger.One; j < t; j++) - { - BigInteger x = BigInteger.ModPow(y, two, n); - - if (x.IsOne) - { - cracked = true; - break; - } - - if (x == nMinusOne) - { - break; - } - - y = x; - } - } - - if (!cracked) - { - throw new InvalidOperationException("Prime factors not found"); - } - - BigInteger p = BigInteger.GreatestCommonDivisor(y - BigInteger.One, n); - BigInteger q = n / p; - BigInteger dp = d % (p - BigInteger.One); - BigInteger dq = d % (q - BigInteger.One); - BigInteger inverseQ = ModInverse(q, p); - - int modLen = rndBuf.Length; - int halfModLen = (modLen + 1) / 2; - - return new RSAParameters - { - Modulus = GetBytes(n, modLen), - Exponent = GetBytes(e, -1), - D = GetBytes(d, modLen), - P = GetBytes(p, halfModLen), - Q = GetBytes(q, halfModLen), - DP = GetBytes(dp, halfModLen), - DQ = GetBytes(dq, halfModLen), - InverseQ = GetBytes(inverseQ, halfModLen) - }; - } - } - - private static byte[] GetBytes(BigInteger value, int size) - { - byte[] bytes = value.ToByteArray(); - - if (size == -1) - { - size = bytes.Length; - } - - if (bytes.Length > size + 1) - { - throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}."); - } - - if (bytes.Length == size + 1 && bytes[bytes.Length - 1] != 0) - { - throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}."); - } - - Array.Resize(ref bytes, size); - Array.Reverse(bytes); - return bytes; - } - - private static BigInteger ModInverse(BigInteger e, BigInteger n) - { - BigInteger r = n; - BigInteger newR = e; - BigInteger t = 0; - BigInteger newT = 1; - - while (newR != 0) - { - BigInteger quotient = r / newR; - BigInteger temp; - - temp = t; - t = newT; - newT = temp - quotient * newT; - - temp = r; - r = newR; - newR = temp - quotient * newR; - } - - if (t < 0) - { - t = t + n; - } - - return t; - } - - public static void CalculateAesCmac(byte[] key, byte[] src, int srcIndex, byte[] dest, int destIndex, int length) - { - var l = new byte[16]; - EncryptCbc(key, l, l, l, 0x10); - byte[] paddedMessage; - int paddedLength = length; - - byte[] firstSubkey = Rol(l); - if ((l[0] & 0x80) == 0x80) - firstSubkey[15] ^= 0x87; - - byte[] secondSubkey = Rol(firstSubkey); - if ((firstSubkey[0] & 0x80) == 0x80) - secondSubkey[15] ^= 0x87; - - if (length != 0 && length % 16 == 0) - { - paddedMessage = new byte[paddedLength]; - Array.Copy(src, srcIndex, paddedMessage, 0, length); - - for (int j = 0; j < firstSubkey.Length; j++) - paddedMessage[length - 16 + j] ^= firstSubkey[j]; - } - else - { - paddedLength += 16 - length % 16; - paddedMessage = new byte[paddedLength]; - paddedMessage[length] = 0x80; - Array.Copy(src, srcIndex, paddedMessage, 0, length); - - for (int j = 0; j < secondSubkey.Length; j++) - paddedMessage[paddedLength - 16 + j] ^= secondSubkey[j]; - } - - var encResult = new byte[paddedMessage.Length]; - EncryptCbc(key, new byte[16], paddedMessage, encResult, paddedLength); - - Array.Copy(encResult, paddedLength - 0x10, dest, destIndex, 0x10); - } - - private static byte[] Rol(byte[] b) - { - var r = new byte[b.Length]; - byte carry = 0; - - for (int i = b.Length - 1; i >= 0; i--) - { - ushort u = (ushort)(b[i] << 1); - r[i] = (byte)((u & 0xff) + carry); - carry = (byte)((u & 0xff00) >> 8); - } - - return r; - } } -} +} \ No newline at end of file diff --git a/src/LibHac/FsSystem/AesXtsFileHeader.cs b/src/LibHac/FsSystem/AesXtsFileHeader.cs index 0288b631..eabf2bcc 100644 --- a/src/LibHac/FsSystem/AesXtsFileHeader.cs +++ b/src/LibHac/FsSystem/AesXtsFileHeader.cs @@ -5,6 +5,8 @@ using System.Text; using LibHac.Fs; using LibHac.Fs.Fsa; +using Aes = LibHac.Crypto.Aes; + namespace LibHac.FsSystem { public class AesXtsFileHeader @@ -79,14 +81,14 @@ namespace LibHac.FsSystem private void DecryptKeys() { - CryptoOld.DecryptEcb(Kek1, EncryptedKey1, DecryptedKey1, 0x10); - CryptoOld.DecryptEcb(Kek2, EncryptedKey2, DecryptedKey2, 0x10); + Aes.DecryptEcb128(EncryptedKey1, DecryptedKey1, Kek1); + Aes.DecryptEcb128(EncryptedKey2, DecryptedKey2, Kek2); } private void EncryptKeys() { - CryptoOld.EncryptEcb(Kek1, DecryptedKey1, EncryptedKey1, 0x10); - CryptoOld.EncryptEcb(Kek2, DecryptedKey2, EncryptedKey2, 0x10); + Aes.EncryptEcb128(DecryptedKey1, EncryptedKey1, Kek1); + Aes.EncryptEcb128(DecryptedKey2, EncryptedKey2, Kek2); } private void GenerateKek(byte[] kekSeed, string path) diff --git a/src/LibHac/FsSystem/NcaUtils/Nca.cs b/src/LibHac/FsSystem/NcaUtils/Nca.cs index 7e55a6f4..a1931d5d 100644 --- a/src/LibHac/FsSystem/NcaUtils/Nca.cs +++ b/src/LibHac/FsSystem/NcaUtils/Nca.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using LibHac.Common; +using LibHac.Crypto; using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; @@ -50,9 +51,9 @@ namespace LibHac.FsSystem.NcaUtils } byte[] encryptedKey = Header.GetEncryptedKey(index).ToArray(); - var decryptedKey = new byte[CryptoOld.Aes128Size]; + var decryptedKey = new byte[Aes.KeySize128]; - CryptoOld.DecryptEcb(keyAreaKey, encryptedKey, decryptedKey, CryptoOld.Aes128Size); + Aes.DecryptEcb128(encryptedKey, decryptedKey, keyAreaKey); return decryptedKey; } @@ -76,9 +77,9 @@ namespace LibHac.FsSystem.NcaUtils } byte[] encryptedKey = accessKey.Value.ToArray(); - var decryptedKey = new byte[CryptoOld.Aes128Size]; + var decryptedKey = new byte[Aes.KeySize128]; - CryptoOld.DecryptEcb(titleKek, encryptedKey, decryptedKey, CryptoOld.Aes128Size); + Aes.DecryptEcb128(encryptedKey, decryptedKey, titleKek); return decryptedKey; } diff --git a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs index b0558233..f52d3d22 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs @@ -160,8 +160,8 @@ namespace LibHac.FsSystem.NcaUtils throw new ArgumentOutOfRangeException($"Key index must be between 0 and 3. Actual: {index}"); } - int offset = NcaHeaderStruct.KeyAreaOffset + CryptoOld.Aes128Size * index; - return _header.Span.Slice(offset, CryptoOld.Aes128Size); + int offset = NcaHeaderStruct.KeyAreaOffset + Aes.KeySize128 * index; + return _header.Span.Slice(offset, Aes.KeySize128); } public NcaFsHeader GetFsHeader(int index) diff --git a/src/LibHac/FsSystem/Save/Header.cs b/src/LibHac/FsSystem/Save/Header.cs index db656cb9..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]; - CryptoOld.CalculateAesCmac(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 6ec53e1e..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); - CryptoOld.CalculateAesCmac(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 b433aa94..611cc7b8 100644 --- a/src/LibHac/FsSystem/StorageExtensions.cs +++ b/src/LibHac/FsSystem/StorageExtensions.cs @@ -1,7 +1,7 @@ using System; -using System.Buffers; using System.IO; using System.Runtime.InteropServices; +using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; @@ -71,10 +71,8 @@ namespace LibHac.FsSystem public static IFile AsFile(this IStorage storage, OpenMode mode) => new StorageFile(storage, mode); - public static void CopyTo(this IStorage input, IStorage output, IProgressReport progress = null) + public static void CopyTo(this IStorage input, IStorage output, IProgressReport progress = null, int bufferSize = 81920) { - const int bufferSize = 81920; - input.GetSize(out long inputSize).ThrowIfFailure(); output.GetSize(out long outputSize).ThrowIfFailure(); @@ -84,25 +82,20 @@ namespace LibHac.FsSystem long pos = 0; - byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - try - { - while (remaining > 0) - { - int toCopy = (int)Math.Min(bufferSize, remaining); - Span buf = buffer.AsSpan(0, toCopy); - input.Read(pos, buf); - output.Write(pos, buf); + using var buffer = new RentedArray(bufferSize); + int rentedBufferSize = buffer.Array.Length; - remaining -= toCopy; - pos += toCopy; - - progress?.ReportAdd(toCopy); - } - } - finally + while (remaining > 0) { - ArrayPool.Shared.Return(buffer); + int toCopy = (int)Math.Min(rentedBufferSize, remaining); + Span buf = buffer.Array.AsSpan(0, toCopy); + input.Read(pos, buf); + output.Write(pos, buf); + + remaining -= toCopy; + pos += toCopy; + + progress?.ReportAdd(toCopy); } progress?.SetTotal(0); @@ -140,27 +133,22 @@ namespace LibHac.FsSystem long pos = offset; - byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - try + using var buffer = new RentedArray(bufferSize); + int rentedBufferSize = buffer.Array.Length; + + buffer.Array.AsSpan(0, (int)Math.Min(remaining, rentedBufferSize)).Fill(value); + + while (remaining > 0) { - buffer.AsSpan(0, (int)Math.Min(remaining, bufferSize)).Fill(value); + int toFill = (int)Math.Min(rentedBufferSize, remaining); + Span buf = buffer.Array.AsSpan(0, toFill); - while (remaining > 0) - { - int toFill = (int)Math.Min(bufferSize, remaining); - Span buf = buffer.AsSpan(0, toFill); + input.Write(pos, buf); - input.Write(pos, buf); + remaining -= toFill; + pos += toFill; - remaining -= toFill; - pos += toFill; - - progress?.ReportAdd(toFill); - } - } - finally - { - ArrayPool.Shared.Return(buffer); + progress?.ReportAdd(toFill); } progress?.SetTotal(0); @@ -200,30 +188,32 @@ namespace LibHac.FsSystem return arr; } - public static void CopyToStream(this IStorage input, Stream output, long length, IProgressReport progress = null) + public static void CopyToStream(this IStorage input, Stream output, long length, IProgressReport progress = null, int bufferSize = 0x8000) { - const int bufferSize = 0x8000; long remaining = length; long inOffset = 0; - var buffer = new byte[bufferSize]; + + using var buffer = new RentedArray(bufferSize); + int rentedBufferSize = buffer.Array.Length; + progress?.SetTotal(length); while (remaining > 0) { - int toWrite = (int)Math.Min(buffer.Length, remaining); - input.Read(inOffset, buffer.AsSpan(0, toWrite)); + int toWrite = (int)Math.Min(rentedBufferSize, remaining); + input.Read(inOffset, buffer.Array.AsSpan(0, toWrite)); - output.Write(buffer, 0, toWrite); + output.Write(buffer.Array, 0, toWrite); remaining -= toWrite; inOffset += toWrite; progress?.ReportAdd(toWrite); } } - public static void CopyToStream(this IStorage input, Stream output) + public static void CopyToStream(this IStorage input, Stream output, int bufferSize = 0x8000) { input.GetSize(out long inputSize).ThrowIfFailure(); - CopyToStream(input, output, inputSize); + CopyToStream(input, output, inputSize, bufferSize: bufferSize); } public static IStorage AsStorage(this Stream stream) diff --git a/src/LibHac/Keyset.cs b/src/LibHac/Keyset.cs index 4bbdc83b..20006594 100644 --- a/src/LibHac/Keyset.cs +++ b/src/LibHac/Keyset.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; +using LibHac.Crypto; using LibHac.Fs; using LibHac.FsSrv; using LibHac.Spl; @@ -81,7 +82,7 @@ namespace LibHac if (_nca0RsaKeyAreaKey.HasValue) return _nca0RsaKeyAreaKey.Value; - _nca0RsaKeyAreaKey = CryptoOld.RecoverRsaParameters(BetaNca0Modulus, BetaNca0Exponent); + _nca0RsaKeyAreaKey = Rsa.RecoverParameters(BetaNca0Modulus, BetaNca0Exponent, new byte[] { 1, 0, 1 }); return _nca0RsaKeyAreaKey.Value; } } @@ -349,7 +350,7 @@ namespace LibHac } Array.Copy(EncryptedKeyblobs[i], expectedCmac, 0x10); - CryptoOld.CalculateAesCmac(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/LibHac/Utilities.cs b/src/LibHac/Utilities.cs index c34207a4..304c22b6 100644 --- a/src/LibHac/Utilities.cs +++ b/src/LibHac/Utilities.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Numerics; using System.Runtime.CompilerServices; @@ -500,6 +500,67 @@ namespace LibHac return value > 0 && ResetLeastSignificantOneBit(value) == 0; } + public static BigInteger GetBigInteger(this ReadOnlySpan bytes) + { + var signPadded = new byte[bytes.Length + 1]; + bytes.CopyTo(signPadded.AsSpan(1)); + Array.Reverse(signPadded); + return new BigInteger(signPadded); + } + + public static byte[] GetBytes(this BigInteger value, int size) + { + byte[] bytes = value.ToByteArray(); + + if (size == -1) + { + size = bytes.Length; + } + + if (bytes.Length > size + 1) + { + throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}."); + } + + if (bytes.Length == size + 1 && bytes[bytes.Length - 1] != 0) + { + throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}."); + } + + Array.Resize(ref bytes, size); + Array.Reverse(bytes); + return bytes; + } + + public static BigInteger ModInverse(BigInteger e, BigInteger n) + { + BigInteger r = n; + BigInteger newR = e; + BigInteger t = 0; + BigInteger newT = 1; + + while (newR != 0) + { + BigInteger quotient = r / newR; + BigInteger temp; + + temp = t; + t = newT; + newT = temp - quotient * newT; + + temp = r; + r = newR; + newR = temp - quotient * newR; + } + + if (t < 0) + { + t = t + n; + } + + return t; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int ResetLeastSignificantOneBit(int value) { diff --git a/src/LibHac/XciHeader.cs b/src/LibHac/XciHeader.cs index 4bfa3bb0..7fa5fb4c 100644 --- a/src/LibHac/XciHeader.cs +++ b/src/LibHac/XciHeader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Text; using LibHac.Crypto; @@ -96,7 +96,7 @@ namespace LibHac Flags = (GameCardAttribute)reader.ReadByte(); PackageId = reader.ReadUInt64(); ValidDataEndPage = reader.ReadInt64(); - AesCbcIv = reader.ReadBytes(CryptoOld.Aes128Size); + AesCbcIv = reader.ReadBytes(Aes.KeySize128); Array.Reverse(AesCbcIv); RootPartitionOffset = reader.ReadInt64(); RootPartitionHeaderSize = reader.ReadInt64(); @@ -111,7 +111,7 @@ namespace LibHac { byte[] encHeader = reader.ReadBytes(EncryptedHeaderSize); var decHeader = new byte[EncryptedHeaderSize]; - CryptoOld.DecryptCbc(keyset.XciHeaderKey, AesCbcIv, encHeader, decHeader, EncryptedHeaderSize); + Aes.DecryptCbc128(encHeader, decHeader, keyset.XciHeaderKey, AesCbcIv); using (var decreader = new BinaryReader(new MemoryStream(decHeader))) { 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 9caf2315..52e2dd2d 100644 --- a/tests/LibHac.Tests/AesCmac.cs +++ b/tests/LibHac.Tests/AesCmac.cs @@ -1,4 +1,6 @@ -using Xunit; +using System; +using LibHac.Crypto; +using Xunit; namespace LibHac.Tests { @@ -57,7 +59,7 @@ namespace LibHac.Tests { var actual = new byte[0x10]; - CryptoOld.CalculateAesCmac(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); }