diff --git a/PKHeX/MainWindow/Main.cs b/PKHeX/MainWindow/Main.cs index fe9a03d84..78d2726fb 100644 --- a/PKHeX/MainWindow/Main.cs +++ b/PKHeX/MainWindow/Main.cs @@ -793,6 +793,15 @@ private void openSAV(SaveFile sav, string path) if (sav == null || sav.Version == GameVersion.Invalid) { Util.Error("Invalid save file loaded. Aborting.", path); return; } + if (!string.IsNullOrEmpty(path)) // If path is null, this is the default save + { + if (sav.RequiresMemeCrypto && !MemeCrypto.CanUseMemeCrypto()) + { + Util.Error("Your platform does not support the required cryptography components.", "In order to be able to save your changes, you must either upgrade to a newer version of Windows or disable FIPS compliance mode."); + // Don't abort loading; user can still view save and fix checksum on another platform. + } + } + // Finish setting up the save file. if (sav.IndeterminateGame && sav.Generation == 3) { diff --git a/PKHeX/PKHeX.csproj b/PKHeX/PKHeX.csproj index aa750f839..2a8ff1fef 100644 --- a/PKHeX/PKHeX.csproj +++ b/PKHeX/PKHeX.csproj @@ -426,6 +426,7 @@ + diff --git a/PKHeX/Saves/SAV7.cs b/PKHeX/Saves/SAV7.cs index 8e1387ab2..c5a5a0636 100644 --- a/PKHeX/Saves/SAV7.cs +++ b/PKHeX/Saves/SAV7.cs @@ -819,29 +819,48 @@ private void setWC6(MysteryGift wc6, int index) // Writeback Validity public override string MiscSaveChecks() { - string r = ""; + var r = new StringBuilder(); + + // MemeCrypto check + if (RequiresMemeCrypto && !MemeCrypto.CanUseMemeCrypto()) + { + r.AppendLine("Platform does not support required cryptography providers."); + r.AppendLine("Checksum will be broken until the file is saved using an OS without FIPS compliance enabled or a newer OS."); + r.AppendLine(); + } + + // FFFF checks byte[] FFFF = Enumerable.Repeat((byte)0xFF, 0x200).ToArray(); for (int i = 0; i < Data.Length / 0x200; i++) { if (!FFFF.SequenceEqual(Data.Skip(i * 0x200).Take(0x200))) continue; - r = $"0x200 chunk @ 0x{(i*0x200).ToString("X5")} is FF'd." - + Environment.NewLine + "Cyber will screw up (as of August 31st 2014)." + Environment.NewLine + Environment.NewLine; + r.AppendLine($"0x200 chunk @ 0x{(i * 0x200).ToString("X5")} is FF'd."); + r.AppendLine("Cyber will screw up (as of August 31st 2014)."); + r.AppendLine(); // Check to see if it is in the Pokedex if (i * 0x200 > PokeDex && i * 0x200 < PokeDex + 0x900) { - r += "Problem lies in the Pokedex. "; + r.Append("Problem lies in the Pokedex. "); if (i * 0x200 == PokeDex + 0x400) - r += "Remove a language flag for a species < 585, ie Petilil"; + r.Append("Remove a language flag for a species < 585, ie Petilil"); } break; } - return r; + return r.ToString(); } public override string MiscSaveInfo() { return Blocks.Aggregate("", (current, b) => current + $"{b.ID.ToString("00")}: {b.Offset.ToString("X5")}-{(b.Offset + b.Length).ToString("X5")}, {b.Length.ToString("X5")}{Environment.NewLine}"); } + + public override bool RequiresMemeCrypto + { + get + { + return true; + } + } } } diff --git a/PKHeX/Saves/SaveFile.cs b/PKHeX/Saves/SaveFile.cs index 9e8080a5c..b85a88600 100644 --- a/PKHeX/Saves/SaveFile.cs +++ b/PKHeX/Saves/SaveFile.cs @@ -550,5 +550,7 @@ public void setData(byte[] input, int Offset) input.CopyTo(Data, Offset); Edited = true; } + + public virtual bool RequiresMemeCrypto { get { return false; } } } } diff --git a/PKHeX/Saves/SaveUtil.cs b/PKHeX/Saves/SaveUtil.cs index 2992dedb5..08dfbf99c 100644 --- a/PKHeX/Saves/SaveUtil.cs +++ b/PKHeX/Saves/SaveUtil.cs @@ -586,7 +586,7 @@ internal static ushort check16(byte[] data, int blockID, ushort initial = 0) } public static byte[] Resign7(byte[] sav7) { - return MemeCrypto.Resign(sav7); + return MemeCrypto.Resign(sav7, false); } /// Calculates the 32bit checksum over an input byte array. Used in GBA save files. /// Input byte array diff --git a/PKHeX/Saves/Substructures/MemeCrypto.cs b/PKHeX/Saves/Substructures/MemeCrypto.cs index 83744f26b..bf2814596 100644 --- a/PKHeX/Saves/Substructures/MemeCrypto.cs +++ b/PKHeX/Saves/Substructures/MemeCrypto.cs @@ -14,7 +14,7 @@ private static byte[] AESECBEncrypt(byte[] key, byte[] data) { using (MemoryStream ms = new MemoryStream()) { - using (var aes = new AesCryptoServiceProvider()) + using (var aes = Util.GetAesProvider()) { aes.Mode = CipherMode.ECB; aes.Padding = PaddingMode.None; @@ -34,7 +34,7 @@ private static byte[] AESECBDecrypt(byte[] key, byte[] data) { using (MemoryStream ms = new MemoryStream()) { - using (var aes = new AesCryptoServiceProvider()) + using (var aes = Util.GetAesProvider()) { aes.Mode = CipherMode.ECB; aes.Padding = PaddingMode.None; @@ -204,64 +204,102 @@ private static byte[] ReverseCrypt(byte[] input) if (input.Length != 0x80) throw new ArgumentException("Invalid Memecrypto decryption byte[]!"); - var sha1 = new SHA1CryptoServiceProvider(); + using (var sha1 = Util.GetSHA1Provider()) + { + byte[] PubKeyDer = "307C300D06092A864886F70D0101010500036B003068026100B61E192091F90A8F76A6EAAA9A3CE58C863F39AE253F037816F5975854E07A9A456601E7C94C29759FE155C064EDDFA111443F81EF1A428CF6CD32F9DAC9D48E94CFB3F690120E8E6B9111ADDAF11E7C96208C37C0143FF2BF3D7E831141A9730203010001".ToByteArray(); + byte[] enc = new byte[0x60]; + Array.Copy(input, 0x20, enc, 0, 0x60); - byte[] PubKeyDer = "307C300D06092A864886F70D0101010500036B003068026100B61E192091F90A8F76A6EAAA9A3CE58C863F39AE253F037816F5975854E07A9A456601E7C94C29759FE155C064EDDFA111443F81EF1A428CF6CD32F9DAC9D48E94CFB3F690120E8E6B9111ADDAF11E7C96208C37C0143FF2BF3D7E831141A9730203010001".ToByteArray(); - byte[] enc = new byte[0x60]; - Array.Copy(input, 0x20, enc, 0, 0x60); + byte[] keybuf = new byte[PubKeyDer.Length + 0x20]; + Array.Copy(PubKeyDer, keybuf, PubKeyDer.Length); + Array.Copy(input, 0, keybuf, PubKeyDer.Length, 0x20); + byte[] key = sha1.ComputeHash(keybuf).Take(0x10).ToArray(); - byte[] keybuf = new byte[PubKeyDer.Length + 0x20]; - Array.Copy(PubKeyDer, keybuf, PubKeyDer.Length); - Array.Copy(input, 0, keybuf, PubKeyDer.Length, 0x20); - byte[] key = sha1.ComputeHash(keybuf).Take(0x10).ToArray(); - - byte[] RSA = RSAEncrypt(enc); - var dec = MemeCryptoAESDecrypt(key, RSA); - if (sha1.ComputeHash(dec).Take(0x8).SequenceEqual(dec.Skip(0x58))) - return dec; - RSA[0] |= 0x80; - dec = MemeCryptoAESDecrypt(key, RSA); - if (sha1.ComputeHash(dec).Take(0x8).SequenceEqual(dec.Skip(0x58))) - return dec; + byte[] RSA = RSAEncrypt(enc); + var dec = MemeCryptoAESDecrypt(key, RSA); + if (sha1.ComputeHash(dec).Take(0x8).SequenceEqual(dec.Skip(0x58))) + return dec; + RSA[0] |= 0x80; + dec = MemeCryptoAESDecrypt(key, RSA); + if (sha1.ComputeHash(dec).Take(0x8).SequenceEqual(dec.Skip(0x58))) + return dec; + } return null; } - public static byte[] Resign(byte[] sav7) + /// + /// Resigns save data. + /// + /// The save data to resign. + /// If true, throw an if MemeCrypto is unsupported. If false, calling this function will have no effect. + /// Thrown if the current platform has FIPS mode enabled on a platform that does not support the required crypto service providers. + /// The resigned save data. + public static byte[] Resign(byte[] sav7, bool throwIfUnsupported = true) { if (sav7 == null || sav7.Length != 0x6BE00) - return null; + return null; - var sha1 = new SHA1CryptoServiceProvider(); + try + { + byte[] outSav = (byte[])sav7.Clone(); - byte[] outSav = (byte[])sav7.Clone(); + using (var sha1 = Util.GetSHA1Provider()) + { + using (var sha256 = Util.GetSHA256Provider()) + { + byte[] CurSig = new byte[0x80]; + Array.Copy(sav7, 0x6BB00, CurSig, 0, 0x80); - byte[] CurSig = new byte[0x80]; - Array.Copy(sav7, 0x6BB00, CurSig, 0, 0x80); + byte[] ChecksumTable = new byte[0x140]; + Array.Copy(sav7, 0x6BC00, ChecksumTable, 0, 0x140); + byte[] Hash = sha256.ComputeHash(ChecksumTable); - byte[] ChecksumTable = new byte[0x140]; - Array.Copy(sav7, 0x6BC00, ChecksumTable, 0, 0x140); - byte[] Hash = new SHA256CryptoServiceProvider().ComputeHash(ChecksumTable); + byte[] PubKeyDer = "307C300D06092A864886F70D0101010500036B003068026100B61E192091F90A8F76A6EAAA9A3CE58C863F39AE253F037816F5975854E07A9A456601E7C94C29759FE155C064EDDFA111443F81EF1A428CF6CD32F9DAC9D48E94CFB3F690120E8E6B9111ADDAF11E7C96208C37C0143FF2BF3D7E831141A9730203010001".ToByteArray(); - byte[] PubKeyDer = "307C300D06092A864886F70D0101010500036B003068026100B61E192091F90A8F76A6EAAA9A3CE58C863F39AE253F037816F5975854E07A9A456601E7C94C29759FE155C064EDDFA111443F81EF1A428CF6CD32F9DAC9D48E94CFB3F690120E8E6B9111ADDAF11E7C96208C37C0143FF2BF3D7E831141A9730203010001".ToByteArray(); + byte[] keybuf = new byte[PubKeyDer.Length + 0x20]; + Array.Copy(PubKeyDer, keybuf, PubKeyDer.Length); + Array.Copy(Hash, 0, keybuf, PubKeyDer.Length, 0x20); + byte[] key = sha1.ComputeHash(keybuf).Take(0x10).ToArray(); - byte[] keybuf = new byte[PubKeyDer.Length + 0x20]; - Array.Copy(PubKeyDer, keybuf, PubKeyDer.Length); - Array.Copy(Hash, 0, keybuf, PubKeyDer.Length, 0x20); - byte[] key = sha1.ComputeHash(keybuf).Take(0x10).ToArray(); + byte[] secret = ReverseCrypt(CurSig) ?? new byte[0x60]; + byte[] secretWorkBuf = new byte[0x78]; + Hash.CopyTo(secretWorkBuf, 0); + Array.Copy(secret, 0, secretWorkBuf, 0x20, 0x58); + Array.Copy(sha1.ComputeHash(secretWorkBuf), 0, secret, 0x58, 8); - byte[] secret = ReverseCrypt(CurSig) ?? new byte[0x60]; - byte[] secretWorkBuf = new byte[0x78]; - Hash.CopyTo(secretWorkBuf, 0); - Array.Copy(secret, 0, secretWorkBuf, 0x20, 0x58); - Array.Copy(sha1.ComputeHash(secretWorkBuf), 0, secret, 0x58, 8); + Hash.CopyTo(outSav, 0x6BB00); + byte[] MemeCrypted = MemeCryptoAESEncrypt(key, secret); + MemeCrypted[0] &= 0x7F; + var RSA = RSADecrypt(MemeCrypted); + Array.Copy(RSA, 0, outSav, 0x6BB20, 0x60); + } + } + return outSav; + } + catch (InvalidOperationException) + { + if (throwIfUnsupported) + { + throw; + } + else + { + return (byte[])sav7.Clone(); + } + } + } - Hash.CopyTo(outSav, 0x6BB00); - byte[] MemeCrypted = MemeCryptoAESEncrypt(key, secret); - MemeCrypted[0] &= 0x7F; - var RSA = RSADecrypt(MemeCrypted); - Array.Copy(RSA, 0, outSav, 0x6BB20, 0x60); - - return outSav; + public static bool CanUseMemeCrypto() + { + try + { + Util.GetSHA256Provider(); + } + catch (InvalidOperationException) + { + return false; + } + return true; } } diff --git a/PKHeX/Util/CryptoUtil.cs b/PKHeX/Util/CryptoUtil.cs new file mode 100644 index 000000000..c6938d619 --- /dev/null +++ b/PKHeX/Util/CryptoUtil.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace PKHeX +{ + public partial class Util + { + + /// + /// Creates a new instance of , or if not supported by the current platform. + /// + /// Thrown if FIPS mode is enabled on a platform that does not support . + public static SHA1 GetSHA1Provider() + { + SHA1 provider; + try + { + provider = new SHA1CryptoServiceProvider(); + } + catch (PlatformNotSupportedException) + { + provider = new SHA1Managed(); + } + return provider; + } + + /// + /// Creates a new instance of , or if not supported by the current platform. + /// + /// Thrown if FIPS mode is enabled on a platform that does not support . + public static SHA256 GetSHA256Provider() + { + SHA256 provider; + try + { + provider = new SHA256CryptoServiceProvider(); + } + catch (PlatformNotSupportedException) + { + provider = new SHA256Managed(); + } + return provider; + } + + /// + /// Creates a new instance of , or if not supported by the current platform. + /// + /// Thrown if FIPS mode is enabled on a platform that does not support . + public static Aes GetAesProvider() + { + Aes provider; + try + { + provider = new AesCryptoServiceProvider(); + } + catch (PlatformNotSupportedException) + { + provider = new AesManaged(); + } + return provider; + } + } +}