Ecryptfs: Change not to remain temporary directory after encryption
[platform/core/security/ode.git] / server / engine / encryption / ecryptfs-engine.cpp
1 /*
2  *  Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License
15  */
16 #include <iomanip>
17
18 #include <unistd.h>
19 #include <sys/vfs.h>
20 #include <sys/mount.h>
21
22 #include <klay/error.h>
23 #include <klay/exception.h>
24 #include <klay/filesystem.h>
25 #include <klay/audit/logger.h>
26
27 #include "../../kernel-keyring.h"
28 #include "../../file-footer.h"
29
30 #include "ecryptfs-engine.h"
31
32 #define OPTION_ONLY_NEW_FILE                    (1 << 0)
33 #define OPTION_EXCEPT_FOR_MEDIA_FILE    (1 << 1)
34
35 #define SUPPORTED_OPTIONS OPTION_ONLY_NEW_FILE
36
37 #define MEDIA_EXCLUSION_LIST "temp_video/Camera/DCIM:mp3|mpga|m4a|mp4|wav|amr|awb|wma|ogg|oga|aac|mka|flac|3gp|3ga|mid|midi|xmf|rtttl|rtx|ota|smf|spm|imy|mpeg|m4v|3gp|3gpp|3g2|3gpp2|wmv|asf|mkv|webm|ts|avi|jpg|jpeg|gif|png|bmp|wbmp|divx|flv|ac3|mov|tiff|f4v|mpeg3|voice"
38
39 #define CIPHER_MODE "aes"
40 #define ENCRYPTION_TMP_DIR ".ecryptfs_encrypted"
41
42
43 #define ECRYPTFS_VERSION_MAJOR 0x00
44 #define ECRYPTFS_VERSION_MINOR 0x04
45 #define ECRYPTFS_VERSION ((ECRYPTFS_VERSION_MAJOR << 8) | ECRYPTFS_VERSION_MINOR)
46
47 #define ECRYPTFS_MAX_KEY_BYTES 64
48 #define ECRYPTFS_MAX_ENCRYPTED_KEY_BYTES 512
49 #define ECRYPTFS_SIG_SIZE 8
50 #define ECRYPTFS_SIG_SIZE_HEX (ECRYPTFS_SIG_SIZE*2)
51 #define ECRYPTFS_PASSWORD_SIG_SIZE ECRYPTFS_SIG_SIZE_HEX
52 #define ECRYPTFS_SALT_SIZE 8
53 #define ECRYPTFS_MAX_KEY_MOD_NAME_BYTES 16
54
55 struct ecryptfs_session_key {
56 #define ECRYPTFS_USERSPACE_SHOULD_TRY_TO_DECRYPT 0x00000001
57 #define ECRYPTFS_USERSPACE_SHOULD_TRY_TO_ENCRYPT 0x00000002
58 #define ECRYPTFS_CONTAINS_DECRYPTED_KEY 0x00000004
59 #define ECRYPTFS_CONTAINS_ENCRYPTED_KEY 0x00000008
60     int32_t flags;
61     int32_t encrypted_key_size;
62     int32_t decrypted_key_size;
63     uint8_t encrypted_key[ECRYPTFS_MAX_ENCRYPTED_KEY_BYTES];
64     uint8_t decrypted_key[ECRYPTFS_MAX_KEY_BYTES];
65 };
66
67 struct ecryptfs_password {
68     int32_t password_bytes;
69     int32_t hash_algo;
70     int32_t hash_iterations;
71     int32_t session_key_encryption_key_bytes;
72 #define ECRYPTFS_PERSISTENT_PASSWORD             0x01
73 #define ECRYPTFS_SESSION_KEY_ENCRYPTION_KEY_SET  0x02
74     uint32_t flags;
75     uint8_t session_key_encryption_key[ECRYPTFS_MAX_KEY_BYTES];
76     uint8_t signature[ECRYPTFS_PASSWORD_SIG_SIZE + 1];
77     uint8_t salt[ECRYPTFS_SALT_SIZE];
78 };
79
80 struct ecryptfs_private_key {
81     uint32_t key_size;
82     uint32_t data_len;
83     uint8_t signature[ECRYPTFS_PASSWORD_SIG_SIZE + 1];
84     char key_mod_alias[ECRYPTFS_MAX_KEY_MOD_NAME_BYTES + 1];
85     uint8_t data[];
86 };
87
88 enum ecryptfs_token_types {ECRYPTFS_PASSWORD, ECRYPTFS_PRIVATE_KEY};
89
90 struct ecryptfs_auth_tok {
91     uint16_t version;
92     uint16_t token_type;
93 #define ECRYPTFS_ENCRYPT_ONLY 0x00000001
94     uint32_t flags;
95     struct ecryptfs_session_key session_key;
96     uint8_t reserved[32];
97     union {
98         struct ecryptfs_password password;
99         struct ecryptfs_private_key private_key;
100     } token;
101 } __attribute__((packed));
102
103 #if 0
104 #define ECRYPTFS_IOCTL_GET_ATTRIBUTES  _IOR('l', 0x10, unsigned int)
105 #define ECRYPTFS_WAS_ENCRYPTED 0x0080
106 #define ECRYPTFS_WAS_ENCRYPTED_OTHER_DEVICE 0x0100
107 #endif
108
109 #define ECRYPTFS_AUTH_TOKEN_TYPE "user"
110
111 namespace ode {
112
113 namespace {
114
115 unsigned long long getAvailableSpace(const std::string& mountPoint)
116 {
117         struct statfs statbuf;
118         if (::statfs(mountPoint.c_str(), &statbuf)) {
119                 throw runtime::Exception("Failed to access " + mountPoint);
120         }
121
122         return (unsigned long long)statbuf.f_bfree * statbuf.f_bsize;
123 }
124
125 bool wasEncrypted(const std::string &path)
126 {
127 #ifdef ECRYPTFS_IOCTL_GET_ATTRIBUTES
128         unsigned int attrs = 0;
129         bool ret = false;
130         int fd = 0;
131
132         fd = ::open(path.c_str(), O_RDWR);
133         if (fd < 0) {
134                 throw runtime::Exception("Failed to open " + path);
135         }
136
137         if (::ioctl(fd, ECRYPTFS_IOCTL_GET_ATTRIBUTES, &attrs) == 0 &&
138                 (attrs & ECRYPTFS_WAS_ENCRYPTED)) {
139                 ret = true;
140         }
141         ::close(fd);
142
143         return ret;
144 #else
145         return true;
146 #endif
147 }
148
149 unsigned long long getEncryptedSize(const runtime::File &file) {
150         unsigned long long originalSize = file.size();
151         unsigned long long result = 0;
152
153         if (originalSize % 4096) {
154                 originalSize = (1 + originalSize / 4096) * 4096;
155         }
156
157 #ifdef ECRYPTFS_IOCTL_GET_ATTRIBUTES
158         if (wasEncrypted(file.getPath())) {
159                 result = originalSize;
160         } else {
161 #endif
162                 result = originalSize + 2 * 4096;
163 #ifdef ECRYPTFS_IOCTL_GET_ATTRIBUTES
164         }
165 #endif
166
167         //TODO : block size have to not hard-coded.
168         //       If there is a better way, the followings have to be changed.
169         unsigned int blockSize = 4096;
170         if (result % blockSize) {
171                 result = (1 + result / blockSize) * blockSize;
172         }
173
174         return result;
175 }
176
177 unsigned long long getDecryptedSize(const runtime::File &file) {
178         unsigned long long originalSize = file.size();
179         unsigned long long result = originalSize;
180
181         if (wasEncrypted(file.getPath())) {
182                 if (originalSize > 2 * 4096) {
183                         result = originalSize - 2 * 4096;
184                 }
185         }
186
187         result = originalSize;
188
189         //TODO : block size have to not hard-coded.
190         //       If there is a better way, the followings have to be changed.
191         unsigned int blockSize = 4096;
192         if (result % blockSize) {
193                 result = (1 + result / blockSize) * blockSize;
194         }
195
196         return result;
197 }
198
199 bool isEnoughToCopyInPlace(const std::string& path,
200         const std::function<unsigned long long(const runtime::File&)> getSizeFunc)
201 {
202         unsigned long long availableSpace = getAvailableSpace(path);
203
204         std::function<bool(const std::string &path)> check;
205         check = [&check, &getSizeFunc, availableSpace](const std::string &path) {
206                 for (runtime::DirectoryIterator iter(path), end;
207                                 iter != end; ++iter) {
208                         if (iter->isDirectory()) {
209                                 if (!check(iter->getPath())) {
210                                         return false;
211                                 }
212                         } else if (getSizeFunc(*iter) > availableSpace) {
213                                 //TODO : have to consider changing file size
214                                 //                      when encrypt/decrypt
215                                 return false;
216                         }
217                 }
218                 return true;
219         };
220
221         return check(path);
222 }
223
224 void copyInPlace(const std::string& source, const std::string& destination,
225                                         const std::string& temp,
226                                         const std::function<bool(const std::string&)> &isTarget,
227                                         const std::function<void(unsigned long long)> &addProgress)
228 {
229         for (runtime::DirectoryIterator iter(source), end;
230                         iter != end; ++iter) {
231                 if (iter->isDirectory()) {
232                         copyInPlace(iter->getPath(), destination + "/" + iter->getName(),
233                                                  temp, isTarget, addProgress);
234                 } else if (isTarget(iter->getPath())) {
235                         std::string tempFilePath = temp + "/" + iter->getName();
236                         std::string destFilePath = destination + "/" + iter->getName();
237
238                         iter->copyTo(tempFilePath);
239                         iter->remove();
240                         if (::rename(tempFilePath.c_str(), destFilePath.c_str()) != 0) {
241                                 throw runtime::Exception("Failed to rename from " + tempFilePath + " to " + destFilePath);
242                         }
243
244                         addProgress(iter->size());
245                 }
246         }
247 }
248
249 void ecryptfsMount(const std::string &source, const std::string &destination, const std::vector<unsigned char> &key, unsigned int options)
250 {
251         ecryptfs_auth_tok payload;
252         std::string mountOption;
253
254         ::memset(&(payload), 0, sizeof(ecryptfs_auth_tok));
255
256         payload.version = ECRYPTFS_VERSION;
257         payload.token_type = ECRYPTFS_PASSWORD;
258         payload.token.password.flags = ECRYPTFS_SESSION_KEY_ENCRYPTION_KEY_SET;
259         payload.token.password.session_key_encryption_key_bytes =
260                 (ECRYPTFS_MAX_KEY_BYTES > key.size())? key.size() :
261                                                                                                 ECRYPTFS_MAX_KEY_BYTES;
262         ::memcpy(payload.token.password.session_key_encryption_key, key.data(),
263                                 payload.token.password.session_key_encryption_key_bytes);
264
265     std::stringstream signature;
266     signature<< std::hex << std::setfill('0') << std::setw(2);
267     for (unsigned int byte : key) {
268         signature << byte;
269     }
270         for (int i = key.size(); i < ECRYPTFS_SIG_SIZE; i++) {
271                 signature << (unsigned int) 0;
272         }
273         ::memcpy((char *)payload.token.password.signature,
274                                 signature.str().c_str(), ECRYPTFS_PASSWORD_SIG_SIZE);
275
276         if (KernelKeyRing::search(KEY_SPEC_USER_KEYRING, ECRYPTFS_AUTH_TOKEN_TYPE,
277                                         (char *)payload.token.password.signature, 0) < 0) {
278                 if (KernelKeyRing::add(ECRYPTFS_AUTH_TOKEN_TYPE,
279                                         (char *)payload.token.password.signature,
280                                         (void *)&payload, sizeof(payload),
281                                         KEY_SPEC_USER_KEYRING) < 0) {
282                         throw runtime::Exception("Unable to add token to keyring.");
283                 }
284         }
285
286         mountOption = "ecryptfs_passthrough"
287                 ",ecryptfs_cipher=" CIPHER_MODE
288                 ",ecryptfs_sig=" + std::string((char *)payload.token.password.signature) +
289                 ",ecryptfs_key_bytes=" + std::to_string(payload.token.password.session_key_encryption_key_bytes);
290
291         if (options & OPTION_EXCEPT_FOR_MEDIA_FILE) {
292                 mountOption += ",ecryptfs_enable_filtering=" MEDIA_EXCLUSION_LIST;
293         }
294
295         INFO("option = " + mountOption);
296         INFO("source = " + source);
297         INFO("dest = " + destination);
298
299         if (::mount(source.c_str(), destination.c_str(), "ecryptfs", MS_NODEV,
300                                 mountOption.c_str()) != 0) {
301                 throw runtime::Exception(runtime::GetSystemErrorMessage());
302         }
303 }
304
305 void ecryptfsUmount(const std::string &destination)
306 {
307         if (::umount(destination.c_str()) != 0) {
308                 throw runtime::Exception(runtime::GetSystemErrorMessage());
309         }
310
311         //TODO : remove key from keyring
312 }
313
314 } // namespace
315
316 EcryptfsEngine::EcryptfsEngine(const std::string &src, const std::string &dest, const ProgressBar &prg) :
317         source(src), destination(dest), progress(prg)
318 {
319 }
320
321 EcryptfsEngine::~EcryptfsEngine()
322 {
323 }
324
325 void EcryptfsEngine::mount(const data &key, unsigned int options)
326 {
327         ecryptfsMount(source, destination, key, options);
328 }
329
330 void EcryptfsEngine::umount()
331 {
332         ecryptfsUmount(destination);
333 }
334
335 void EcryptfsEngine::encrypt(const data &key, unsigned int options)
336 {
337         if (!isEnoughToCopyInPlace(source, getDecryptedSize)) {
338                 throw runtime::Exception("No space to encryption");
339         }
340
341         progress.update(0);
342
343         try {
344                 ecryptfsMount(source, destination, key, options);
345         } catch (runtime::Exception &e) {
346                 throw runtime::Exception("Failed to mount - " + std::string(e.what()));
347         }
348
349         try {
350                 unsigned long long totalSize = getAvailableSpace(source), current;
351             runtime::File tempDir(destination + "/" ENCRYPTION_TMP_DIR);
352
353                 if (tempDir.exists()) {
354                         tempDir.remove(true);
355                 }
356
357                 tempDir.makeDirectory();
358                 if (!(options & OPTION_ONLY_NEW_FILE)) {
359                         copyInPlace(destination, destination, tempDir.getPath(),
360                                         [](const std::string &file) {
361                                                 return true;
362                                         },
363                                         [&current, &totalSize, this](unsigned long long size) {
364                                                 current += size;
365                                                 this->progress.update(current * 100 / totalSize);
366                                         });
367                 }
368         } catch (runtime::Exception &e) {
369                 try {
370                         ecryptfsUmount(destination);
371                 } catch (runtime::Exception &e) {}
372                 throw runtime::Exception("Failed to encrypt file - " + std::string(e.what()));
373         }
374
375         sync();
376
377         progress.done();
378 }
379
380 void EcryptfsEngine::decrypt(const data &key, unsigned int options)
381 {
382         if (!isEnoughToCopyInPlace(destination, getEncryptedSize)) {
383                 throw runtime::Exception("No space to encryption");
384         }
385
386         progress.update(0);
387
388         try {
389                 unsigned long long totalSize = getAvailableSpace(source), current;
390             runtime::File tempDir(source + "/" ENCRYPTION_TMP_DIR);
391                 runtime::File tempMountpoint(tempDir.getPath() + "/mount");
392
393                 tempDir.makeDirectory();
394
395                 tempMountpoint.makeDirectory();
396                 ecryptfsMount(source, tempMountpoint.getPath(), key, 0);
397
398                 copyInPlace(tempMountpoint.getPath(), source,
399                                         tempDir.getPath(), wasEncrypted,
400                                         [&current, &totalSize, this](unsigned long long size) {
401                                                 current += size;
402                                                 this->progress.update(current * 100 / totalSize);
403                                         });
404                 ecryptfsUmount(tempMountpoint.getPath());
405
406                 tempDir.remove(true);
407         } catch (runtime::Exception &e) {
408                 throw runtime::Exception("Failed to decrypt file - " + std::string(e.what()));
409         }
410
411         sync();
412
413         progress.done();
414 }
415
416 bool EcryptfsEngine::isKeyMetaSet()
417 {
418     return FileFooter::exist(source);
419 }
420
421 const EcryptfsEngine::data EcryptfsEngine::getKeyMeta()
422 {
423     return FileFooter::read(source);
424 }
425
426 void EcryptfsEngine::setKeyMeta(const data &meta)
427 {
428         FileFooter::write(source, meta);
429 }
430
431 void EcryptfsEngine::clearKeyMeta()
432 {
433         FileFooter::clear(source);
434 }
435
436 unsigned int EcryptfsEngine::getSupportedOptions()
437 {
438         return SUPPORTED_OPTIONS;
439 }
440
441 } // namespace ode