OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2009-2010 The Chromium OS Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 // Contains the implementation of class Mount | |
6 | |
7 // TODO(fes): Use correct ordering of file includes once the platform-specific | |
8 // calls are moved into a separate file. Right now, this is required in order | |
9 // to avoid redefinitions. | |
10 #include "base/file_util.h" | |
11 #include "base/logging.h" | |
12 #include "base/platform_thread.h" | |
13 #include "base/time.h" | |
14 #include "base/string_util.h" | |
15 #include "chromeos/utility.h" | |
16 #include "cryptohome/cryptohome_common.h" | |
17 #include "cryptohome/mount.h" | |
18 #include "cryptohome/username_passkey.h" | |
19 | |
20 extern "C" { | |
21 #include <ecryptfs.h> | |
22 #include <keyutils.h> | |
23 } | |
24 #include <dirent.h> | |
25 #include <errno.h> | |
26 #include <openssl/err.h> | |
27 #include <openssl/evp.h> | |
28 #include <openssl/rand.h> | |
29 #include <openssl/sha.h> | |
30 #include <pwd.h> | |
31 #include <signal.h> | |
32 #include <sys/mount.h> | |
33 #include <sys/stat.h> | |
34 #include <sys/types.h> | |
35 | |
36 using std::string; | |
37 | |
38 namespace cryptohome { | |
39 | |
40 const string kDefaultEntropySource = "/dev/urandom"; | |
41 const string kDefaultHomeDir = "/home/chronos/user"; | |
42 const int kDefaultMountOptions = MS_NOEXEC | MS_NOSUID | MS_NODEV; | |
43 const string kDefaultShadowRoot = "/home/.shadow"; | |
44 const string kDefaultSharedUser = "chronos"; | |
45 const string kDefaultSkeletonSource = "/etc/skel"; | |
46 const string kIncognitoUser = "incognito"; | |
47 const string kMtab = "/etc/mtab"; | |
48 const string kOpenSSLMagic = "Salted__"; | |
49 const std::string kProcDir = "/proc"; | |
50 | |
51 Mount::Mount() | |
52 : default_user_(-1), | |
53 default_group_(-1), | |
54 default_username_(kDefaultSharedUser), | |
55 entropy_source_(kDefaultEntropySource), | |
56 home_dir_(kDefaultHomeDir), | |
57 shadow_root_(kDefaultShadowRoot), | |
58 skel_source_(kDefaultSkeletonSource), | |
59 system_salt_(), | |
60 set_vault_ownership_(true) { | |
61 } | |
62 | |
63 Mount::Mount(const std::string& username, const std::string& entropy_source, | |
64 const std::string& home_dir, const std::string& shadow_root, | |
65 const std::string& skel_source) | |
66 : default_user_(-1), | |
67 default_group_(-1), | |
68 default_username_(username), | |
69 entropy_source_(entropy_source), | |
70 home_dir_(home_dir), | |
71 shadow_root_(shadow_root), | |
72 skel_source_(skel_source), | |
73 system_salt_(), | |
74 set_vault_ownership_(true) { | |
75 } | |
76 | |
77 Mount::~Mount() { | |
78 } | |
79 | |
80 bool Mount::Init() { | |
81 bool result = true; | |
82 | |
83 // Load the passwd entry for the shared user | |
84 struct passwd* user_info = getpwnam(default_username_.c_str()); | |
mschilder
2010/05/27 04:20:18
getpwnam_r() instead?
| |
85 if (user_info) { | |
86 // Store the user's uid/gid for later use in changing vault ownership | |
87 default_user_ = user_info->pw_uid; | |
88 default_group_ = user_info->pw_gid; | |
89 } else { | |
90 result = false; | |
91 } | |
92 | |
93 // One-time load of the global system salt (used in generating username | |
94 // hashes) | |
95 if (!LoadFileBytes(FilePath(StringPrintf("%s/salt", shadow_root_.c_str())), | |
96 system_salt_)) { | |
97 result = false; | |
98 } | |
99 | |
100 return result; | |
101 } | |
102 | |
103 bool Mount::EnsureCryptohome(const Credentials& credentials, bool* created) { | |
104 // If the user has an old-style cryptohome, delete it | |
105 FilePath old_image_path(StringPrintf("%s/image", | |
106 GetUserDirectory(credentials).c_str())); | |
107 if (file_util::PathExists(old_image_path)) { | |
108 file_util::Delete(FilePath(GetUserDirectory(credentials)), true); | |
109 } | |
110 // Now check for the presence of a vault directory | |
111 FilePath vault_path(GetUserVaultPath(credentials)); | |
112 if (!file_util::DirectoryExists(vault_path)) { | |
113 // If the vault directory doesn't exist, then create the cryptohome from | |
114 // scratch | |
115 bool result = CreateCryptohome(credentials, 0); | |
116 if (created) { | |
117 *created = result; | |
118 } | |
119 return result; | |
120 } | |
121 if (created) { | |
122 *created = false; | |
123 } | |
124 return true; | |
125 } | |
126 | |
127 bool Mount::MountCryptohome(const Credentials& credentials, int index, | |
128 MountError* mount_error) { | |
129 std::string username = credentials.GetFullUsername(); | |
130 if (username.compare(kIncognitoUser) == 0) { | |
131 // TODO(fes): Have incognito set error conditions? | |
132 if (mount_error) { | |
133 *mount_error = MOUNT_ERROR_NONE; | |
134 } | |
135 return MountIncognitoCryptohome(); | |
136 } | |
137 | |
138 bool created = false; | |
139 if (!EnsureCryptohome(credentials, &created)) { | |
140 LOG(ERROR) << "Error creating cryptohome."; | |
141 if (mount_error) { | |
142 *mount_error = MOUNT_ERROR_FATAL; | |
143 } | |
144 return false; | |
145 } | |
146 | |
147 FilePath user_key_file(GetUserKeyFile(credentials, index)); | |
148 | |
149 // Retrieve the user's salt for the key index | |
150 SecureBlob user_salt = GetUserSalt(credentials, index); | |
151 | |
152 // Generate the passkey wrapper (key encryption key) | |
153 SecureBlob passkey_wrapper = | |
154 PasskeyToWrapper(credentials.GetPasskey(), user_salt, 1); | |
155 | |
156 // Attempt to unwrap the master key at the index with the passkey wrapper | |
157 VaultKeyset vault_keyset; | |
158 bool mount_result = UnwrapMasterKey(user_key_file, passkey_wrapper, | |
159 &vault_keyset); | |
160 | |
161 if (!mount_result) { | |
162 if (mount_error) { | |
163 *mount_error = Mount::MOUNT_ERROR_KEY_FAILURE; | |
164 } | |
165 return false; | |
166 } | |
167 | |
168 // Add the decrypted key to the keyring so that ecryptfs can use it | |
169 string key_signature, fnek_signature; | |
170 if (!AddKeyToEcryptfsKeyring(vault_keyset, &key_signature, &fnek_signature)) { | |
171 LOG(ERROR) << "Cryptohome mount failed because of keyring failure."; | |
172 if (mount_error) { | |
173 *mount_error = MOUNT_ERROR_FATAL; | |
174 } | |
175 return false; | |
176 } | |
177 | |
178 // Specify the ecryptfs options for mounting the user's cryptohome | |
179 string ecryptfs_options = StringPrintf("ecryptfs_cipher=aes" | |
180 ",ecryptfs_key_bytes=%d" | |
181 ",ecryptfs_fnek_sig=%s,ecryptfs_sig=%s" | |
182 ",ecryptfs_unlink_sigs", | |
183 CRYPTOHOME_AES_KEY_BYTES, | |
184 fnek_signature.c_str(), | |
185 key_signature.c_str()); | |
186 | |
187 // TODO(fes): mount(1) -> mount(2): how to issue "user" | |
188 string vault_path = GetUserVaultPath(credentials); | |
189 // Attempt to mount the user's cryptohome | |
190 if (mount(vault_path.c_str(), home_dir_.c_str(), | |
191 "ecryptfs", kDefaultMountOptions, ecryptfs_options.c_str())) { | |
192 LOG(ERROR) << "Cryptohome mount failed: " << errno << " for vault: " | |
193 << vault_path; | |
194 if (mount_error) { | |
195 *mount_error = MOUNT_ERROR_FATAL; | |
196 } | |
197 return false; | |
198 } | |
199 | |
200 if (created) { | |
201 CopySkeletonForUser(credentials); | |
202 } | |
203 | |
204 if (mount_error) { | |
205 *mount_error = MOUNT_ERROR_NONE; | |
206 } | |
207 return true; | |
208 } | |
209 | |
210 bool Mount::UnmountCryptohome() { | |
211 // Try an immediate unmount | |
212 bool was_busy; | |
213 if (!Unmount(home_dir_.c_str(), false, &was_busy)) { | |
214 // If the unmount fails, do a lazy unmount | |
215 Unmount(home_dir_.c_str(), true, NULL); | |
216 if (was_busy) { | |
217 // Signal processes to close | |
218 if (TerminatePidsWithOpenFiles(home_dir_, false)) { | |
219 // Then we had to send a SIGINT to some processes with open files. Give | |
220 // a "grace" period before killing with a SIGKILL. | |
221 // TODO(fes): This isn't ideal, nor is it accurate (a static sleep can't | |
222 // guarantee that processes will clean up in time). If we switch to VFS | |
223 // namespace-based mounts, then we can get away from this construct. | |
224 PlatformThread::Sleep(100); | |
225 sync(); | |
226 if (TerminatePidsWithOpenFiles(home_dir_, true)) { | |
227 PlatformThread::Sleep(100); | |
228 } | |
229 } | |
230 } | |
231 sync(); | |
232 // TODO(fes): This should return an error condition if it is still mounted. | |
233 // Right now it doesn't, since it should get cleaned up outside of | |
234 // cryptohome since we failed over to a lazy unmount | |
235 } | |
236 | |
237 // TODO(fes): Do we need to keep this behavior? | |
238 //TerminatePidsForUser(default_user_, true); | |
239 | |
240 // Clear the user keyring if the unmount was successful | |
241 keyctl(KEYCTL_CLEAR, KEY_SPEC_USER_KEYRING); | |
242 | |
243 return true; | |
244 } | |
245 | |
246 bool Mount::RemoveCryptohome(const Credentials& credentials) { | |
247 std::string user_dir = GetUserDirectory(credentials); | |
248 CHECK(user_dir.length() > (shadow_root_.length() + 1)); | |
249 | |
250 return file_util::Delete(FilePath(user_dir), true); | |
251 } | |
252 | |
253 bool Mount::IsCryptohomeMounted() { | |
254 // Trivial string match from /etc/mtab to see if the cryptohome mount point is | |
255 // listed. This works because Chrome OS is a controlled environment and the | |
256 // only way /home/chronos/user should be mounted is if cryptohome mounted it. | |
257 string contents; | |
258 if (file_util::ReadFileToString(FilePath(kMtab), &contents)) { | |
259 if (contents.find(StringPrintf(" %s ", home_dir_.c_str()).c_str()) | |
260 != string::npos) { | |
261 return true; | |
262 } | |
263 } | |
264 return false; | |
265 } | |
266 | |
267 bool Mount::IsCryptohomeMountedForUser(const Credentials& credentials) { | |
268 // Trivial string match from /etc/mtab to see if the cryptohome mount point | |
269 // and the user's vault path are present. Assumes this user is mounted if it | |
270 // finds both. This will need to change if simultaneous login is implemented. | |
271 string contents; | |
272 if (file_util::ReadFileToString(FilePath(kMtab), &contents)) { | |
273 FilePath vault_path(GetUserVaultPath(credentials)); | |
274 if ((contents.find(StringPrintf(" %s ", home_dir_.c_str()).c_str()) | |
275 != string::npos) | |
276 && (contents.find(StringPrintf("%s ", | |
277 vault_path.value().c_str()).c_str()) | |
278 != string::npos)) { | |
279 return true; | |
280 } | |
281 } | |
282 return false; | |
283 } | |
284 | |
285 bool Mount::CreateCryptohome(const Credentials& credentials, int index) { | |
286 // Save the current umask | |
287 mode_t original_mask = umask(S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | |
288 | S_IXOTH); | |
289 | |
290 // Create the user's entry in the shadow root | |
291 FilePath user_dir(GetUserDirectory(credentials)); | |
292 file_util::CreateDirectory(user_dir); | |
293 | |
294 // Generates a new master key and salt at the given index | |
295 if (!CreateMasterKey(credentials, index)) { | |
296 umask(original_mask); | |
297 return false; | |
298 } | |
299 | |
300 // Create the user's path and set the proper ownership | |
301 FilePath vault_path(GetUserVaultPath(credentials)); | |
302 if (!file_util::CreateDirectory(vault_path)) { | |
303 LOG(ERROR) << "Couldn't create vault path: " << vault_path.value().c_str(); | |
304 umask(original_mask); | |
305 return false; | |
306 } | |
307 if (set_vault_ownership_) { | |
308 // TODO(fes): Move platform-specific calls to a separate class | |
309 if (chown(vault_path.value().c_str(), default_user_, default_group_)) { | |
310 LOG(ERROR) << "Couldn't change owner (" << default_user_ << ":" | |
311 << default_group_ << ") of vault path: " | |
312 << vault_path.value().c_str(); | |
313 umask(original_mask); | |
314 return false; | |
315 } | |
316 } | |
317 | |
318 // Restore the umask | |
319 umask(original_mask); | |
320 return true; | |
321 } | |
322 | |
323 bool Mount::TestCredentials(const Credentials& credentials) { | |
324 // Iterate over the keys in the user's entry in the shadow root to see if | |
325 // these credentials successfully decrypt any | |
326 for (int index = 0; /* loop forever */; index++) { | |
327 FilePath user_key_file(GetUserKeyFile(credentials, index)); | |
328 if (!file_util::AbsolutePath(&user_key_file)) { | |
329 break; | |
330 } | |
331 | |
332 SecureBlob user_salt = GetUserSalt(credentials, index); | |
333 | |
334 SecureBlob passkey_wrapper = PasskeyToWrapper(credentials.GetPasskey(), | |
335 user_salt, 1); | |
336 | |
337 VaultKeyset vault_keyset; | |
338 if (UnwrapMasterKey(user_key_file, passkey_wrapper, &vault_keyset)) { | |
339 return true; | |
340 } | |
341 } | |
342 return false; | |
343 } | |
344 | |
345 bool Mount::MigratePasskey(const Credentials& credentials, | |
346 const char* old_key) { | |
347 // Iterate over the keys in the user's entry in the shadow root to see if | |
348 // these credentials successfully decrypt any | |
349 std::string username = credentials.GetFullUsername(); | |
350 UsernamePasskey old_credentials(username.c_str(), username.length(), | |
351 old_key, strlen(old_key)); | |
352 for (int index = 0; /* loop forever */; index++) { | |
353 FilePath user_key_file(GetUserKeyFile(old_credentials, index)); | |
354 if (!file_util::AbsolutePath(&user_key_file)) { | |
355 break; | |
356 } | |
357 | |
358 SecureBlob user_salt = GetUserSalt(old_credentials, index); | |
359 | |
360 SecureBlob passkey_wrapper = PasskeyToWrapper(old_credentials.GetPasskey(), | |
361 user_salt, 1); | |
362 | |
363 VaultKeyset vault_keyset; | |
364 if (UnwrapMasterKey(user_key_file, passkey_wrapper, &vault_keyset)) { | |
365 // Save to the next key index first so that if there is a failure, we | |
366 // don't delete the existing working keyset. | |
367 bool save_result = SaveVaultKeyset(credentials, vault_keyset, index + 1); | |
368 if (save_result) { | |
369 // Saved okay, move to index 0. | |
370 if (!file_util::ReplaceFile(FilePath(GetUserKeyFile(credentials, | |
371 index + 1)), | |
372 FilePath(GetUserKeyFile(credentials, 0)))) { | |
373 return false; | |
374 } | |
375 if (!file_util::ReplaceFile(FilePath(GetUserSaltFile(credentials, | |
376 index + 1)), | |
377 FilePath(GetUserSaltFile(credentials, 0)))) { | |
378 return false; | |
379 } | |
380 // Remove older keys | |
381 for (index = 1; /* loop forever */; index++) { | |
382 FilePath old_user_key_file(GetUserKeyFile(old_credentials, index)); | |
383 FilePath old_user_salt_file(GetUserSaltFile(old_credentials, index)); | |
384 if (!file_util::AbsolutePath(&old_user_key_file)) { | |
385 break; | |
386 } | |
387 file_util::Delete(old_user_key_file, false); | |
388 file_util::Delete(old_user_salt_file, false); | |
389 } | |
390 return true; | |
391 } else { | |
392 // Couldn't save the vault keyset, delete it | |
393 file_util::Delete(FilePath(GetUserKeyFile(credentials, index + 1)), | |
394 false); | |
395 file_util::Delete(FilePath(GetUserSaltFile(credentials, index + 1)), | |
396 false); | |
397 return false; | |
398 } | |
399 } | |
400 } | |
401 return false; | |
402 } | |
403 | |
404 bool Mount::MountIncognitoCryptohome() { | |
405 // Attempt to mount incognitofs | |
406 if (mount("incognitofs", home_dir_.c_str(), "tmpfs", | |
407 kDefaultMountOptions, "")) { | |
408 LOG(ERROR) << "Cryptohome mount failed: " << errno << " for incognitofs"; | |
409 return false; | |
410 } | |
411 if (set_vault_ownership_) { | |
412 if (chown(home_dir_.c_str(), default_user_, default_group_)) { | |
413 LOG(ERROR) << "Couldn't change owner (" << default_user_ << ":" | |
414 << default_group_ << ") of incognitofs path: " | |
415 << home_dir_.c_str(); | |
416 bool was_busy; | |
417 Unmount(home_dir_.c_str(), false, &was_busy); | |
418 return false; | |
419 } | |
420 } | |
421 CopySkeleton(); | |
422 return true; | |
423 } | |
424 | |
425 string Mount::GetUserDirectory(const Credentials& credentials) { | |
426 return StringPrintf("%s/%s", | |
427 shadow_root_.c_str(), | |
428 credentials.GetObfuscatedUsername(system_salt_).c_str()); | |
429 } | |
430 | |
431 string Mount::GetUserSaltFile(const Credentials& credentials, int index) { | |
432 return StringPrintf("%s/%s/master.%d.salt", | |
433 shadow_root_.c_str(), | |
434 credentials.GetObfuscatedUsername(system_salt_).c_str(), | |
435 index); | |
436 } | |
437 | |
438 string Mount::GetUserKeyFile(const Credentials& credentials, int index) { | |
439 return StringPrintf("%s/%s/master.%d", | |
440 shadow_root_.c_str(), | |
441 credentials.GetObfuscatedUsername(system_salt_).c_str(), | |
442 index); | |
443 } | |
444 | |
445 string Mount::GetUserVaultPath(const Credentials& credentials) { | |
446 return StringPrintf("%s/%s/vault", | |
447 shadow_root_.c_str(), | |
448 credentials.GetObfuscatedUsername(system_salt_).c_str()); | |
449 } | |
450 | |
451 void Mount::RecursiveCopy(const FilePath& destination, | |
452 const FilePath& source) { | |
453 file_util::FileEnumerator file_enumerator(source, false, | |
454 file_util::FileEnumerator::FILES); | |
455 FilePath next_path; | |
456 while (!(next_path = file_enumerator.Next()).empty()) { | |
457 FilePath file_name = next_path.BaseName(); | |
458 FilePath destination_file = destination.Append(file_name); | |
459 file_util::CopyFile(next_path, destination_file); | |
460 if (set_vault_ownership_) { | |
461 if (chown(destination_file.value().c_str(), default_user_, | |
462 default_group_)) { | |
463 LOG(ERROR) << "Couldn't change owner (" << default_user_ << ":" | |
464 << default_group_ << ") of skeleton path: " | |
465 << destination_file.value().c_str(); | |
466 } | |
467 } | |
468 } | |
469 file_util::FileEnumerator dir_enumerator(source, false, | |
470 file_util::FileEnumerator::DIRECTORIES); | |
471 while (!(next_path = dir_enumerator.Next()).empty()) { | |
472 FilePath dir_name = next_path.BaseName(); | |
473 FilePath destination_dir = destination.Append(dir_name); | |
474 file_util::CreateDirectory(destination_dir); | |
475 if (set_vault_ownership_) { | |
476 if (chown(destination_dir.value().c_str(), default_user_, | |
477 default_group_)) { | |
478 LOG(ERROR) << "Couldn't change owner (" << default_user_ << ":" | |
479 << default_group_ << ") of skeleton path: " | |
480 << destination_dir.value().c_str(); | |
481 } | |
482 } | |
483 RecursiveCopy(destination_dir, next_path); | |
484 } | |
485 } | |
486 | |
487 void Mount::CopySkeletonForUser(const Credentials& credentials) { | |
488 if (IsCryptohomeMountedForUser(credentials)) { | |
489 CopySkeleton(); | |
490 } | |
491 } | |
492 | |
493 void Mount::CopySkeleton() { | |
494 RecursiveCopy(FilePath(home_dir_), FilePath(skel_source_)); | |
495 } | |
496 | |
497 void Mount::GetSecureRandom(unsigned char *rand, int length) const { | |
498 // TODO(fes): Get assistance from the TPM when it is available | |
499 // Seed the OpenSSL random number generator until it is happy | |
500 while (!RAND_status()) { | |
501 char buffer[256]; | |
502 file_util::ReadFile(FilePath(entropy_source_), buffer, sizeof(buffer)); | |
503 RAND_add(buffer, sizeof(buffer), sizeof(buffer)); | |
504 } | |
505 | |
506 // Have OpenSSL generate the random bytes | |
507 RAND_bytes(rand, length); | |
508 } | |
509 | |
510 bool Mount::Unmount(const std::string& path, bool lazy, bool* was_busy) { | |
511 if (lazy) { | |
512 // TODO(fes): Place platform-specific calls in a separate class so that | |
513 // they can be mocked. | |
514 if (umount2(path.c_str(), MNT_DETACH)) { | |
515 if (was_busy) { | |
516 *was_busy = (errno == EBUSY); | |
517 } | |
518 return false; | |
519 } | |
520 } else { | |
521 if (umount(path.c_str())) { | |
522 if (was_busy) { | |
523 *was_busy = (errno == EBUSY); | |
524 } | |
525 return false; | |
526 } | |
527 } | |
528 if (was_busy) { | |
529 *was_busy = false; | |
530 } | |
531 return true; | |
532 } | |
533 | |
534 bool Mount::UnwrapMasterKey(const FilePath& path, | |
535 const chromeos::Blob& passkey, | |
536 VaultKeyset* vault_keyset) { | |
537 // TODO(fes): Update this with openssl/tpm_engine or opencryptoki once | |
538 // available | |
539 // Load the encrypted master key | |
540 SecureBlob cipher_text; | |
541 if (!LoadFileBytes(path, cipher_text)) { | |
542 LOG(ERROR) << "Unable to read master key file"; | |
543 return false; | |
544 } | |
545 | |
546 unsigned int header_size = kOpenSSLMagic.length() + PKCS5_SALT_LEN; | |
547 | |
548 if (cipher_text.size() < header_size) { | |
549 LOG(ERROR) << "Master key file too short"; | |
550 return false; | |
551 } | |
552 | |
553 // Grab the salt used in converting the passkey to a key (OpenSSL | |
554 // passkey-encrypted files have the format: | |
555 // Salted__<8-byte-salt><ciphertext>) | |
556 unsigned char salt[PKCS5_SALT_LEN]; | |
557 memcpy(salt, &cipher_text[kOpenSSLMagic.length()], PKCS5_SALT_LEN); | |
558 | |
559 cipher_text.erase(cipher_text.begin(), cipher_text.begin() + header_size); | |
560 | |
561 unsigned char wrapper_key[EVP_MAX_KEY_LENGTH]; | |
562 unsigned char iv[EVP_MAX_IV_LENGTH]; | |
563 | |
564 // Convert the passkey to a key encryption key | |
565 if (!EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), salt, &passkey[0], | |
566 passkey.size(), 1, wrapper_key, iv)) { | |
567 LOG(ERROR) << "Failure converting bytes to key"; | |
568 return false; | |
569 } | |
570 | |
571 SecureBlob plain_text(cipher_text.size()); | |
572 | |
573 int final_size = 0; | |
574 int decrypt_size = plain_text.size(); | |
575 | |
576 // Do the actual decryption | |
577 EVP_CIPHER_CTX d_ctx; | |
578 EVP_CIPHER_CTX_init(&d_ctx); | |
579 EVP_DecryptInit_ex(&d_ctx, EVP_aes_256_ecb(), NULL, wrapper_key, iv); | |
580 if (!EVP_DecryptUpdate(&d_ctx, &plain_text[0], &decrypt_size, | |
581 &cipher_text[0], | |
582 cipher_text.size())) { | |
583 LOG(ERROR) << "DecryptUpdate failed"; | |
584 return false; | |
585 } | |
586 if (!EVP_DecryptFinal_ex(&d_ctx, &plain_text[decrypt_size], &final_size)) { | |
587 unsigned long err = ERR_get_error(); | |
588 ERR_load_ERR_strings(); | |
589 ERR_load_crypto_strings(); | |
590 | |
591 LOG(ERROR) << "DecryptFinal Error: " << err | |
592 << ": " << ERR_lib_error_string(err) | |
593 << ", " << ERR_func_error_string(err) | |
594 << ", " << ERR_reason_error_string(err); | |
595 | |
596 return false; | |
597 } | |
598 final_size += decrypt_size; | |
599 | |
600 plain_text.resize(final_size); | |
601 | |
602 if (plain_text.size() != VaultKeyset::SerializedSize()) { | |
603 LOG(ERROR) << "Plain text was not the correct size: " << plain_text.size() | |
604 << ", expected: " << VaultKeyset::SerializedSize(); | |
605 return false; | |
606 } | |
607 | |
608 (*vault_keyset).AssignBuffer(plain_text); | |
609 | |
610 return true; | |
611 } | |
612 | |
613 bool Mount::CreateMasterKey(const Credentials& credentials, int index) { | |
614 VaultKeyset vault_keyset; | |
615 vault_keyset.CreateRandom(*this); | |
616 return SaveVaultKeyset(credentials, vault_keyset, index); | |
617 } | |
618 | |
619 bool Mount::SaveVaultKeyset(const Credentials& credentials, | |
620 const VaultKeyset& vault_keyset, | |
621 int index) { | |
622 unsigned char wrapper_key[EVP_MAX_KEY_LENGTH]; | |
623 unsigned char iv[EVP_MAX_IV_LENGTH]; | |
624 unsigned char salt[PKCS5_SALT_LEN]; | |
625 | |
626 // Create a salt for this master key | |
627 GetSecureRandom(salt, sizeof(salt)); | |
628 SecureBlob user_salt = GetUserSalt(credentials, index, true); | |
629 | |
630 // Convert the passkey to a passkey wrapper | |
631 SecureBlob passkey_wrapper = | |
632 PasskeyToWrapper(credentials.GetPasskey(), user_salt, 1); | |
633 | |
634 // Convert the passkey wrapper to a key encryption key | |
635 if (!EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), salt, &passkey_wrapper[0], | |
636 passkey_wrapper.size(), 1, wrapper_key, iv)) { | |
637 LOG(ERROR) << "Failure converting bytes to key"; | |
638 return false; | |
639 } | |
640 | |
641 SecureBlob keyset_blob = vault_keyset.ToBuffer(); | |
642 | |
643 // Store the salt and encrypt the master key | |
644 unsigned int header_size = kOpenSSLMagic.length() + PKCS5_SALT_LEN; | |
645 SecureBlob cipher_text(header_size | |
646 + keyset_blob.size() | |
647 + EVP_CIPHER_block_size(EVP_aes_256_ecb())); | |
648 memcpy(&cipher_text[0], kOpenSSLMagic.c_str(), kOpenSSLMagic.length()); | |
649 memcpy(&cipher_text[kOpenSSLMagic.length()], salt, PKCS5_SALT_LEN); | |
650 | |
651 int current_size = header_size; | |
652 int encrypt_size = 0; | |
653 EVP_CIPHER_CTX e_ctx; | |
654 EVP_CIPHER_CTX_init(&e_ctx); | |
655 | |
656 // Encrypt the keyset | |
657 EVP_EncryptInit_ex(&e_ctx, EVP_aes_256_ecb(), NULL, wrapper_key, iv); | |
658 if (!EVP_EncryptUpdate(&e_ctx, &cipher_text[current_size], &encrypt_size, | |
659 &keyset_blob[0], | |
660 keyset_blob.size())) { | |
661 LOG(ERROR) << "EncryptUpdate failed"; | |
662 return false; | |
663 } | |
664 current_size += encrypt_size; | |
665 encrypt_size = 0; | |
666 | |
667 // Finish the encryption | |
668 if (!EVP_EncryptFinal_ex(&e_ctx, &cipher_text[current_size], &encrypt_size)) { | |
669 LOG(ERROR) << "EncryptFinal failed"; | |
670 return false; | |
671 } | |
672 current_size += encrypt_size; | |
673 cipher_text.resize(current_size); | |
674 | |
675 chromeos::SecureMemset(wrapper_key, sizeof(wrapper_key), 0); | |
676 | |
677 // Save the master key | |
678 unsigned int data_written = file_util::WriteFile( | |
679 FilePath(GetUserKeyFile(credentials, index)), | |
680 reinterpret_cast<const char*>(&cipher_text[0]), | |
681 cipher_text.size()); | |
682 | |
683 if (data_written != cipher_text.size()) { | |
684 LOG(ERROR) << "Write to master key failed"; | |
685 return false; | |
686 } | |
687 return true; | |
688 } | |
689 | |
690 SecureBlob Mount::PasskeyToWrapper(const chromeos::Blob& passkey, | |
691 const chromeos::Blob& salt, int iters) { | |
692 // TODO(fes): Update this when TPM support is available, or use a memory- | |
693 // bound strengthening algorithm. | |
694 int update_length = passkey.size(); | |
695 SecureBlob holder(CRYPTOHOME_MAX(update_length, SHA_DIGEST_LENGTH)); | |
696 memcpy(&holder[0], &passkey[0], update_length); | |
697 | |
698 // Repeatedly hash the user passkey and salt to generate the wrapper | |
699 for (int i = 0; i < iters; ++i) { | |
700 SHA_CTX ctx; | |
701 unsigned char md_value[SHA_DIGEST_LENGTH]; | |
702 | |
703 SHA1_Init(&ctx); | |
704 SHA1_Update(&ctx, &salt[0], salt.size()); | |
705 SHA1_Update(&ctx, &holder[0], update_length); | |
706 SHA1_Final(md_value, &ctx); | |
707 | |
708 memcpy(&holder[0], md_value, SHA_DIGEST_LENGTH); | |
709 update_length = SHA_DIGEST_LENGTH; | |
710 } | |
711 | |
712 holder.resize(update_length); | |
713 SecureBlob wrapper(update_length * 2); | |
714 AsciiEncodeToBuffer(holder, reinterpret_cast<char*>(&wrapper[0]), | |
715 wrapper.size()); | |
716 return wrapper; | |
717 } | |
718 | |
719 SecureBlob Mount::GetSystemSalt() { | |
720 return system_salt_; | |
721 } | |
722 | |
723 SecureBlob Mount::GetUserSalt(const Credentials& credentials, int index, | |
724 bool force) { | |
725 FilePath path(GetUserSaltFile(credentials, index)); | |
726 return GetOrCreateSalt(path, CRYPTOHOME_DEFAULT_SALT_LENGTH, force); | |
727 } | |
728 | |
729 SecureBlob Mount::GetOrCreateSalt(const FilePath& path, int length, | |
730 bool force) { | |
731 SecureBlob salt; | |
732 if (force || !file_util::PathExists(path)) { | |
733 // If this salt doesn't exist, automatically create it | |
734 salt.resize(length); | |
735 GetSecureRandom(&salt[0], salt.size()); | |
736 int data_written = file_util::WriteFile(path, | |
737 reinterpret_cast<const char*>(&salt[0]), | |
738 length); | |
739 if (data_written != length) { | |
740 LOG(ERROR) << "Could not write user salt"; | |
741 return SecureBlob(); | |
742 } | |
743 } else { | |
744 // Otherwise just load the contents of the salt | |
745 int64 file_size; | |
746 if (!file_util::GetFileSize(path, &file_size)) { | |
747 LOG(ERROR) << "Could not get size of " << path.value(); | |
748 return SecureBlob(); | |
749 } | |
750 if (file_size > INT_MAX) { | |
751 LOG(ERROR) << "File " << path.value() << " is too large: " << file_size; | |
752 return SecureBlob(); | |
753 } | |
754 salt.resize(file_size); | |
755 int data_read = file_util::ReadFile(path, reinterpret_cast<char*>(&salt[0]), | |
756 file_size); | |
757 if (data_read != file_size) { | |
758 LOG(ERROR) << "Could not read entire file " << file_size; | |
759 return SecureBlob(); | |
760 } | |
761 } | |
762 return salt; | |
763 } | |
764 | |
765 void Mount::AsciiEncodeToBuffer(const chromeos::Blob& blob, char* buffer, | |
766 int buffer_length) { | |
767 const char hex_chars[] = "0123456789abcdef"; | |
768 int i = 0; | |
769 for (chromeos::Blob::const_iterator it = blob.begin(); | |
770 it < blob.end() && (i + 1) < buffer_length; ++it) { | |
771 buffer[i++] = hex_chars[((*it) >> 4) & 0x0f]; | |
772 buffer[i++] = hex_chars[(*it) & 0x0f]; | |
773 } | |
774 if (i < buffer_length) { | |
775 buffer[i] = '\0'; | |
776 } | |
777 } | |
778 | |
779 bool Mount::AddKeyToEcryptfsKeyring(const VaultKeyset& vault_keyset, | |
780 string* key_signature, | |
781 string* fnek_signature) { | |
782 // Clear the user keyring | |
783 keyctl(KEYCTL_CLEAR, KEY_SPEC_USER_KEYRING); | |
784 | |
785 // Add the FEK | |
786 *key_signature = chromeos::AsciiEncode(vault_keyset.FEK_SIG()); | |
787 if (!PushVaultKey(vault_keyset.FEK(), *key_signature, | |
788 vault_keyset.FEK_SALT())) { | |
789 LOG(ERROR) << "Couldn't add ecryptfs key to keyring"; | |
790 return false; | |
791 } | |
792 | |
793 // Add the FNEK | |
794 *fnek_signature = chromeos::AsciiEncode(vault_keyset.FNEK_SIG()); | |
795 if (!PushVaultKey(vault_keyset.FNEK(), *fnek_signature, | |
796 vault_keyset.FNEK_SALT())) { | |
797 LOG(ERROR) << "Couldn't add ecryptfs fnek key to keyring"; | |
798 return false; | |
799 } | |
800 | |
801 return true; | |
802 } | |
803 | |
804 bool Mount::PushVaultKey(const SecureBlob& key, const std::string& key_sig, | |
805 const SecureBlob& salt) { | |
806 DCHECK(key.size() == ECRYPTFS_MAX_KEY_BYTES); | |
807 DCHECK(key_sig.length() == (ECRYPTFS_SIG_SIZE * 2)); | |
808 DCHECK(salt.size() == ECRYPTFS_SALT_SIZE); | |
809 | |
810 struct ecryptfs_auth_tok auth_token; | |
811 | |
812 generate_payload(&auth_token, const_cast<char*>(key_sig.c_str()), | |
813 const_cast<char*>(reinterpret_cast<const char*>(&salt[0])), | |
814 const_cast<char*>(reinterpret_cast<const char*>(&key[0]))); | |
815 | |
816 if (ecryptfs_add_auth_tok_to_keyring(&auth_token, | |
817 const_cast<char*>(key_sig.c_str())) < 0) { | |
818 LOG(ERROR) << "PushVaultKey failed"; | |
819 } | |
820 | |
821 return true; | |
822 } | |
823 | |
824 bool Mount::LoadFileBytes(const FilePath& path, | |
825 SecureBlob& blob) { | |
826 int64 file_size; | |
827 if (!file_util::PathExists(path)) { | |
828 LOG(ERROR) << path.value() << " does not exist!"; | |
829 return false; | |
830 } | |
831 if (!file_util::GetFileSize(path, &file_size)) { | |
832 LOG(ERROR) << "Could not get size of " << path.value(); | |
833 return false; | |
834 } | |
835 if (file_size > INT_MAX) { | |
836 LOG(ERROR) << "File " << path.value() << " is too large: " << file_size; | |
837 return false; | |
838 } | |
839 SecureBlob buf(file_size); | |
840 int data_read = file_util::ReadFile(path, reinterpret_cast<char*>(&buf[0]), | |
841 file_size); | |
842 // Cast is okay because of comparison to INT_MAX above | |
843 if (data_read != static_cast<int>(file_size)) { | |
844 LOG(ERROR) << "Could not read entire file " << file_size; | |
845 return false; | |
846 } | |
847 blob.swap(buf); | |
848 return true; | |
849 } | |
850 | |
851 bool Mount::TerminatePidsWithOpenFiles(const std::string& path, bool hard) { | |
852 std::vector<pid_t> pids = LookForOpenFiles(path); | |
853 for (std::vector<pid_t>::iterator it = pids.begin(); it != pids.end(); it++) { | |
854 pid_t pid = static_cast<pid_t>(*it); | |
855 if (pid != getpid()) { | |
856 if (hard) { | |
857 kill(pid, SIGTERM); | |
858 } else { | |
859 kill(pid, SIGKILL); | |
860 } | |
861 } | |
862 } | |
863 return (pids.size() != 0); | |
864 } | |
865 | |
866 std::vector<pid_t> Mount::LookForOpenFiles(const std::string& path_in) { | |
867 // Make sure that if we get a directory, it has a trailing separator | |
868 FilePath file_path(path_in); | |
869 file_util::EnsureEndsWithSeparator(&file_path); | |
870 std::string path = file_path.value(); | |
871 | |
872 std::vector<pid_t> pids; | |
873 | |
874 // Open /proc | |
875 DIR* proc_dir = opendir(kProcDir.c_str()); | |
876 | |
877 if (!proc_dir) { | |
878 return pids; | |
879 } | |
880 | |
881 int linkbuf_length = path.length(); | |
882 std::vector<char> linkbuf(linkbuf_length); | |
883 | |
884 // List PIDs in /proc | |
885 while (struct dirent* pid_dirent = readdir(proc_dir)) { | |
886 pid_t pid = static_cast<pid_t>(atoi(pid_dirent->d_name)); | |
887 // Ignore PID 1 and errors | |
888 if (pid <= 1) { | |
889 continue; | |
890 } | |
891 // Open /proc/<pid>/fd | |
892 std::string fd_dirname = StringPrintf("%s/%d/fd", kProcDir.c_str(), pid); | |
893 DIR* fd_dir = opendir(fd_dirname.c_str()); | |
894 if (!fd_dir) { | |
895 continue; | |
896 } | |
897 | |
898 // List open file descriptors | |
899 while (struct dirent* fd_dirent = readdir(fd_dir)) { | |
mschilder
2010/05/27 04:20:18
readdir_r()?
| |
900 std::string fd_path = StringPrintf("%s/%s", fd_dirname.c_str(), | |
901 fd_dirent->d_name); | |
902 ssize_t link_length = readlink(fd_path.c_str(), &linkbuf[0], | |
903 linkbuf.size()); | |
904 if (link_length > 0) { | |
905 std::string open_file(&linkbuf[0], link_length); | |
906 if (open_file.length() >= path.length()) { | |
907 if (open_file.substr(0, path.length()).compare(path) == 0) { | |
908 pids.push_back(pid); | |
909 break; | |
910 } | |
911 } | |
912 } | |
913 } | |
914 | |
915 closedir(fd_dir); | |
916 } | |
917 | |
918 closedir(proc_dir); | |
919 | |
920 return pids; | |
921 } | |
922 | |
923 bool Mount::TerminatePidsForUser(const uid_t uid, bool hard) { | |
924 std::vector<pid_t> pids = GetPidsForUser(uid); | |
925 for (std::vector<pid_t>::iterator it = pids.begin(); it != pids.end(); it++) { | |
926 pid_t pid = static_cast<pid_t>(*it); | |
927 if (pid != getpid()) { | |
928 if (hard) { | |
929 kill(pid, SIGTERM); | |
930 } else { | |
931 kill(pid, SIGKILL); | |
932 } | |
933 } | |
934 } | |
935 return (pids.size() != 0); | |
936 } | |
937 | |
938 // TODO(fes): Pull this into a separate helper class | |
939 std::vector<pid_t> Mount::GetPidsForUser(uid_t uid) { | |
940 std::vector<pid_t> pids; | |
941 | |
942 // Open /proc | |
943 DIR* proc_dir = opendir(kProcDir.c_str()); | |
944 | |
945 if (!proc_dir) { | |
946 return pids; | |
947 } | |
948 | |
949 // List PIDs in /proc | |
950 while (struct dirent* pid_dirent = readdir(proc_dir)) { | |
951 pid_t pid = static_cast<pid_t>(atoi(pid_dirent->d_name)); | |
952 if (pid <= 0) { | |
953 continue; | |
954 } | |
955 // Open /proc/<pid>/status | |
956 std::string stat_path = StringPrintf("%s/%d/status", kProcDir.c_str(), | |
957 pid); | |
958 string contents; | |
959 if (!file_util::ReadFileToString(FilePath(stat_path), &contents)) { | |
960 continue; | |
961 } | |
962 | |
963 size_t uid_loc = contents.find("Uid:"); | |
964 if (!uid_loc) { | |
965 continue; | |
966 } | |
967 uid_loc += 4; | |
968 | |
969 size_t uid_end = contents.find("\n", uid_loc); | |
970 if (!uid_end) { | |
971 continue; | |
972 } | |
973 | |
974 contents = contents.substr(uid_loc, uid_end - uid_loc); | |
975 | |
976 std::vector<std::string> tokens; | |
977 Tokenize(contents, " \t", &tokens); | |
978 | |
979 for (std::vector<std::string>::iterator it = tokens.begin(); | |
980 it != tokens.end(); it++) { | |
981 std::string& value = *it; | |
982 if (value.length() == 0) { | |
983 continue; | |
984 } | |
985 uid_t check_uid = static_cast<uid_t>(atoi(value.c_str())); | |
986 if (check_uid == uid) { | |
987 pids.push_back(pid); | |
988 break; | |
989 } | |
990 } | |
991 } | |
992 | |
993 closedir(proc_dir); | |
994 | |
995 return pids; | |
996 } | |
997 | |
998 } // cryptohome | |
OLD | NEW |