1 /*---------------------------------------------------------------------------*
2 Project: Horizon
3 File: FsSampleSecureSave.cpp
4
5 Copyright (C)2009-2012 Nintendo Co., Ltd. All rights reserved.
6
7 These coded instructions, statements, and computer programs contain
8 proprietary information of Nintendo of America Inc. and/or Nintendo
9 Company Ltd., and are protected by Federal copyright law. They may
10 not be disclosed to third parties or copied or duplicated in any form,
11 in whole or in part, without the prior written consent of Nintendo.
12
13 $Rev: 47440 $
14 *---------------------------------------------------------------------------*/
15
16 #include <nn.h>
17 #include "demo.h"
18
19 #include "../Common/FsSampleCommon.h"
20 #include "FsSampleSecureSave.h"
21
22 using namespace nn;
23
24 /*
25 * Overview
26 *
27 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.
28 *
29 *
30 * This sample illustrates a method to detect this type of rollback.
31 *
32 * The A button prevents rollback the current state while saving.
33 * The Y button reads save data and checks, at this time, if rollback has been done and displays those results.
34 *
35 */
36
37 /*
38 * Basic Flow
39 *
40 * By using the SetSaveDataSecureValue and VerifySaveDataSecureValue functions, 64-bit values can be written to and read from the system NAND memory.
41 *
42 *
43 * In a normal sequence, the same value is saved to save data and the system NAND memory.
44 * If data is rolled back, save data values will be affected, but values saved in system NAND memory will not.
45 *
46 * By determining if these values match, you can detect whether rollback has been performed.
47 */
48
49 namespace sample { namespace fs { namespace securesave {
50
51 namespace
52 {
53 const char SAVE_DATA_ARCHIVE[] = "data:";
54 } // Namespace
55
56 // Resets the save data.
Reset()57 void SaveData::Reset()
58 {
59 // Use a random value to initialize the rollback prevention value.
60 version = 0;
61 UpdateVersion();
62
63 // Resets the data.
64 userData.data1 = 0;
65 userData.data2 = 3;
66 }
67
68 // Update the rollback prevention value.
UpdateVersion()69 void SaveData::UpdateVersion()
70 {
71 bit64 old = version;
72 do
73 {
74 nn::crypto::GenerateRandomBytes(&version, sizeof(version));
75 version &= SECURE_VALUE_MASK;
76
77 // The value 0 is obtained if SetSaveDataSecureValue has never been called.
78 // To distinguish this from the case when it has never been called, the value 0 is not used.
79 if (version == 0)
80 {
81 version = 1;
82 }
83 } while (old == version);
84 }
85
86 // Generate a file name.
GetFileName(char * pFileName,size_t bufferSize,u32 fileIndex)87 void GetFileName(char* pFileName, size_t bufferSize, u32 fileIndex)
88 {
89 nstd::TSNPrintf(pFileName, bufferSize, "%s/Data%d", SAVE_DATA_ARCHIVE, fileIndex);
90 }
91
92 // Writes data while updating the rollback prevention counter.
Save(SaveData & saveData,u32 fileIndex)93 void Save(SaveData &saveData, u32 fileIndex)
94 {
95 // Update the rollback prevention value.
96 saveData.UpdateVersion();
97
98 NN_ERR_THROW_FATAL_ALL(nn::fs::MountSaveData(SAVE_DATA_ARCHIVE));
99
100 // Write the rollback prevention value and data used by the application to save data.
101 // We skip error handling because this is just a sample.
102 // In code intended for actual products, be sure to carry out proper error handling.
103 {
104 char fileName[0x20];
105 GetFileName(fileName, 0x20, fileIndex);
106 nn::fs::FileOutputStream file(fileName, false);
107 file.Write(&saveData, sizeof(saveData), true);
108 }
109
110 // Be sure to prohibit sleep from the point CommitSaveData is called until SetSecureSaveData has completed execution.
111 // (Here, prohibition is not used to keep the code simple)
112 //
113 // 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.
114 //
115 // This is because save data can be lost if sleep is done during this time, and during sleep the power is turned off.
116 //
117 // 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.
118 // To make this time as short as possible, continuously call CommitSaveData and SetSaveDataSecureValue.
119
120 NN_ERR_THROW_FATAL_ALL(nn::fs::CommitSaveData(SAVE_DATA_ARCHIVE));
121 u32 shift = SECURE_VALUE_SHIFT * fileIndex;
122 nn::fs::SetSaveDataSecureValue(saveData.version << shift, SECURE_VALUE_MASK << shift);
123 nn::fs::Unmount(SAVE_DATA_ARCHIVE);
124 }
125
126 // Loads the save data.
127 // Performs the rewind check at the same time and returns false if rewind was done.
Load(SaveData & saveData,u32 fileIndex)128 bool Load(SaveData &saveData, u32 fileIndex)
129 {
130 NN_ERR_THROW_FATAL_ALL(nn::fs::MountSaveData(SAVE_DATA_ARCHIVE));
131
132 // Reads the rollback prevention counter.
133 // We skip error handling because this is just a sample.
134 // In code intended for actual products, be sure to carry out proper error handling.
135 {
136 char fileName[0x20];
137 GetFileName(fileName, 0x20, fileIndex);
138 nn::fs::FileInputStream file(fileName);
139 file.Read(&saveData, sizeof(saveData));
140 }
141
142 nn::fs::Unmount(SAVE_DATA_ARCHIVE);
143
144 NN_LOG("SecureValue on SaveDataStorage: %llu\n", saveData.version);
145
146 // 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.
147 u32 shift = SECURE_VALUE_SHIFT * fileIndex;
148 if ( saveData.version == (saveData.version & SECURE_VALUE_MASK) &&
149 nn::fs::VerifySaveDataSecureValue(saveData.version << shift, SECURE_VALUE_MASK << shift) )
150 {
151 return true;
152 }
153 else
154 {
155 NN_LOG("SecureValue verification failure.\n");
156 // Resets the save data.
157 saveData.Reset();
158 return false;
159 }
160 }
161
162 // Initializes the save data.
FormatSaveData()163 void FormatSaveData()
164 {
165 const size_t maxFiles = 8;
166 const size_t maxDirectories = 8;
167 const bool isDuplicateAll = true;
168
169 // Format the save data region
170 Result result = nn::fs::FormatSaveData(maxFiles, maxDirectories, isDuplicateAll);
171 if (result.IsFailure())
172 {
173 NN_LOG("FormatSaveData() failed.\n");
174 NN_ERR_THROW_FATAL_ALL(result);
175 }
176
177 // Mount in order to compose the data default state
178 result = nn::fs::MountSaveData(SAVE_DATA_ARCHIVE);
179 if (result.IsFailure())
180 {
181 NN_LOG("MountSaveData() failed : Cannot use save data!\n");
182 NN_ERR_THROW_FATAL_ALL(result);
183 }
184
185 // Create each file
186 for ( u32 i = 0; i < DATA_FILE_NUM; i++ )
187 {
188 char fileName[0x20];
189 GetFileName(fileName, 0x20, i);
190 nn::fs::CreateFile(fileName, sizeof(SaveData));
191 }
192
193 // Commit
194 NN_ERR_THROW_FATAL_ALL(nn::fs::CommitSaveData(SAVE_DATA_ARCHIVE));
195
196 // Unmount
197 nn::fs::Unmount(SAVE_DATA_ARCHIVE);
198 }
199
200 // Save data check when starting the application
201 // Returns true if initialized.
InitializeSaveData()202 bool InitializeSaveData()
203 {
204 Result result = nn::fs::MountSaveData(SAVE_DATA_ARCHIVE);
205 if (result.IsFailure())
206 {
207 NN_DBG_PRINT_RESULT(result);
208
209 if ((result <= nn::fs::ResultNotFormatted()) ||
210 (result <= nn::fs::ResultBadFormat()) ||
211 (result <= nn::fs::ResultVerificationFailed()))
212 {
213 // Save data needs to be initialized
214
215 // If a rollback prevention value has already been written to the system NAND memory region, we recommend giving a warning.
216 // 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.
217 //
218 //
219 if (!nn::fs::VerifySaveDataSecureValue(0))
220 {
221 }
222
223 FormatSaveData();
224
225 // Write the data default value
226 for ( u32 i = 0; i < DATA_FILE_NUM; i++ )
227 {
228 NN_LOG("Initializing Save Data %d...", i);
229 SaveData saveData;
230 saveData.Reset();
231 Save(saveData, i);
232 NN_LOG("done.\n");
233 }
234 return true;
235 }
236 else
237 {
238 // Unexpected errors
239 NN_ERR_THROW_FATAL_ALL(result);
240 }
241 }
242 else
243 {
244 nn::fs::Unmount(SAVE_DATA_ARCHIVE);
245 }
246 return false;
247 }
248
249 }}}
250