/*---------------------------------------------------------------------------* Project: Horizon File: FsSampleSecureSave.cpp Copyright (C)2009-2012 Nintendo Co., Ltd. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Rev: 47440 $ *---------------------------------------------------------------------------*/ #include #include "demo.h" #include "../Common/FsSampleCommon.h" #include "FsSampleSecureSave.h" using namespace nn; /* * Overview * Because download applications and other applications write save data to an SD card, users can easily rollback save data by simply backing up the data on their PC. * * * This sample illustrates a method to detect this type of rollback. * * The A button prevents rollback the current state while saving. * The Y button reads save data and checks, at this time, if rollback has been done and displays those results. * */ /* * Basic Flow * * By using the SetSaveDataSecureValue and VerifySaveDataSecureValue functions, 64-bit values can be written to and read from the system NAND memory. * * * In a normal sequence, the same value is saved to save data and the system NAND memory. * If data is rolled back, save data values will be affected, but values saved in system NAND memory will not. * * By determining if these values match, you can detect whether rollback has been performed. */ namespace sample { namespace fs { namespace securesave { namespace { const char SAVE_DATA_ARCHIVE[] = "data:"; } // Namespace // Resets the save data. void SaveData::Reset() { // Use a random value to initialize the rollback prevention value. version = 0; UpdateVersion(); // Resets the data. userData.data1 = 0; userData.data2 = 3; } // Update the rollback prevention value. void SaveData::UpdateVersion() { bit64 old = version; do { nn::crypto::GenerateRandomBytes(&version, sizeof(version)); version &= SECURE_VALUE_MASK; // The value 0 is obtained if SetSaveDataSecureValue has never been called. // To distinguish this from the case when it has never been called, the value 0 is not used. if (version == 0) { version = 1; } } while (old == version); } // Generate a file name. void GetFileName(char* pFileName, size_t bufferSize, u32 fileIndex) { nstd::TSNPrintf(pFileName, bufferSize, "%s/Data%d", SAVE_DATA_ARCHIVE, fileIndex); } // Writes data while updating the rollback prevention counter. void Save(SaveData &saveData, u32 fileIndex) { // Update the rollback prevention value. saveData.UpdateVersion(); NN_ERR_THROW_FATAL_ALL(nn::fs::MountSaveData(SAVE_DATA_ARCHIVE)); // Write the rollback prevention value and data used by the application to save data. // We skip error handling because this is just a sample. // In code intended for actual products, be sure to carry out proper error handling. { char fileName[0x20]; GetFileName(fileName, 0x20, fileIndex); nn::fs::FileOutputStream file(fileName, false); file.Write(&saveData, sizeof(saveData), true); } // Be sure to prohibit sleep from the point CommitSaveData is called until SetSecureSaveData has completed execution. // (Here, prohibition is not used to keep the code simple) // // Regardless of whether CommitSaveData has been called and save data has been rewritten, the comp;arison of values when data is loaded will fail if SetSaveDataSecureValue is not called and the SecureSaveData value is not updated. // // This is because save data can be lost if sleep is done during this time, and during sleep the power is turned off. // // For the same reason, the save data is lost if the media where the save data is to be saved is removed during this time. // To make this time as short as possible, continuously call CommitSaveData and SetSaveDataSecureValue. NN_ERR_THROW_FATAL_ALL(nn::fs::CommitSaveData(SAVE_DATA_ARCHIVE)); u32 shift = SECURE_VALUE_SHIFT * fileIndex; nn::fs::SetSaveDataSecureValue(saveData.version << shift, SECURE_VALUE_MASK << shift); nn::fs::Unmount(SAVE_DATA_ARCHIVE); } // Loads the save data. // Performs the rewind check at the same time and returns false if rewind was done. bool Load(SaveData &saveData, u32 fileIndex) { NN_ERR_THROW_FATAL_ALL(nn::fs::MountSaveData(SAVE_DATA_ARCHIVE)); // Reads the rollback prevention counter. // We skip error handling because this is just a sample. // In code intended for actual products, be sure to carry out proper error handling. { char fileName[0x20]; GetFileName(fileName, 0x20, fileIndex); nn::fs::FileInputStream file(fileName); file.Read(&saveData, sizeof(saveData)); } nn::fs::Unmount(SAVE_DATA_ARCHIVE); NN_LOG("SecureValue on SaveDataStorage: %llu\n", saveData.version); // A determination is made that data has been rolled back if the value is invalid or does not match the system NAND value upon comparison. u32 shift = SECURE_VALUE_SHIFT * fileIndex; if ( saveData.version == (saveData.version & SECURE_VALUE_MASK) && nn::fs::VerifySaveDataSecureValue(saveData.version << shift, SECURE_VALUE_MASK << shift) ) { return true; } else { NN_LOG("SecureValue verification failure.\n"); // Resets the save data. saveData.Reset(); return false; } } // Initializes the save data. void FormatSaveData() { const size_t maxFiles = 8; const size_t maxDirectories = 8; const bool isDuplicateAll = true; // Format the save data region Result result = nn::fs::FormatSaveData(maxFiles, maxDirectories, isDuplicateAll); if (result.IsFailure()) { NN_LOG("FormatSaveData() failed.\n"); NN_ERR_THROW_FATAL_ALL(result); } // Mount in order to compose the data default state result = nn::fs::MountSaveData(SAVE_DATA_ARCHIVE); if (result.IsFailure()) { NN_LOG("MountSaveData() failed : Cannot use save data!\n"); NN_ERR_THROW_FATAL_ALL(result); } // Create each file for ( u32 i = 0; i < DATA_FILE_NUM; i++ ) { char fileName[0x20]; GetFileName(fileName, 0x20, i); nn::fs::CreateFile(fileName, sizeof(SaveData)); } // Commit NN_ERR_THROW_FATAL_ALL(nn::fs::CommitSaveData(SAVE_DATA_ARCHIVE)); // Unmount nn::fs::Unmount(SAVE_DATA_ARCHIVE); } // Save data check when starting the application // Returns true if initialized. bool InitializeSaveData() { Result result = nn::fs::MountSaveData(SAVE_DATA_ARCHIVE); if (result.IsFailure()) { NN_DBG_PRINT_RESULT(result); if ((result <= nn::fs::ResultNotFormatted()) || (result <= nn::fs::ResultBadFormat()) || (result <= nn::fs::ResultVerificationFailed())) { // Save data needs to be initialized // If a rollback prevention value has already been written to the system NAND memory region, we recommend giving a warning. // If an attempt is made to play an application saved onto different SD cards on the same system and the rollback prevention value is reset because there was no save data on one of them, the save data on the other will also become unusable. // // if (!nn::fs::VerifySaveDataSecureValue(0)) { } FormatSaveData(); // Write the data default value for ( u32 i = 0; i < DATA_FILE_NUM; i++ ) { NN_LOG("Initializing Save Data %d...", i); SaveData saveData; saveData.Reset(); Save(saveData, i); NN_LOG("done.\n"); } return true; } else { // Unexpected errors NN_ERR_THROW_FATAL_ALL(result); } } else { nn::fs::Unmount(SAVE_DATA_ARCHIVE); } return false; } }}}