1/**************************************************************************/
2/* file_access_encrypted.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "file_access_encrypted.h"
32
33#include "core/crypto/crypto_core.h"
34#include "core/string/print_string.h"
35#include "core/variant/variant.h"
36
37#include <stdio.h>
38
39Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic) {
40 ERR_FAIL_COND_V_MSG(file != nullptr, ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open.");
41 ERR_FAIL_COND_V(p_key.size() != 32, ERR_INVALID_PARAMETER);
42
43 pos = 0;
44 eofed = false;
45 use_magic = p_with_magic;
46
47 if (p_mode == MODE_WRITE_AES256) {
48 data.clear();
49 writing = true;
50 file = p_base;
51 key = p_key;
52
53 } else if (p_mode == MODE_READ) {
54 writing = false;
55 key = p_key;
56
57 if (use_magic) {
58 uint32_t magic = p_base->get_32();
59 ERR_FAIL_COND_V(magic != ENCRYPTED_HEADER_MAGIC, ERR_FILE_UNRECOGNIZED);
60 }
61
62 unsigned char md5d[16];
63 p_base->get_buffer(md5d, 16);
64 length = p_base->get_64();
65
66 unsigned char iv[16];
67 for (int i = 0; i < 16; i++) {
68 iv[i] = p_base->get_8();
69 }
70
71 base = p_base->get_position();
72 ERR_FAIL_COND_V(p_base->get_length() < base + length, ERR_FILE_CORRUPT);
73 uint64_t ds = length;
74 if (ds % 16) {
75 ds += 16 - (ds % 16);
76 }
77 data.resize(ds);
78
79 uint64_t blen = p_base->get_buffer(data.ptrw(), ds);
80 ERR_FAIL_COND_V(blen != ds, ERR_FILE_CORRUPT);
81
82 {
83 CryptoCore::AESContext ctx;
84
85 ctx.set_encode_key(key.ptrw(), 256); // Due to the nature of CFB, same key schedule is used for both encryption and decryption!
86 ctx.decrypt_cfb(ds, iv, data.ptrw(), data.ptrw());
87 }
88
89 data.resize(length);
90
91 unsigned char hash[16];
92 ERR_FAIL_COND_V(CryptoCore::md5(data.ptr(), data.size(), hash) != OK, ERR_BUG);
93
94 ERR_FAIL_COND_V_MSG(String::md5(hash) != String::md5(md5d), ERR_FILE_CORRUPT, "The MD5 sum of the decrypted file does not match the expected value. It could be that the file is corrupt, or that the provided decryption key is invalid.");
95
96 file = p_base;
97 }
98
99 return OK;
100}
101
102Error FileAccessEncrypted::open_and_parse_password(Ref<FileAccess> p_base, const String &p_key, Mode p_mode) {
103 String cs = p_key.md5_text();
104 ERR_FAIL_COND_V(cs.length() != 32, ERR_INVALID_PARAMETER);
105 Vector<uint8_t> key_md5;
106 key_md5.resize(32);
107 for (int i = 0; i < 32; i++) {
108 key_md5.write[i] = cs[i];
109 }
110
111 return open_and_parse(p_base, key_md5, p_mode);
112}
113
114Error FileAccessEncrypted::open_internal(const String &p_path, int p_mode_flags) {
115 return OK;
116}
117
118void FileAccessEncrypted::_close() {
119 if (file.is_null()) {
120 return;
121 }
122
123 if (writing) {
124 Vector<uint8_t> compressed;
125 uint64_t len = data.size();
126 if (len % 16) {
127 len += 16 - (len % 16);
128 }
129
130 unsigned char hash[16];
131 ERR_FAIL_COND(CryptoCore::md5(data.ptr(), data.size(), hash) != OK); // Bug?
132
133 compressed.resize(len);
134 memset(compressed.ptrw(), 0, len);
135 for (int i = 0; i < data.size(); i++) {
136 compressed.write[i] = data[i];
137 }
138
139 CryptoCore::AESContext ctx;
140 ctx.set_encode_key(key.ptrw(), 256);
141
142 if (use_magic) {
143 file->store_32(ENCRYPTED_HEADER_MAGIC);
144 }
145
146 file->store_buffer(hash, 16);
147 file->store_64(data.size());
148
149 unsigned char iv[16];
150 for (int i = 0; i < 16; i++) {
151 iv[i] = Math::rand() % 256;
152 file->store_8(iv[i]);
153 }
154
155 ctx.encrypt_cfb(len, iv, compressed.ptrw(), compressed.ptrw());
156
157 file->store_buffer(compressed.ptr(), compressed.size());
158 data.clear();
159 }
160
161 file.unref();
162}
163
164bool FileAccessEncrypted::is_open() const {
165 return file != nullptr;
166}
167
168String FileAccessEncrypted::get_path() const {
169 if (file.is_valid()) {
170 return file->get_path();
171 } else {
172 return "";
173 }
174}
175
176String FileAccessEncrypted::get_path_absolute() const {
177 if (file.is_valid()) {
178 return file->get_path_absolute();
179 } else {
180 return "";
181 }
182}
183
184void FileAccessEncrypted::seek(uint64_t p_position) {
185 if (p_position > get_length()) {
186 p_position = get_length();
187 }
188
189 pos = p_position;
190 eofed = false;
191}
192
193void FileAccessEncrypted::seek_end(int64_t p_position) {
194 seek(get_length() + p_position);
195}
196
197uint64_t FileAccessEncrypted::get_position() const {
198 return pos;
199}
200
201uint64_t FileAccessEncrypted::get_length() const {
202 return data.size();
203}
204
205bool FileAccessEncrypted::eof_reached() const {
206 return eofed;
207}
208
209uint8_t FileAccessEncrypted::get_8() const {
210 ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode.");
211 if (pos >= get_length()) {
212 eofed = true;
213 return 0;
214 }
215
216 uint8_t b = data[pos];
217 pos++;
218 return b;
219}
220
221uint64_t FileAccessEncrypted::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
222 ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
223 ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode.");
224
225 uint64_t to_copy = MIN(p_length, get_length() - pos);
226 for (uint64_t i = 0; i < to_copy; i++) {
227 p_dst[i] = data[pos++];
228 }
229
230 if (to_copy < p_length) {
231 eofed = true;
232 }
233
234 return to_copy;
235}
236
237Error FileAccessEncrypted::get_error() const {
238 return eofed ? ERR_FILE_EOF : OK;
239}
240
241void FileAccessEncrypted::store_buffer(const uint8_t *p_src, uint64_t p_length) {
242 ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
243 ERR_FAIL_COND(!p_src && p_length > 0);
244
245 if (pos < get_length()) {
246 for (uint64_t i = 0; i < p_length; i++) {
247 store_8(p_src[i]);
248 }
249 } else if (pos == get_length()) {
250 data.resize(pos + p_length);
251 for (uint64_t i = 0; i < p_length; i++) {
252 data.write[pos + i] = p_src[i];
253 }
254 pos += p_length;
255 }
256}
257
258void FileAccessEncrypted::flush() {
259 ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
260
261 // encrypted files keep data in memory till close()
262}
263
264void FileAccessEncrypted::store_8(uint8_t p_dest) {
265 ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
266
267 if (pos < get_length()) {
268 data.write[pos] = p_dest;
269 pos++;
270 } else if (pos == get_length()) {
271 data.push_back(p_dest);
272 pos++;
273 }
274}
275
276bool FileAccessEncrypted::file_exists(const String &p_name) {
277 Ref<FileAccess> fa = FileAccess::open(p_name, FileAccess::READ);
278 if (fa.is_null()) {
279 return false;
280 }
281 return true;
282}
283
284uint64_t FileAccessEncrypted::_get_modified_time(const String &p_file) {
285 return 0;
286}
287
288BitField<FileAccess::UnixPermissionFlags> FileAccessEncrypted::_get_unix_permissions(const String &p_file) {
289 if (file.is_valid()) {
290 return file->_get_unix_permissions(p_file);
291 }
292 return 0;
293}
294
295Error FileAccessEncrypted::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) {
296 if (file.is_valid()) {
297 return file->_set_unix_permissions(p_file, p_permissions);
298 }
299 return FAILED;
300}
301
302bool FileAccessEncrypted::_get_hidden_attribute(const String &p_file) {
303 if (file.is_valid()) {
304 return file->_get_hidden_attribute(p_file);
305 }
306 return false;
307}
308
309Error FileAccessEncrypted::_set_hidden_attribute(const String &p_file, bool p_hidden) {
310 if (file.is_valid()) {
311 return file->_set_hidden_attribute(p_file, p_hidden);
312 }
313 return FAILED;
314}
315
316bool FileAccessEncrypted::_get_read_only_attribute(const String &p_file) {
317 if (file.is_valid()) {
318 return file->_get_read_only_attribute(p_file);
319 }
320 return false;
321}
322
323Error FileAccessEncrypted::_set_read_only_attribute(const String &p_file, bool p_ro) {
324 if (file.is_valid()) {
325 return file->_set_read_only_attribute(p_file, p_ro);
326 }
327 return FAILED;
328}
329
330void FileAccessEncrypted::close() {
331 _close();
332}
333
334FileAccessEncrypted::~FileAccessEncrypted() {
335 _close();
336}
337