diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock.sln b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock.sln new file mode 100644 index 0000000..864a6c6 --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C# Express 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwlBackupBlock", "TWLBackupBlock\TwlBackupBlock.csproj", "{E62F6169-868A-48E4-9B1D-C2EB3BE11F39}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E62F6169-868A-48E4-9B1D-C2EB3BE11F39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E62F6169-868A-48E4-9B1D-C2EB3BE11F39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E62F6169-868A-48E4-9B1D-C2EB3BE11F39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E62F6169-868A-48E4-9B1D-C2EB3BE11F39}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock.sln.VisualState.xml b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock.sln.VisualState.xml new file mode 100644 index 0000000..5758c9a --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock.sln.VisualState.xml @@ -0,0 +1,13 @@ + + + [0-1000]C:\Users\N2891\Documents\Visual Studio 2008\Projects\TWLBackupBlock\TWLBackupBlock.sln + [0-1004]TwlBackupBlockTest.AesCmacTest.GetMacTest + false + + + + + + + + \ No newline at end of file diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock.suo b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock.suo new file mode 100644 index 0000000..a37f9f0 Binary files /dev/null and b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock.suo differ diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/AbstractBody.cs b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/AbstractBody.cs new file mode 100644 index 0000000..3979d5d --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/AbstractBody.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace TwlBackupBlock +{ + /// + /// データ本体を表す抽象クラスです。 + /// + public abstract class AbstractBody : IEnumerable, ICloneable + { + /// + /// 指定したインデックスにアクセスします。 + /// + /// アクセスするインデックス。 + /// 指定したインデックスのバイトデータ。 + public abstract byte this[int index] + { + get; + set; + } + + /// + /// データ本体のバイトサイズを取得します。 + /// + public abstract int Length + { + get; + } + + /// + /// イテレータブロック。 + /// + /// 列挙子。 + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Length; i++) + { + yield return this[i]; + } + } + + /// + /// イテレータブロック。 + /// + /// 列挙子。 + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// データ本体を表すバイト配列を取得します。 + /// + /// データ本体を表すバイト配列 + public abstract byte[] GetBytes(); + + /// + /// バイト配列からデータ本体を構成します。 + /// + /// データ本体を表すバイト配列 + public abstract void SetBytes(byte[] bytes); + + /// + /// AbstractBodyのディープコピーを作成します。 + /// + /// AbstractBodyのコピー + public abstract object Clone(); + } +} diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/AesCmac.cs b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/AesCmac.cs new file mode 100644 index 0000000..42ce0a5 --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/AesCmac.cs @@ -0,0 +1,170 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Cryptography; + +namespace TwlBackupBlock +{ + /// + /// AES-CMACによるMACを生成します。 + /// 鍵は16バイト、24バイト、32バイトに対応しています。 + /// + public class AesCmac + { + private const int BLOCK_SIZE = 16; + private static readonly int[] LEGAL_KEY_SIZES = { 16, 24, 32 }; + + /// + /// 指定された鍵を利用してデータのMACを取得します。 + /// + /// MACを発行したいデータ。 + /// AES-CMACの鍵。 + /// に対するMAC。 + public static byte[] GetMac(byte[] data, byte[] key) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + if (key == null) + { + throw new ArgumentNullException("key"); + } + if (!CheckKeySize(key.Length)) + { + string message = string.Format("key.Length is illegal (key.Length:{0})", key.Length); + throw new ArgumentException("key", message); + } + + var context = new AesCmacContext(block => Encrypt(block, key)); + + context.Update(data); + + return context.GetMac(); + } + + /// + /// 指定された鍵を利用して16バイトブロックをAESで暗号化します。 + /// + /// 暗号化するブロック。メソッド呼出し後は、暗号化されたブロック。 + /// AESの鍵。 + private static void Encrypt(byte[] block, byte[] key) + { + Debug.Assert(block.Length == BLOCK_SIZE); + Debug.Assert(CheckKeySize(key.Length)); + + using (var ms = new MemoryStream(block)) + { + var aes = new RijndaelManaged(); + aes.BlockSize = BLOCK_SIZE * 8; + aes.Mode = CipherMode.ECB; + aes.Key = key; + using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Read)) + { + cs.Read(block, 0, block.Length); + } + } + } + + /// + /// 鍵のサイズが16バイト、24バイト、32バイトのいずれであるか判定します。 + /// + /// 鍵のバイトサイズ + /// + private static bool CheckKeySize(int keySize) + { + return LEGAL_KEY_SIZES.Any(e => (e == keySize)); + } + + /// + /// AES-CMAC生成器です。データの入力履歴に応じたMAC生成器の内部状態を保持します。 + /// + private class AesCmacContext + { + public delegate void Encrypt(byte[] block); + + private Encrypt encryptOperation; + private byte[] block; + private int filledIndex; + + /// + /// 指定された暗号化処理をもとに、AesCmacContextクラスの新しいインスタンスを初期化します。 + /// + /// 16バイトブロックを暗号化するメソッド + public AesCmacContext(Encrypt op) + { + encryptOperation = op; + block = Enumerable.Repeat(0, BLOCK_SIZE).ToArray(); + filledIndex = 0; + } + + /// + /// 指定された入力データによりMAC生成器を更新します。 + /// + /// MACを発行したいデータ + public void Update(byte[] data) + { + for (int i = 0; i < data.Length; ) + { + if (filledIndex >= BLOCK_SIZE) + { + encryptOperation(block); + filledIndex = 0; + } + while (filledIndex < BLOCK_SIZE && i < data.Length) + { + block[filledIndex] ^= data[i]; + ++filledIndex; + ++i; + } + } + } + + /// + /// 現在までの入力履歴からMACを生成します。 + /// + /// これまでの入力データに対するMAC + public byte[] GetMac() + { + byte[] padding = Enumerable.Repeat(0, BLOCK_SIZE).ToArray(); + encryptOperation(padding); + Multiply128(padding); + + if (filledIndex < BLOCK_SIZE) + { + block[filledIndex] ^= 0x80; + Multiply128(padding); + } + + for (int i = 0; i < BLOCK_SIZE; ++i) + { + padding[i] ^= block[i]; + } + + encryptOperation(padding); + + return padding; + } + + /// + /// 16バイトの整数に128を乗じます。オーバーフローは無視します。 + /// + /// 16バイト整数 + private void Multiply128(byte[] block) + { + bool isCarried = ((block[0] & 0x80) != 0); + + for (int i = 0; i < block.Length - 1; i++) + { + block[i] = (byte)(((uint)block[i] << 1) | ((uint)block[i + 1] >> 7)); + } + block[block.Length - 1] <<= 1; + if (isCarried) + { + block[block.Length - 1] = (byte)(block[block.Length - 1] ^ 0x87); + } + } + } + } +} diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Block.cs b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Block.cs new file mode 100644 index 0000000..37a0ea4 --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Block.cs @@ -0,0 +1,53 @@ +using System; +using System.Diagnostics; + +namespace TwlBackupBlock +{ + /// + /// データブロックです。 + /// + public class Block : ICloneable + { + private const int MAC_SIZE = 16; + private const int IV_SIZE = 16; + + public AbstractBody body; + public byte[] mac; + public byte[] iv; + + /// + /// Blockクラスの新しいインスタンスを初期化します。 + /// + public Block() + { + body = null; + mac = null; + iv = null; + } + + /// + /// 指定したデータ本体、MAC、IVを使用して、Blockクラスの新しいインスタンスを初期化します。 + /// + /// データ本体。 + /// MAC。 + /// IV。 + public Block(AbstractBody body, byte[] mac, byte[] iv) + { + Debug.Assert(mac.Length == MAC_SIZE); + Debug.Assert(iv.Length == IV_SIZE); + + this.body = body; + this.mac = mac; + this.iv = iv; + } + + /// + /// Blockのディープコピーを作成します。 + /// + /// + public object Clone() + { + return new Block((AbstractBody)body.Clone(), (byte[])mac.Clone(), (byte[])iv.Clone()); + } + } +} diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Blocks.cs b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Blocks.cs new file mode 100644 index 0000000..d217dd3 --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Blocks.cs @@ -0,0 +1,88 @@ +using System; +using System.Diagnostics; + +namespace TwlBackupBlock +{ + /// + /// ブロックの集合です。 + /// + public class Blocks : ICloneable + { + private const int NUM_BLOCKS = 7; + + public Block banner; + public Block header; + public Block signature; + public Block tmd; + public Block content; + public Block saveData; + public Block subBanner; + + /// + /// Blocksクラスの新しいインスタンスを初期化します。 + /// + public Blocks() + { + banner = new Block(); + header = new Block(); + signature = new Block(); + tmd = new Block(); + content = new Block(); + saveData = new Block(); + subBanner = new Block(); + } + + /// + /// 指定したインデックスのブロックにアクセスします。 + /// + /// インデックス。 + /// 指定したインデックスのブロック。 + public Block this[int index] + { + get + { + switch (index) + { + case 0: return banner; + case 1: return header; + case 2: return signature; + case 3: return tmd; + case 4: return content; + case 5: return saveData; + case 6: return subBanner; + default: + Debug.Assert(false); + return null; // never reach + } + } + } + + /// + /// ブロック数(=7)を取得します。 + /// + public int Length + { + get + { + return NUM_BLOCKS; + } + } + + /// + /// Blocksのディープコピーを作成します。 + /// + /// Blocksのコピー + public object Clone() + { + Blocks newBlocks = new Blocks(); + newBlocks.banner = (Block)banner.Clone(); + newBlocks.header = (Block)header.Clone(); + newBlocks.signature = (Block)signature.Clone(); + newBlocks.tmd = (Block)tmd.Clone(); + newBlocks.content = (Block)content.Clone(); + newBlocks.saveData = (Block)saveData.Clone(); + newBlocks.subBanner = (Block)subBanner.Clone(); + return newBlocks; + } + } +} diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Body.cs b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Body.cs new file mode 100644 index 0000000..8ffa98c --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Body.cs @@ -0,0 +1,81 @@ +using System; +using System.Diagnostics; + +namespace TwlBackupBlock +{ + /// + /// バイト単位でアクセスする機能だけを持ったデータ本体です。 + /// + public class Body : AbstractBody + { + private byte[] data; + + /// + /// 指定したデータ本体をもとに、Bodyクラスの新しいインスタンスを初期化します。 + /// + /// + public Body(byte[] data) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + this.data = (byte[])data.Clone(); + } + + public override byte this[int index] + { + get + { + if (index < 0 || index >= data.Length) + { + throw new ArgumentOutOfRangeException("index"); + } + return data[index]; + } + set + { + if (index < 0 || index >= data.Length) + { + throw new ArgumentOutOfRangeException("index"); + } + data[index] = value; + } + } + + public override int Length + { + get + { + Debug.Assert(data != null); + return data.Length; + } + } + + public override byte[] GetBytes() + { + Debug.Assert(data != null); + return (byte[])data.Clone(); + } + + public override void SetBytes(byte[] bytes) + { + Debug.Assert(data != null); + if (bytes == null) + { + throw new ArgumentNullException("bytes"); + } + if (bytes.Length != data.Length) + { + string message = string.Format("bytes.Length != {0} (bytes.Length:{1})", data.Length, bytes.Length); + throw new ArgumentException("bytes", message); + } + bytes.CopyTo(data, 0); + } + + public override object Clone() + { + return new Body(GetBytes()); + } + } +} diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/ExtBinaryReader.cs b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/ExtBinaryReader.cs new file mode 100644 index 0000000..5c66a91 --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/ExtBinaryReader.cs @@ -0,0 +1,138 @@ +using System; +using System.IO; + +namespace TwlBackupBlock +{ + /// + /// BinaryReaderのReadをラップするクラスです。 + /// + public class ExtBinaryReader : IDisposable + { + private bool disposed; + private BinaryReader br; + + /// + /// 指定したMemoryStreamをもとに、ExtBinaryReaderクラスの新しいインスタンスを初期化します。 + /// + /// + public ExtBinaryReader(MemoryStream ms) + { + disposed = false; + br = new BinaryReader(ms); + } + + ~ExtBinaryReader() + { + Dispose(false); + } + + /// + /// ExtBinaryReaderを破棄します。 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// ExtBinaryReaderを破棄します。 + /// + /// BinaryReaderも破棄する場合はtrue。それ以外の場合はfalse。 + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + ((IDisposable)br).Dispose(); + } + } + disposed = true; + } + + /// + /// 現在のストリームから次のバイトを読み取り、ストリームの現在位置を1バイトだけ進めます。 + /// + /// データを読み取るバッファ + public void Read(ref Byte value) + { + value = br.ReadByte(); + } + + /// + /// 現在のストリームから2バイト符号なし整数を読み取り、ストリームの現在位置を2バイトだけ進めます。 + /// + /// データを読み取るバッファ + public void Read(ref UInt16 value) + { + value = br.ReadUInt16(); + } + + /// + /// 現在のストリームから4バイト符号なし整数を読み取り、ストリームの現在位置を4バイトだけ進めます。 + /// + /// データを読み取るバッファ + public void Read(ref UInt32 value) + { + value = br.ReadUInt32(); + } + + /// + /// 現在のストリームから8バイト符号なし整数を読み取り、ストリームの現在位置を8バイトだけ進めます。 + /// + /// データを読み取るバッファ + public void Read(ref UInt64 value) + { + value = br.ReadUInt64(); + } + + /// + /// 現在のストリームから2バイト符号付き整数を読み取り、ストリームの現在位置を2バイトだけ進めます。 + /// + /// データを読み取るバッファ + public void Read(ref Int16 value) + { + value = br.ReadInt16(); + } + + /// + /// 現在のストリームから4バイト符号付き整数を読み取り、ストリームの現在位置を4バイトだけ進めます。 + /// + /// データを読み取るバッファ + public void Read(ref Int32 value) + { + value = br.ReadInt32(); + } + + /// + /// 現在のストリームから8バイト符号付き整数を読み取り、ストリームの現在位置を8バイトだけ進めます。 + /// + /// データを読み取るバッファ + public void Read(ref Int64 value) + { + value = br.ReadInt64(); + } + + /// + /// 現在のストリームから指定した配列にバイトを読み取り、ストリームの現在位置を指定した配列のバイト数だけ進めます。 + /// + /// データを読み取るバッファ + public void Read(Byte[] buffer) + { + br.Read(buffer, 0, buffer.Length); + } + + /// + /// 現在のストリームから指定した配列に4バイト符号なし整数を読み取り、ストリームの現在位置を指定した配列のバイト数だけ進めます。 + /// + /// データを読み取るバッファ + public void Read(UInt32[] buffer) + { + for (int i = 0; i < buffer.Length; i++) + { + Read(ref buffer[i]); + } + } + } +} diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/HeaderBody.cs b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/HeaderBody.cs new file mode 100644 index 0000000..a84680e --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/HeaderBody.cs @@ -0,0 +1,260 @@ +using System; +using System.IO; +using System.Diagnostics; + +namespace TwlBackupBlock +{ + /// + /// ヘッダブロックのデータ本体。 + /// + public class HeaderBody : AbstractBody + { + public const int HEADER_SIZE = 160; + + public const int UNIQUE_ID_SIZE = 32; + public const int HARDWARE_ID_SIZE = 16; + public const int NUM_OF_SECTION = 4; + public const int TMD_RESERVED_SIZE = 62; + public const int RESERVED_SIZE = 4; + + public UInt32 signature; + public UInt16 companyCode; + public UInt16 version; + public readonly Byte[] uniqueId; + public readonly Byte[] hardwareId; + public UInt64 titleId; + public Int64 requiredSize; + public readonly UInt32[] fileSizes; + public UInt32 contentId; + public readonly TmdReserved tmdReserved; + public UInt16 contentIndex; + public readonly Byte[] reserved; + + public HeaderBody(byte[] data) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + if (data.Length != HEADER_SIZE) + { + string message = string.Format("data.Length % {0} != 0 (data.Length:{1})", HEADER_SIZE, data.Length); + throw new ArgumentException("data", message); + } + + uniqueId = new Byte[UNIQUE_ID_SIZE]; + hardwareId = new Byte[HARDWARE_ID_SIZE]; + fileSizes = new UInt32[NUM_OF_SECTION]; + tmdReserved = new TmdReserved(); + reserved = new Byte[RESERVED_SIZE]; + + SetBytes(data); + } + + public override byte this[int index] + { + get + { + if (index < 0 || index >= HEADER_SIZE) + { + throw new ArgumentException("index"); + } + return ReadByte(index); + } + set + { + if (index < 0 || index >= HEADER_SIZE) + { + throw new ArgumentException("index"); + } + WriteByte(index, value); + } + } + + public override int Length + { + get + { + return HEADER_SIZE; + } + } + + /// + /// 指定したバイトを取得します。 + /// + /// 取得したいバイトのインデックス。 + /// で指定したバイト + public byte ReadByte(int index) + { + if (index < 0 || index >= HEADER_SIZE) + { + throw new ArgumentException("index"); + } + // TODO 処理が遅ければ効率化 + byte[] bytes = GetBytes(); + return bytes[index]; + } + + /// + /// 指定したバイトを書き換えます。 + /// + /// 書き換えたいバイトのインデックス。 + /// 上書きに使うバイト。 + public void WriteByte(int index, byte b) + { + if (index < 0 || index >= HEADER_SIZE) + { + throw new ArgumentException("index"); + } + // TODO 処理が遅ければ効率化 + byte[] bytes = GetBytes(); + bytes[index] = b; + SetBytes(bytes); + } + + public override byte[] GetBytes() + { + byte[] bytes = new byte[HEADER_SIZE]; + + using (var ms = new MemoryStream(bytes)) + using (var bw = new BinaryWriter(ms)) + { + bw.Write(signature); + bw.Write(companyCode); + bw.Write(version); + bw.Write(uniqueId); + bw.Write(hardwareId); + bw.Write(titleId); + bw.Write(requiredSize); + foreach (UInt32 e in fileSizes) + { + bw.Write(e); + } + bw.Write(contentId); + bw.Write(tmdReserved.GetBytes()); + bw.Write(contentIndex); + bw.Write(reserved); + + Debug.Assert(ms.Position == HEADER_SIZE); + } + + return bytes; + } + + public override void SetBytes(byte[] bytes) + { + if (bytes == null) + { + throw new ArgumentNullException("bytes"); + } + if (bytes.Length != HEADER_SIZE) + { + string message = string.Format("bytes.Length != {0} (bytes.Length:{1})", HEADER_SIZE, bytes.Length); + throw new ArgumentException("bytes", message); + } + + using (var ms = new MemoryStream(bytes)) + using (var br = new ExtBinaryReader(ms)) + { + br.Read(ref signature); + br.Read(ref companyCode); + br.Read(ref version); + br.Read(uniqueId); + br.Read(hardwareId); + br.Read(ref titleId); + br.Read(ref requiredSize); + br.Read(fileSizes); + br.Read(ref contentId); + { + byte[] tmdReservedBytes = new byte[TMD_RESERVED_SIZE]; + br.Read(tmdReservedBytes); + tmdReserved.SetBytes(tmdReservedBytes); + } + br.Read(ref contentIndex); + br.Read(reserved); + + Debug.Assert(ms.Position == HEADER_SIZE); + } + } + + public override object Clone() + { + return new HeaderBody(GetBytes()); + } + } + + /// + /// ヘッダブロック中のTMDReserved。 + /// + public class TmdReserved + { + private const int TMD_RESERVED_SIZE = 62; + + public const int RESERVED8_SIZE = 3; + public const int PARENTAL_CONTROL_SIZE = 16; + public const int RESERVED_SIZE = 30; + + public UInt32 publicSaveSize; + public UInt32 privateSaveSize; + public UInt32 reserved32; + public Byte flags; + public readonly Byte[] reserved8; + public readonly Byte[] parentalControl; + public readonly Byte[] reserved; + + public TmdReserved() + { + reserved8 = new Byte[RESERVED8_SIZE]; + parentalControl = new Byte[PARENTAL_CONTROL_SIZE]; + reserved = new Byte[RESERVED_SIZE]; + } + + public byte[] GetBytes() + { + byte[] bytes = new byte[TMD_RESERVED_SIZE]; + + using (var ms = new MemoryStream(bytes)) + using (var bw = new BinaryWriter(ms)) + { + bw.Write(publicSaveSize); + bw.Write(privateSaveSize); + bw.Write(reserved32); + bw.Write(flags); + bw.Write(reserved8); + bw.Write(parentalControl); + bw.Write(reserved); + + Debug.Assert(ms.Position == TMD_RESERVED_SIZE); + } + + return bytes; + } + + public void SetBytes(byte[] bytes) + { + if (bytes == null) + { + throw new ArgumentNullException("bytes"); + } + if (bytes.Length != TMD_RESERVED_SIZE) + { + string message = string.Format("bytes.Length != {0} (bytes.Length:{1})", TMD_RESERVED_SIZE, bytes.Length); + throw new ArgumentException("bytes", message); + } + + using (var ms = new MemoryStream(bytes)) + using (var br = new ExtBinaryReader(ms)) + { + br.Read(ref publicSaveSize); + br.Read(ref privateSaveSize); + br.Read(ref reserved32); + br.Read(ref flags); + br.Read(reserved8); + br.Read(parentalControl); + br.Read(reserved); + + Debug.Assert(ms.Position == TMD_RESERVED_SIZE); + } + } + } +} diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/AssemblyInfo.cs b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..61551d7 --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 +// アセンブリに関連付けられている情報を変更するには、 +// これらの属性値を変更してください。 +[assembly: AssemblyTitle("TWLBackupBlock")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("TWLBackupBlock")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから +// 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、 +// その型の ComVisible 属性を true に設定してください。 +[assembly: ComVisible(false)] + +// 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です +[assembly: Guid("46f10db4-5827-4ead-9f8c-ef885eb49f40")] + +// アセンブリのバージョン情報は、以下の 4 つの値で構成されています: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を +// 既定値にすることができます: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/Resources.Designer.cs b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/Resources.Designer.cs new file mode 100644 index 0000000..ac2ffd0 --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// このコードはツールによって生成されました。 +// ランタイム バージョン:2.0.50727.4959 +// +// このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 +// コードが再生成されるときに損失したりします。 +// +//------------------------------------------------------------------------------ + +namespace TwlBackupBlock.Properties { + using System; + + + /// + /// ローカライズされた文字列などを検索するための、厳密に型指定されたリソース クラスです。 + /// + // このクラスは StronglyTypedResourceBuilder クラスが ResGen + // または Visual Studio のようなツールを使用して自動生成されました。 + // メンバを追加または削除するには、.ResX ファイルを編集して、/str オプションと共に + // ResGen を実行し直すか、または VS プロジェクトをビルドし直します。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// このクラスで使用されているキャッシュされた ResourceManager インスタンスを返します。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TwlBackupBlock.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 厳密に型指定されたこのリソース クラスを使用して、すべての検索リソースに対し、 + /// 現在のスレッドの CurrentUICulture プロパティをオーバーライドします。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/Resources.resx b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/Resources.resx new file mode 100644 index 0000000..85c9090 --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/Resources.resx @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/Settings.Designer.cs b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/Settings.Designer.cs new file mode 100644 index 0000000..2e8ed9f --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// このコードはツールによって生成されました。 +// ランタイム バージョン:2.0.50727.4959 +// +// このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 +// コードが再生成されるときに損失したりします。 +// +//------------------------------------------------------------------------------ + +namespace TwlBackupBlock.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/Settings.settings b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/Settings.settings new file mode 100644 index 0000000..15034e7 --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Properties/Settings.settings @@ -0,0 +1,6 @@ + + + + + + diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/SignatureBody.cs b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/SignatureBody.cs new file mode 100644 index 0000000..3873d11 --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/SignatureBody.cs @@ -0,0 +1,192 @@ +using System; +using System.IO; +using System.Diagnostics; + +namespace TwlBackupBlock +{ + /// + /// 署名ブロック中のハッシュセット。 + /// + public class TwlBackupHashSet + { + public const int HASH_SIZE = 32; + public const int NUM_OF_SECTION = 4; + + public readonly byte[] banner; + public readonly byte[] header; + public readonly byte[][] section; + + public TwlBackupHashSet() + { + banner = new byte[HASH_SIZE]; + header = new byte[HASH_SIZE]; + section = new byte[NUM_OF_SECTION][]; + for (int i = 0; i < section.Length; i++) + { + section[i] = new byte[HASH_SIZE]; + } + } + } + /// + /// 署名ブロックのデータ本体。 + /// + public class SignatureBody : AbstractBody + { + public const int SIGNATURE_SIZE = 1024; + + public const int ECC_SIGN_SIZE = 60; + public const int ECC_CERT_SIZE = 384; + + public readonly TwlBackupHashSet digest; + public readonly byte[] sign; + public readonly byte[][] cert; + public UInt32 reserved; + + public SignatureBody(byte[] data) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + if (data.Length != SIGNATURE_SIZE) + { + string message = string.Format("data.Length % {0} != 0 (data.Length:{1})", SIGNATURE_SIZE, data.Length); + throw new ArgumentException("data", message); + } + + digest = new TwlBackupHashSet(); + sign = new byte[ECC_SIGN_SIZE]; + cert = new byte[2][]; + for (int i = 0; i < cert.Length; i++) + { + cert[i] = new byte[ECC_CERT_SIZE]; + } + + SetBytes(data); + } + + public override byte this[int index] + { + get + { + if (index < 0 || index >= SIGNATURE_SIZE) + { + throw new ArgumentException("index"); + } + return ReadByte(index); + } + set + { + if (index < 0 || index >= SIGNATURE_SIZE) + { + throw new ArgumentException("index"); + } + WriteByte(index, value); + } + } + + public override int Length + { + get + { + return SIGNATURE_SIZE; + } + } + + /// + /// 指定したバイトを取得します。 + /// + /// 取得したいバイトのインデックス。 + /// で指定したバイト + public byte ReadByte(int index) + { + if (index < 0 || index >= SIGNATURE_SIZE) + { + throw new ArgumentException("index"); + } + // TODO 処理が遅ければ効率化 + byte[] bytes = GetBytes(); + return bytes[index]; + } + + /// + /// 指定したバイトを書き換えます。 + /// + /// 書き換えたいバイトのインデックス。 + /// 上書きに使うバイト。 + public void WriteByte(int index, byte b) + { + if (index < 0 || index >= SIGNATURE_SIZE) + { + throw new ArgumentException("index"); + } + // TODO 処理が遅ければ効率化 + byte[] bytes = GetBytes(); + bytes[index] = b; + SetBytes(bytes); + } + + public override byte[] GetBytes() + { + byte[] bytes = new byte[SIGNATURE_SIZE]; + + using (var ms = new MemoryStream(bytes)) + using (var bw = new BinaryWriter(ms)) + { + bw.Write(digest.banner); + bw.Write(digest.header); + foreach (var e in digest.section) + { + bw.Write(e); + } + bw.Write(sign); + foreach (var e in cert) + { + bw.Write(e); + } + bw.Write(reserved); + + Debug.Assert(ms.Position == SIGNATURE_SIZE); + } + + return bytes; + } + + public override void SetBytes(byte[] bytes) + { + if (bytes == null) + { + throw new ArgumentNullException("bytes"); + } + if (bytes.Length != SIGNATURE_SIZE) + { + string message = string.Format("bytes.Length != {0} (bytes.Length:{1})", SIGNATURE_SIZE, bytes.Length); + throw new ArgumentException("bytes", message); + } + + using (var ms = new MemoryStream(bytes)) + using (var br = new ExtBinaryReader(ms)) + { + br.Read(digest.banner); + br.Read(digest.header); + foreach (var e in digest.section) + { + br.Read(e); + } + br.Read(sign); + foreach (var e in cert) + { + br.Read(e); + } + br.Read(ref reserved); + + Debug.Assert(ms.Position == SIGNATURE_SIZE); + } + } + + public override object Clone() + { + return new SignatureBody(GetBytes()); + } + } +} diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/TWLBackupBlock.csproj.user b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/TWLBackupBlock.csproj.user new file mode 100644 index 0000000..7ff3943 --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/TWLBackupBlock.csproj.user @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/TwlBackupBlock.csproj b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/TwlBackupBlock.csproj new file mode 100644 index 0000000..6ece8d8 --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/TwlBackupBlock.csproj @@ -0,0 +1,93 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {E62F6169-868A-48E4-9B1D-C2EB3BE11F39} + Library + Properties + TwlBackupBlock + TwlBackupBlock + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + document.xml + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + document.xml + + + + + 3.5 + + + 3.5 + + + 3.5 + + + + + + + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + \ No newline at end of file diff --git a/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Utility.cs b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Utility.cs new file mode 100644 index 0000000..d12c4b3 --- /dev/null +++ b/TwlBackupChecker/TWLBackupBlock/TWLBackupBlock/Utility.cs @@ -0,0 +1,496 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Cryptography; + +namespace TwlBackupBlock +{ + /// + /// TWLバックアップデータの暗号化・復号化処理を提供します。 + /// + public class Utility + { + private const int KEY_SIZE = 16; + private const int ROUND_SCALE = 16; + + private const int AES_MAC_SIZE = 16; + private const int AES_IV_SIZE = 16; + private const int AES_SIGN_HEADER_SIZE = 32; + + private const int BANNER_SIZE = 16 * 1024; + private const int HEADER_SIZE = 160; + private const int SIGNATURE_SIZE = 1024; + + private const int ENCRYPT_BANNER_SIZE = BANNER_SIZE + AES_SIGN_HEADER_SIZE; + private const int ENCRYPT_HEADER_SIZE = HEADER_SIZE + AES_SIGN_HEADER_SIZE; + private const int ENCRYPT_SIGNATURE_SIZE = SIGNATURE_SIZE + AES_SIGN_HEADER_SIZE; + + /// + /// バックアップデータをブロックに分割して復号化をおこないます。 + /// + /// バックアップデータ。 + /// データ本体の復号鍵。 + /// 分割、復号化されたブロック。 + public static Blocks DecryptBackupData(byte[] data, byte[] key) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + if (key == null) + { + throw new ArgumentNullException("key"); + } + { + int minDataSize = ENCRYPT_BANNER_SIZE + ENCRYPT_HEADER_SIZE + ENCRYPT_SIGNATURE_SIZE; + if (data.Length < minDataSize) + { + string message = string.Format("data.Length < {0} (data.Length:{1})", data, minDataSize); + throw new ArgumentException("data", message); + } + } + if (key.Length != KEY_SIZE) + { + string message = string.Format("key.Length != {0} (key.Length:{1})", KEY_SIZE, key.Length); + throw new ArgumentException("key", message); + } + + Blocks blocks = new Blocks(); + + using (var ms = new MemoryStream(data)) + using (var br = new BinaryReader(ms)) + { + blocks.banner.body = new Body(DecryptBlock(br, BANNER_SIZE, key, out blocks.banner.mac, out blocks.banner.iv)); + HeaderBody headerBody = new HeaderBody(DecryptBlock(br, HEADER_SIZE, key, out blocks.header.mac, out blocks.header.iv)); + blocks.header.body = headerBody; + + if (!CheckSignature(headerBody)) + { + throw new Exception("ヘッダブロックのシグネチャが正しくありません"); + } + + blocks.signature.body = new SignatureBody(DecryptBlock(br, SIGNATURE_SIZE, key, out blocks.signature.mac, out blocks.signature.iv)); + + int TMD_SIZE = (int)headerBody.fileSizes[0]; + int CONTENT_SIZE = (int)headerBody.fileSizes[1]; + int SAVE_DATA_SIZE = (int)headerBody.fileSizes[2]; + int SUB_BANNER_SIZE = (int)headerBody.fileSizes[3]; + + blocks.tmd.body = new Body(DecryptBlock(br, TMD_SIZE, key, out blocks.tmd.mac, out blocks.tmd.iv)); + blocks.content.body = new Body(DecryptBlock(br, CONTENT_SIZE, key, out blocks.content.mac, out blocks.content.iv)); + blocks.saveData.body = new Body(DecryptBlock(br, SAVE_DATA_SIZE, key, out blocks.saveData.mac, out blocks.saveData.iv)); + blocks.subBanner.body = new Body(DecryptBlock(br, SUB_BANNER_SIZE, key, out blocks.subBanner.mac, out blocks.subBanner.iv)); + + Debug.Assert(ms.Position == data.Length); + } + + return blocks; + } + + /// + /// ヘッダのデータ本体のシグネチャが正しいか判定します。 + /// + /// ヘッダのデータ本体。 + /// シグネチャが正しい場合はtrue。それ以外の場合はfalse。 + private static bool CheckSignature(HeaderBody header) + { + byte[] signature = { (byte)'3', (byte)'F', (byte)'D', (byte)'T' }; + byte[] bytes = BitConverter.GetBytes(header.signature); + Debug.Assert(signature.Length == bytes.Length); + + for (int i = 0; i < bytes.Length; i++) + { + if (bytes[i] != signature[i]) + { + return false; + } + } + + return true; + } + + /// + /// binaryReaderから読み込んだブロックを復号化します。 + /// + /// ブロックを読み出すBinaryReader。 + /// 暗号化前のブロックのデータ本体のバイトサイズ。 + /// データ本体の復号鍵。 + /// ブロックのMAC。 + /// ブロックのIV。 + /// ブロックのデータ本体。 + private static byte[] DecryptBlock(BinaryReader binaryReader, int bodySize, byte[] key, out byte[] mac, out byte[] iv) + { + Debug.Assert(key.Length == KEY_SIZE); + + int encryptBodySize = RoundUp(bodySize, ROUND_SCALE); + + byte[] body = binaryReader.ReadBytes(encryptBodySize); + mac = binaryReader.ReadBytes(AES_MAC_SIZE); + iv = binaryReader.ReadBytes(AES_IV_SIZE); + + return DecryptBody(body, key, iv, bodySize); + } + + /// + /// ブロックのデータ本体を復号化します。 + /// + /// 暗号化されたデータ本体。 + /// データ本体の復号鍵。 + /// 暗号化に利用したIV。 + /// 暗号化前のブロックのデータ本体のバイトサイズ。 + /// 復号化されたデータ本体。 + public static byte[] DecryptBody(byte[] body, byte[] key, byte[] iv, int size) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (key == null) + { + throw new ArgumentNullException("key"); + } + if (iv == null) + { + throw new ArgumentNullException("iv"); + } + if (body.Length % ROUND_SCALE != 0) + { + string message = string.Format("body.Length % {0} != 0 (body.Length:{1})", ROUND_SCALE, body.Length); + throw new ArgumentException("body", message); + } + if (key.Length != KEY_SIZE) + { + string message = string.Format("key.Length != {0} (key.Length:{1})", KEY_SIZE, key.Length); + throw new ArgumentException("key", message); + } + if (iv.Length != AES_IV_SIZE) + { + string message = string.Format("iv.Length != {0} (iv.Length:{1})", AES_IV_SIZE, iv.Length); + throw new ArgumentException("iv", message); + } + if (size < 0) + { + string message = string.Format("size < 0 (size:{0})", size); + throw new ArgumentException("size", message); + } + if (size > body.Length) + { + string message = string.Format("size > body.Length (size:{0}, body.Length:{1})", size, body.Length); + throw new ArgumentException("size", message); + } + + byte[] result = new byte[size]; + + using (var ms = new MemoryStream(body)) + { + RijndaelManaged aes = new RijndaelManaged(); + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.Zeros; + aes.Key = key; + aes.IV = iv; + using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read)) + { + cs.Read(result, 0, result.Length); + } + } + + return result; + } + + /// + /// ブロックのデータ本体を暗号化します。 + /// + /// 復号化されたデータ本体。 + /// データ本体の暗号鍵。 + /// 暗号化に利用するIV。 + /// 暗号化されたデータ本体。 + public static byte[] EncryptBody(byte[] body, byte[] key, byte[] iv) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (key == null) + { + throw new ArgumentNullException("key"); + } + if (iv == null) + { + throw new ArgumentNullException("iv"); + } + if (key.Length != KEY_SIZE) + { + string message = string.Format("key.Length != {0} (key.Length:{1})", KEY_SIZE, key.Length); + throw new ArgumentException("key", message); + } + if (iv.Length != AES_IV_SIZE) + { + string message = string.Format("iv.Length != {0} (iv.Length:{1})", AES_IV_SIZE, iv.Length); + throw new ArgumentException("iv", message); + } + + byte[] result = new byte[RoundUp(body.Length, ROUND_SCALE)]; + + using (var ms = new MemoryStream(body)) + { + RijndaelManaged aes = new RijndaelManaged(); + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.Zeros; + aes.Key = key; + aes.IV = iv; + using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Read)) + { + cs.Read(result, 0, result.Length); + } + } + + return result; + } + + /// + /// ブロックのデータ本体を暗号化します。内部的にはEncryptBody(body.GetBytes(), key, iv)を呼び出します。 + /// + /// 暗号化されたデータ本体。 + /// データ本体の暗号鍵。 + /// 暗号化に利用するIV。 + /// 暗号化されたデータ本体。 + public static byte[] EncryptBody(AbstractBody body, byte[] key, byte[] iv) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + + return EncryptBody(body.GetBytes(), key, iv); + } + + /// + /// ブロックのデータ本体からハッシュを取得します。 + /// + /// 暗号化前のデータ本体。 + /// 32バイトのハッシュ。 + public static byte[] GetSha256(byte[] data) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + + var sha = new SHA256Managed(); + + byte[] buf = Enumerable.Repeat(0, RoundUp(data.Length, ROUND_SCALE)).ToArray(); + Array.Copy(data, 0, buf, 0, data.Length); + + return sha.ComputeHash(buf); + } + + /// + /// ハッシュからMACを取得します。 + /// + /// データ本体のハッシュ。 + /// AES-CMACの鍵。 + /// 16バイトのMAC。 + public static byte[] GetAesCmac(byte[] data, byte[] key) + { + return AesCmac.GetMac(data, key); + } + + /// + /// 基準値の倍数のうち、指定された整数以上で最小のものを返します。 + /// + /// 整数。 + /// 基準値。 + /// yの倍数のうち、x以上で最小の整数。 + private static int RoundUp(int x, int y) + { + return (x + y - 1) / y * y; + } + + /// + /// インポートシーケンスと同様の手順でバックアップデータを検証します。 + /// ただし、このメソッドでは署名データの検証はおこないません。 + /// 署名以外の整合性がとれており、署名の整合性がとれていない場合、このメソッドはtrueを返します。 + /// + /// 検証にかけるバックアップデータ。 + /// データ本体の暗号鍵。 + /// MACの鍵。 + /// 検証に成功した場合はtrue。それ以外の場合はfalse。 + public static bool Verify(byte[] data, byte[] aesCbcKey, byte[] aesCmacKey) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + if (aesCbcKey == null) + { + throw new ArgumentNullException("key"); + } + if (aesCbcKey.Length != KEY_SIZE) + { + string message = string.Format("key.Length != {0} (key.Length:{1})", KEY_SIZE, aesCbcKey.Length); + throw new ArgumentException("key", message); + } + + { + int minDataSize = ENCRYPT_BANNER_SIZE + ENCRYPT_HEADER_SIZE + ENCRYPT_SIGNATURE_SIZE; + if (data.Length < minDataSize) + { + Console.WriteLine("バックアップファイルサイズがバナー+ヘッダ+署名ブロック未満です"); + return false; + } + } + + Blocks blocks = new Blocks(); + + using (var ms = new MemoryStream(data)) + using (var br = new BinaryReader(ms)) + { + blocks.banner.body = new Body(DecryptBlock(br, BANNER_SIZE, aesCbcKey, out blocks.banner.mac, out blocks.banner.iv)); + HeaderBody headerBody = new HeaderBody(DecryptBlock(br, HEADER_SIZE, aesCbcKey, out blocks.header.mac, out blocks.header.iv)); + blocks.header.body = headerBody; + SignatureBody signatureBody = new SignatureBody(DecryptBlock(br, SIGNATURE_SIZE, aesCbcKey, out blocks.signature.mac, out blocks.signature.iv)); + blocks.signature.body = signatureBody; + + if (!CheckMac(blocks.header, aesCmacKey, blocks.header.mac)) + { + Console.WriteLine("ヘッダブロックのMACが一致しません"); + return false; + } + if (!CheckSignature(headerBody)) + { + Console.WriteLine("ヘッダブロックのシグネチャが正しくありません"); + return false; + } + if (!CheckMac(blocks.signature, aesCmacKey, blocks.signature.mac)) + { + Console.WriteLine("署名ブロックのMACが一致しません"); + return false; + } + /* 署名データの検証 */ + if (!CheckHash(blocks.header, signatureBody.digest.header)) + { + Console.WriteLine("ヘッダブロックのハッシュが一致しません"); + return false; + } + if (!CheckMac(blocks.banner, aesCmacKey, blocks.banner.mac)) + { + Console.WriteLine("バナーブロックのMACが一致しません"); + return false; + } + if (!CheckHash(blocks.banner, signatureBody.digest.banner)) + { + Console.WriteLine("バナーブロックのハッシュが一致しません"); + return false; + } + + int TMD_SIZE = (int)headerBody.fileSizes[0]; + int CONTENT_SIZE = (int)headerBody.fileSizes[1]; + int SAVE_DATA_SIZE = (int)headerBody.fileSizes[2]; + int SUB_BANNER_SIZE = (int)headerBody.fileSizes[3]; + + int restSize = RoundUp(TMD_SIZE, ROUND_SCALE) + RoundUp(CONTENT_SIZE, ROUND_SCALE) + + RoundUp(SAVE_DATA_SIZE, ROUND_SCALE) + RoundUp(SUB_BANNER_SIZE, ROUND_SCALE) + + AES_SIGN_HEADER_SIZE * 4; + if (data.Length - ms.Position != restSize) + { + Console.WriteLine("TMD、コンテンツ、publicセーブデータ、サブバナーブロックサイズが実際のサイズと一致しません"); + return false; + } + blocks.tmd.body = new Body(DecryptBlock(br, TMD_SIZE, aesCbcKey, out blocks.tmd.mac, out blocks.tmd.iv)); + if (!CheckMac(blocks.tmd, aesCmacKey, blocks.tmd.mac)) + { + Console.WriteLine("TMDブロックのMACが一致しません"); + return false; + } + if (!CheckHash(blocks.tmd, signatureBody.digest.section[0])) + { + Console.WriteLine("TMDブロックのハッシュが一致しません"); + return false; + } + blocks.content.body = new Body(DecryptBlock(br, CONTENT_SIZE, aesCbcKey, out blocks.content.mac, out blocks.content.iv)); + if (!CheckMac(blocks.content, aesCmacKey, blocks.content.mac)) + { + Console.WriteLine("コンテンツブロックのMACが一致しません"); + return false; + } + if (!CheckHash(blocks.content, signatureBody.digest.section[1])) + { + Console.WriteLine("コンテンツブロックのハッシュが一致しません"); + return false; + } + blocks.saveData.body = new Body(DecryptBlock(br, SAVE_DATA_SIZE, aesCbcKey, out blocks.saveData.mac, out blocks.saveData.iv)); + if (!CheckMac(blocks.saveData, aesCmacKey, blocks.saveData.mac)) + { + Console.WriteLine("publicセーブデータブロックのMACが一致しません"); + return false; + } + if (!CheckHash(blocks.saveData, signatureBody.digest.section[2])) + { + Console.WriteLine("publicセーブデータブロックのハッシュが一致しません"); + return false; + } + blocks.subBanner.body = new Body(DecryptBlock(br, SUB_BANNER_SIZE, aesCbcKey, out blocks.subBanner.mac, out blocks.subBanner.iv)); + if (!CheckMac(blocks.subBanner, aesCmacKey, blocks.subBanner.mac)) + { + Console.WriteLine("サブバナーブロックの内容とMACが一致しません"); + return false; + } + if (!CheckHash(blocks.subBanner, signatureBody.digest.section[3])) + { + Console.WriteLine("サブバナーブロックのハッシュが一致しません"); + return false; + } + + Debug.Assert(ms.Position == data.Length); + } + + return true; + } + + /// + /// ハッシュによりブロックが正しいか判定します。 + /// + /// ブロック。 + /// ハッシュ。 + /// ハッシュがブロックから生成したものと一致した場合はtrue。それ以外の場合はfalse。 + private static bool CheckHash(Block block, byte[] hash) + { + byte[] tryHash = GetSha256(block.body.GetBytes()); + Debug.Assert(tryHash.Length == hash.Length); + + for (int i = 0; i < tryHash.Length; i++) + { + if (tryHash[i] != hash[i]) + { + return false; + } + } + return true; + } + + /// + /// MACによりブロックが正しいか判定します。 + /// + /// ブロック。 + /// MACの鍵。 + /// MAC。 + /// MACがブロックから生成したものと一致した場合はtrue。それ以外の場合はfalse。 + private static bool CheckMac(Block block, byte[] key, byte[] mac) + { + byte[] tryMac = GetAesCmac(GetSha256(block.body.GetBytes()), key); + Debug.Assert(tryMac.Length == mac.Length); + + for (int i = 0; i < tryMac.Length; ++i) + { + if (tryMac[i] != mac[i]) + { + return false; + } + } + return true; + } + } +}