2 * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
21 #include <sys/mount.h>
23 #include <klay/error.h>
24 #include <klay/exception.h>
25 #include <klay/filesystem.h>
26 #include <klay/audit/logger.h>
28 #include "../../kernel-keyring.h"
29 #include "../../file-footer.h"
31 #include "ecryptfs-engine.h"
33 #define OPTION_ONLY_NEW_FILE (1 << 0)
34 #define OPTION_EXCEPT_FOR_MEDIA_FILE (1 << 1)
36 #define SUPPORTED_OPTIONS OPTION_ONLY_NEW_FILE
38 #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"
40 #define CIPHER_MODE "aes"
41 #define ENCRYPTION_TMP_DIR ".ecryptfs_temp"
44 #define ECRYPTFS_VERSION_MAJOR 0x00
45 #define ECRYPTFS_VERSION_MINOR 0x04
46 #define ECRYPTFS_VERSION ((ECRYPTFS_VERSION_MAJOR << 8) | ECRYPTFS_VERSION_MINOR)
48 #define ECRYPTFS_SALT_SIZE 8
49 #define ECRYPTFS_SIGNATURE_SIZE 16
50 #define ECRYPTFS_MAX_KEY_SIZE 64
51 #define ECRYPTFS_MAX_KEY_MOD_NAME_SIZE 16
52 #define ECRYPTFS_MAX_ENCRYPTED_KEY_SIZE 512
54 struct EcryptfsPassword {
56 PersistentPassword = 0x01,
57 SessionKeyEncryptionKeySet = 0x02
61 int32_t hashAlgorithm;
62 int32_t hashIterations;
63 int32_t sessionKeyEncryptionKeySize;
65 uint8_t sessionKeyEncryptionKey[ECRYPTFS_MAX_KEY_SIZE];
66 uint8_t signature[ECRYPTFS_SIGNATURE_SIZE + 1];
67 uint8_t salt[ECRYPTFS_SALT_SIZE];
70 struct EcryptfsPrivateKey {
73 uint8_t signature[ECRYPTFS_SIGNATURE_SIZE + 1];
74 char keyModAlias[ECRYPTFS_MAX_KEY_MOD_NAME_SIZE + 1];
78 struct EcryptfsSessionKey {
80 UserspaceShouldTryToDecrypt = 0x00000001,
81 UserspaceShouldTryToEncrypt = 0x00000002,
82 ContainsDecryptedKey = 0x00000004,
83 ContainsEncryptedKey = 0x00000008
87 int32_t encryptedKeySize;
88 int32_t decryptedKeySize;
89 uint8_t encryptedKey[ECRYPTFS_MAX_ENCRYPTED_KEY_SIZE];
90 uint8_t decryptedKey[ECRYPTFS_MAX_KEY_SIZE];
93 : flags(0), encryptedKeySize(0), decryptedKeySize(0),
94 encryptedKey{0, }, decryptedKey{0, }
96 } __attribute__((packed));
98 struct EcryptfsPayload {
110 EcryptfsSessionKey sessionKey;
111 uint8_t reserved[32];
113 EcryptfsPassword password;
114 EcryptfsPrivateKey privateKey;
117 EcryptfsPayload(Type type)
118 : version(ECRYPTFS_VERSION), type(type), flags(0), reserved{0, }
120 ::memset(&token, 0, sizeof(token));
122 } __attribute__((packed));
125 #define ECRYPTFS_IOCTL_GET_ATTRIBUTES _IOR('l', 0x10, unsigned int)
126 #define ECRYPTFS_WAS_ENCRYPTED 0x0080
127 #define ECRYPTFS_WAS_ENCRYPTED_OTHER_DEVICE 0x0100
130 #define ECRYPTFS_AUTH_TOKEN_TYPE "user"
136 off_t getAvailableSpace(const std::string& mountPoint)
138 struct statfs statbuf;
139 if (::statfs(mountPoint.c_str(), &statbuf)) {
140 throw runtime::Exception("Failed to access " + mountPoint);
143 return statbuf.f_bfree * statbuf.f_bsize;
146 bool wasEncrypted(const std::string &path)
148 #ifdef ECRYPTFS_IOCTL_GET_ATTRIBUTES
149 unsigned int attrs = 0;
153 fd = ::open(path.c_str(), O_RDWR);
155 throw runtime::Exception("Failed to open " + path);
158 if (::ioctl(fd, ECRYPTFS_IOCTL_GET_ATTRIBUTES, &attrs) == 0 &&
159 (attrs & ECRYPTFS_WAS_ENCRYPTED)) {
170 off_t getEncryptedSize(const runtime::File &file) {
171 off_t originalSize = file.size();
174 if (originalSize % 4096) {
175 originalSize = (1 + originalSize / 4096) * 4096;
178 #ifdef ECRYPTFS_IOCTL_GET_ATTRIBUTES
179 if (wasEncrypted(file.getPath())) {
180 result = originalSize;
183 result = originalSize + 2 * 4096;
184 #ifdef ECRYPTFS_IOCTL_GET_ATTRIBUTES
188 //TODO : block size have to not hard-coded.
189 // If there is a better way, the followings have to be changed.
190 blksize_t blockSize = 4096;
191 if (result % blockSize) {
192 result = (1 + result / blockSize) * blockSize;
198 off_t getDecryptedSize(const runtime::File &file) {
199 off_t originalSize = file.size();
200 off_t result = originalSize;
202 if (wasEncrypted(file.getPath())) {
203 if (originalSize > 2 * 4096) {
204 result = originalSize - 2 * 4096;
208 result = originalSize;
210 //TODO : block size have to not hard-coded.
211 // If there is a better way, the followings have to be changed.
212 blksize_t blockSize = 4096;
213 if (result % blockSize) {
214 result = (1 + result / blockSize) * blockSize;
220 bool isEnoughToCopyInPlace(const std::string& path,
221 const std::function<off_t(const runtime::File&)> getSizeFunc)
223 off_t availableSpace = getAvailableSpace(path);
225 std::function<bool(const std::string &path)> check;
226 check = [&check, &getSizeFunc, availableSpace](const std::string &path) {
227 for (runtime::DirectoryIterator iter(path), end;
228 iter != end; ++iter) {
229 if (iter->isDirectory()) {
230 if (!check(iter->getPath())) {
233 } else if (getSizeFunc(*iter) > availableSpace) {
234 //TODO : have to consider changing file size
235 // when encrypt/decrypt
245 void copyInPlace(const std::string& source, const std::string& destination,
246 const std::string& temp,
247 const std::function<bool(const std::string&)> &isTarget,
248 const std::function<void(off_t)> &addProgress)
250 for (runtime::DirectoryIterator iter(source), end;
251 iter != end; ++iter) {
252 if (iter->isDirectory()) {
253 copyInPlace(iter->getPath(), destination + "/" + iter->getName(),
254 temp, isTarget, addProgress);
255 } else if (isTarget(iter->getPath())) {
256 std::string tempFilePath = temp + "/" + iter->getName();
257 std::string destFilePath = destination + "/" + iter->getName();
259 iter->copyTo(tempFilePath);
261 if (::rename(tempFilePath.c_str(), destFilePath.c_str()) != 0) {
262 throw runtime::Exception("Failed to rename from " + tempFilePath + " to " + destFilePath);
265 addProgress(iter->size());
270 void ecryptfsMount(const std::string &source, const std::string &destination, const std::vector<unsigned char> &key, unsigned int options)
272 EcryptfsPayload payload(EcryptfsPayload::Type::PasswordToken);
273 std::string mountOption;
275 payload.token.password.flags = EcryptfsPassword::Flag::
276 SessionKeyEncryptionKeySet;
277 payload.token.password.sessionKeyEncryptionKeySize =
278 (ECRYPTFS_MAX_KEY_SIZE > key.size())? key.size() :
279 ECRYPTFS_MAX_KEY_SIZE;
280 ::memcpy(payload.token.password.sessionKeyEncryptionKey, key.data(),
281 payload.token.password.sessionKeyEncryptionKeySize);
283 std::stringstream signature;
284 signature<< std::hex << std::setfill('0') << std::setw(2);
285 for (unsigned int byte : key) {
288 for (int i = key.size(); i < ECRYPTFS_SIGNATURE_SIZE / 2; i++) {
289 signature << (unsigned int) 0;
291 ::memcpy((char *)payload.token.password.signature,
292 signature.str().c_str(), ECRYPTFS_SIGNATURE_SIZE);
294 if (KernelKeyRing::search(KEY_SPEC_USER_KEYRING, ECRYPTFS_AUTH_TOKEN_TYPE,
295 (char *)payload.token.password.signature, 0) < 0) {
296 if (KernelKeyRing::add(ECRYPTFS_AUTH_TOKEN_TYPE,
297 (char *)payload.token.password.signature,
298 (void *)&payload, sizeof(payload),
299 KEY_SPEC_USER_KEYRING) < 0) {
300 throw runtime::Exception("Unable to add token to keyring.");
304 mountOption = "ecryptfs_passthrough"
305 ",ecryptfs_cipher=" CIPHER_MODE
306 ",ecryptfs_sig=" + std::string((char *)payload.token.password.signature) +
307 ",ecryptfs_key_bytes=" + std::to_string(payload.token.password.sessionKeyEncryptionKeySize);
309 if (options & OPTION_EXCEPT_FOR_MEDIA_FILE) {
310 mountOption += ",ecryptfs_enable_filtering=" MEDIA_EXCLUSION_LIST;
313 INFO("option = " + mountOption);
314 INFO("source = " + source);
315 INFO("dest = " + destination);
317 if (::mount(source.c_str(), destination.c_str(), "ecryptfs", MS_NODEV,
318 mountOption.c_str()) != 0) {
319 throw runtime::Exception(runtime::GetSystemErrorMessage());
323 void ecryptfsUmount(const std::string &destination)
325 if (::umount(destination.c_str()) != 0) {
326 throw runtime::Exception(runtime::GetSystemErrorMessage());
329 //TODO : remove key from keyring
334 EcryptfsEngine::EcryptfsEngine(const std::string &src, const std::string &dest, const ProgressBar &prg) :
335 source(src), destination(dest), progress(prg)
339 EcryptfsEngine::~EcryptfsEngine()
343 void EcryptfsEngine::mount(const data &key, unsigned int options)
345 ecryptfsMount(source, destination, key, options);
348 void EcryptfsEngine::umount()
350 ecryptfsUmount(destination);
353 bool EcryptfsEngine::isMounted()
355 std::ifstream file("/proc/mounts");
358 while (std::getline(file, line)) {
359 std::stringstream info(line);
360 std::string src, dest, type;
362 info >> src >> dest >> type;
363 if (type == "ecryptfs" && src == source && dest == destination) {
371 void EcryptfsEngine::encrypt(const data &key, unsigned int options)
373 if (!isEnoughToCopyInPlace(source, getDecryptedSize)) {
374 throw runtime::Exception("No space to encryption");
380 ecryptfsMount(source, destination, key, options);
381 } catch (runtime::Exception &e) {
382 throw runtime::Exception("Failed to mount - " + std::string(e.what()));
386 off_t totalSize = getAvailableSpace(source), current;
387 runtime::File tempDir(destination + "/" ENCRYPTION_TMP_DIR);
389 if (tempDir.exists()) {
390 tempDir.remove(true);
392 tempDir.makeDirectory();
394 if (!(options & OPTION_ONLY_NEW_FILE)) {
395 copyInPlace(destination, destination, tempDir.getPath(),
396 [](const std::string &file) {
399 [¤t, &totalSize, this](off_t size) {
401 this->progress.update(current * 100 / totalSize);
405 tempDir.remove(true);
406 } catch (runtime::Exception &e) {
408 ecryptfsUmount(destination);
409 } catch (runtime::Exception &e) {}
410 throw runtime::Exception("Failed to encrypt file - " + std::string(e.what()));
418 void EcryptfsEngine::decrypt(const data &key, unsigned int options)
420 if (!isEnoughToCopyInPlace(destination, getEncryptedSize)) {
421 throw runtime::Exception("No space to encryption");
427 off_t totalSize = getAvailableSpace(source), current;
428 runtime::File tempDir(source + "/" ENCRYPTION_TMP_DIR);
429 runtime::File tempMountpoint(tempDir.getPath() + "/mount");
431 if (tempDir.exists()) {
432 tempDir.remove(true);
434 tempDir.makeDirectory();
436 tempMountpoint.makeDirectory();
437 ecryptfsMount(source, tempMountpoint.getPath(), key, 0);
439 copyInPlace(tempMountpoint.getPath(), source,
440 tempDir.getPath(), wasEncrypted,
441 [¤t, &totalSize, this](off_t size) {
443 this->progress.update(current * 100 / totalSize);
445 ecryptfsUmount(tempMountpoint.getPath());
447 tempDir.remove(true);
448 } catch (runtime::Exception &e) {
449 throw runtime::Exception("Failed to decrypt file - " + std::string(e.what()));
457 bool EcryptfsEngine::isKeyMetaSet()
459 return FileFooter::exist(source);
462 const EcryptfsEngine::data EcryptfsEngine::getKeyMeta()
464 return FileFooter::read(source);
467 void EcryptfsEngine::setKeyMeta(const data &meta)
469 FileFooter::write(source, meta);
472 void EcryptfsEngine::clearKeyMeta()
474 FileFooter::clear(source);
477 unsigned int EcryptfsEngine::getSupportedOptions()
479 return SUPPORTED_OPTIONS;