Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Unified Diff: src/platform/cryptohome/mount.cc

Issue 2051003: Initial patch from Will. (Closed) Base URL: ssh://git@chromiumos-git/chromiumos
Patch Set: Address style nits. Created 10 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: src/platform/cryptohome/mount.cc
diff --git a/src/platform/cryptohome/mount.cc b/src/platform/cryptohome/mount.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c1eabefcddb1284e5d9683295a7c5f8ad07e27cb
--- /dev/null
+++ b/src/platform/cryptohome/mount.cc
@@ -0,0 +1,998 @@
+// Copyright (c) 2009-2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Contains the implementation of class Mount
+
+// TODO(fes): Use correct ordering of file includes once the platform-specific
+// calls are moved into a separate file. Right now, this is required in order
+// to avoid redefinitions.
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/platform_thread.h"
+#include "base/time.h"
+#include "base/string_util.h"
+#include "chromeos/utility.h"
+#include "cryptohome/cryptohome_common.h"
+#include "cryptohome/mount.h"
+#include "cryptohome/username_passkey.h"
+
+extern "C" {
+#include <ecryptfs.h>
+#include <keyutils.h>
+}
+#include <dirent.h>
+#include <errno.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+#include <pwd.h>
+#include <signal.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+using std::string;
+
+namespace cryptohome {
+
+const string kDefaultEntropySource = "/dev/urandom";
+const string kDefaultHomeDir = "/home/chronos/user";
+const int kDefaultMountOptions = MS_NOEXEC | MS_NOSUID | MS_NODEV;
+const string kDefaultShadowRoot = "/home/.shadow";
+const string kDefaultSharedUser = "chronos";
+const string kDefaultSkeletonSource = "/etc/skel";
+const string kIncognitoUser = "incognito";
+const string kMtab = "/etc/mtab";
+const string kOpenSSLMagic = "Salted__";
+const std::string kProcDir = "/proc";
+
+Mount::Mount()
+ : default_user_(-1),
+ default_group_(-1),
+ default_username_(kDefaultSharedUser),
+ entropy_source_(kDefaultEntropySource),
+ home_dir_(kDefaultHomeDir),
+ shadow_root_(kDefaultShadowRoot),
+ skel_source_(kDefaultSkeletonSource),
+ system_salt_(),
+ set_vault_ownership_(true) {
+}
+
+Mount::Mount(const std::string& username, const std::string& entropy_source,
+ const std::string& home_dir, const std::string& shadow_root,
+ const std::string& skel_source)
+ : default_user_(-1),
+ default_group_(-1),
+ default_username_(username),
+ entropy_source_(entropy_source),
+ home_dir_(home_dir),
+ shadow_root_(shadow_root),
+ skel_source_(skel_source),
+ system_salt_(),
+ set_vault_ownership_(true) {
+}
+
+Mount::~Mount() {
+}
+
+bool Mount::Init() {
+ bool result = true;
+
+ // Load the passwd entry for the shared user
+ struct passwd* user_info = getpwnam(default_username_.c_str());
mschilder 2010/05/27 04:20:18 getpwnam_r() instead?
+ if (user_info) {
+ // Store the user's uid/gid for later use in changing vault ownership
+ default_user_ = user_info->pw_uid;
+ default_group_ = user_info->pw_gid;
+ } else {
+ result = false;
+ }
+
+ // One-time load of the global system salt (used in generating username
+ // hashes)
+ if (!LoadFileBytes(FilePath(StringPrintf("%s/salt", shadow_root_.c_str())),
+ system_salt_)) {
+ result = false;
+ }
+
+ return result;
+}
+
+bool Mount::EnsureCryptohome(const Credentials& credentials, bool* created) {
+ // If the user has an old-style cryptohome, delete it
+ FilePath old_image_path(StringPrintf("%s/image",
+ GetUserDirectory(credentials).c_str()));
+ if (file_util::PathExists(old_image_path)) {
+ file_util::Delete(FilePath(GetUserDirectory(credentials)), true);
+ }
+ // Now check for the presence of a vault directory
+ FilePath vault_path(GetUserVaultPath(credentials));
+ if (!file_util::DirectoryExists(vault_path)) {
+ // If the vault directory doesn't exist, then create the cryptohome from
+ // scratch
+ bool result = CreateCryptohome(credentials, 0);
+ if (created) {
+ *created = result;
+ }
+ return result;
+ }
+ if (created) {
+ *created = false;
+ }
+ return true;
+}
+
+bool Mount::MountCryptohome(const Credentials& credentials, int index,
+ MountError* mount_error) {
+ std::string username = credentials.GetFullUsername();
+ if (username.compare(kIncognitoUser) == 0) {
+ // TODO(fes): Have incognito set error conditions?
+ if (mount_error) {
+ *mount_error = MOUNT_ERROR_NONE;
+ }
+ return MountIncognitoCryptohome();
+ }
+
+ bool created = false;
+ if (!EnsureCryptohome(credentials, &created)) {
+ LOG(ERROR) << "Error creating cryptohome.";
+ if (mount_error) {
+ *mount_error = MOUNT_ERROR_FATAL;
+ }
+ return false;
+ }
+
+ FilePath user_key_file(GetUserKeyFile(credentials, index));
+
+ // Retrieve the user's salt for the key index
+ SecureBlob user_salt = GetUserSalt(credentials, index);
+
+ // Generate the passkey wrapper (key encryption key)
+ SecureBlob passkey_wrapper =
+ PasskeyToWrapper(credentials.GetPasskey(), user_salt, 1);
+
+ // Attempt to unwrap the master key at the index with the passkey wrapper
+ VaultKeyset vault_keyset;
+ bool mount_result = UnwrapMasterKey(user_key_file, passkey_wrapper,
+ &vault_keyset);
+
+ if (!mount_result) {
+ if (mount_error) {
+ *mount_error = Mount::MOUNT_ERROR_KEY_FAILURE;
+ }
+ return false;
+ }
+
+ // Add the decrypted key to the keyring so that ecryptfs can use it
+ string key_signature, fnek_signature;
+ if (!AddKeyToEcryptfsKeyring(vault_keyset, &key_signature, &fnek_signature)) {
+ LOG(ERROR) << "Cryptohome mount failed because of keyring failure.";
+ if (mount_error) {
+ *mount_error = MOUNT_ERROR_FATAL;
+ }
+ return false;
+ }
+
+ // Specify the ecryptfs options for mounting the user's cryptohome
+ string ecryptfs_options = StringPrintf("ecryptfs_cipher=aes"
+ ",ecryptfs_key_bytes=%d"
+ ",ecryptfs_fnek_sig=%s,ecryptfs_sig=%s"
+ ",ecryptfs_unlink_sigs",
+ CRYPTOHOME_AES_KEY_BYTES,
+ fnek_signature.c_str(),
+ key_signature.c_str());
+
+ // TODO(fes): mount(1) -> mount(2): how to issue "user"
+ string vault_path = GetUserVaultPath(credentials);
+ // Attempt to mount the user's cryptohome
+ if (mount(vault_path.c_str(), home_dir_.c_str(),
+ "ecryptfs", kDefaultMountOptions, ecryptfs_options.c_str())) {
+ LOG(ERROR) << "Cryptohome mount failed: " << errno << " for vault: "
+ << vault_path;
+ if (mount_error) {
+ *mount_error = MOUNT_ERROR_FATAL;
+ }
+ return false;
+ }
+
+ if (created) {
+ CopySkeletonForUser(credentials);
+ }
+
+ if (mount_error) {
+ *mount_error = MOUNT_ERROR_NONE;
+ }
+ return true;
+}
+
+bool Mount::UnmountCryptohome() {
+ // Try an immediate unmount
+ bool was_busy;
+ if (!Unmount(home_dir_.c_str(), false, &was_busy)) {
+ // If the unmount fails, do a lazy unmount
+ Unmount(home_dir_.c_str(), true, NULL);
+ if (was_busy) {
+ // Signal processes to close
+ if (TerminatePidsWithOpenFiles(home_dir_, false)) {
+ // Then we had to send a SIGINT to some processes with open files. Give
+ // a "grace" period before killing with a SIGKILL.
+ // TODO(fes): This isn't ideal, nor is it accurate (a static sleep can't
+ // guarantee that processes will clean up in time). If we switch to VFS
+ // namespace-based mounts, then we can get away from this construct.
+ PlatformThread::Sleep(100);
+ sync();
+ if (TerminatePidsWithOpenFiles(home_dir_, true)) {
+ PlatformThread::Sleep(100);
+ }
+ }
+ }
+ sync();
+ // TODO(fes): This should return an error condition if it is still mounted.
+ // Right now it doesn't, since it should get cleaned up outside of
+ // cryptohome since we failed over to a lazy unmount
+ }
+
+ // TODO(fes): Do we need to keep this behavior?
+ //TerminatePidsForUser(default_user_, true);
+
+ // Clear the user keyring if the unmount was successful
+ keyctl(KEYCTL_CLEAR, KEY_SPEC_USER_KEYRING);
+
+ return true;
+}
+
+bool Mount::RemoveCryptohome(const Credentials& credentials) {
+ std::string user_dir = GetUserDirectory(credentials);
+ CHECK(user_dir.length() > (shadow_root_.length() + 1));
+
+ return file_util::Delete(FilePath(user_dir), true);
+}
+
+bool Mount::IsCryptohomeMounted() {
+ // Trivial string match from /etc/mtab to see if the cryptohome mount point is
+ // listed. This works because Chrome OS is a controlled environment and the
+ // only way /home/chronos/user should be mounted is if cryptohome mounted it.
+ string contents;
+ if (file_util::ReadFileToString(FilePath(kMtab), &contents)) {
+ if (contents.find(StringPrintf(" %s ", home_dir_.c_str()).c_str())
+ != string::npos) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Mount::IsCryptohomeMountedForUser(const Credentials& credentials) {
+ // Trivial string match from /etc/mtab to see if the cryptohome mount point
+ // and the user's vault path are present. Assumes this user is mounted if it
+ // finds both. This will need to change if simultaneous login is implemented.
+ string contents;
+ if (file_util::ReadFileToString(FilePath(kMtab), &contents)) {
+ FilePath vault_path(GetUserVaultPath(credentials));
+ if ((contents.find(StringPrintf(" %s ", home_dir_.c_str()).c_str())
+ != string::npos)
+ && (contents.find(StringPrintf("%s ",
+ vault_path.value().c_str()).c_str())
+ != string::npos)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Mount::CreateCryptohome(const Credentials& credentials, int index) {
+ // Save the current umask
+ mode_t original_mask = umask(S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH
+ | S_IXOTH);
+
+ // Create the user's entry in the shadow root
+ FilePath user_dir(GetUserDirectory(credentials));
+ file_util::CreateDirectory(user_dir);
+
+ // Generates a new master key and salt at the given index
+ if (!CreateMasterKey(credentials, index)) {
+ umask(original_mask);
+ return false;
+ }
+
+ // Create the user's path and set the proper ownership
+ FilePath vault_path(GetUserVaultPath(credentials));
+ if (!file_util::CreateDirectory(vault_path)) {
+ LOG(ERROR) << "Couldn't create vault path: " << vault_path.value().c_str();
+ umask(original_mask);
+ return false;
+ }
+ if (set_vault_ownership_) {
+ // TODO(fes): Move platform-specific calls to a separate class
+ if (chown(vault_path.value().c_str(), default_user_, default_group_)) {
+ LOG(ERROR) << "Couldn't change owner (" << default_user_ << ":"
+ << default_group_ << ") of vault path: "
+ << vault_path.value().c_str();
+ umask(original_mask);
+ return false;
+ }
+ }
+
+ // Restore the umask
+ umask(original_mask);
+ return true;
+}
+
+bool Mount::TestCredentials(const Credentials& credentials) {
+ // Iterate over the keys in the user's entry in the shadow root to see if
+ // these credentials successfully decrypt any
+ for (int index = 0; /* loop forever */; index++) {
+ FilePath user_key_file(GetUserKeyFile(credentials, index));
+ if (!file_util::AbsolutePath(&user_key_file)) {
+ break;
+ }
+
+ SecureBlob user_salt = GetUserSalt(credentials, index);
+
+ SecureBlob passkey_wrapper = PasskeyToWrapper(credentials.GetPasskey(),
+ user_salt, 1);
+
+ VaultKeyset vault_keyset;
+ if (UnwrapMasterKey(user_key_file, passkey_wrapper, &vault_keyset)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Mount::MigratePasskey(const Credentials& credentials,
+ const char* old_key) {
+ // Iterate over the keys in the user's entry in the shadow root to see if
+ // these credentials successfully decrypt any
+ std::string username = credentials.GetFullUsername();
+ UsernamePasskey old_credentials(username.c_str(), username.length(),
+ old_key, strlen(old_key));
+ for (int index = 0; /* loop forever */; index++) {
+ FilePath user_key_file(GetUserKeyFile(old_credentials, index));
+ if (!file_util::AbsolutePath(&user_key_file)) {
+ break;
+ }
+
+ SecureBlob user_salt = GetUserSalt(old_credentials, index);
+
+ SecureBlob passkey_wrapper = PasskeyToWrapper(old_credentials.GetPasskey(),
+ user_salt, 1);
+
+ VaultKeyset vault_keyset;
+ if (UnwrapMasterKey(user_key_file, passkey_wrapper, &vault_keyset)) {
+ // Save to the next key index first so that if there is a failure, we
+ // don't delete the existing working keyset.
+ bool save_result = SaveVaultKeyset(credentials, vault_keyset, index + 1);
+ if (save_result) {
+ // Saved okay, move to index 0.
+ if (!file_util::ReplaceFile(FilePath(GetUserKeyFile(credentials,
+ index + 1)),
+ FilePath(GetUserKeyFile(credentials, 0)))) {
+ return false;
+ }
+ if (!file_util::ReplaceFile(FilePath(GetUserSaltFile(credentials,
+ index + 1)),
+ FilePath(GetUserSaltFile(credentials, 0)))) {
+ return false;
+ }
+ // Remove older keys
+ for (index = 1; /* loop forever */; index++) {
+ FilePath old_user_key_file(GetUserKeyFile(old_credentials, index));
+ FilePath old_user_salt_file(GetUserSaltFile(old_credentials, index));
+ if (!file_util::AbsolutePath(&old_user_key_file)) {
+ break;
+ }
+ file_util::Delete(old_user_key_file, false);
+ file_util::Delete(old_user_salt_file, false);
+ }
+ return true;
+ } else {
+ // Couldn't save the vault keyset, delete it
+ file_util::Delete(FilePath(GetUserKeyFile(credentials, index + 1)),
+ false);
+ file_util::Delete(FilePath(GetUserSaltFile(credentials, index + 1)),
+ false);
+ return false;
+ }
+ }
+ }
+ return false;
+}
+
+bool Mount::MountIncognitoCryptohome() {
+ // Attempt to mount incognitofs
+ if (mount("incognitofs", home_dir_.c_str(), "tmpfs",
+ kDefaultMountOptions, "")) {
+ LOG(ERROR) << "Cryptohome mount failed: " << errno << " for incognitofs";
+ return false;
+ }
+ if (set_vault_ownership_) {
+ if (chown(home_dir_.c_str(), default_user_, default_group_)) {
+ LOG(ERROR) << "Couldn't change owner (" << default_user_ << ":"
+ << default_group_ << ") of incognitofs path: "
+ << home_dir_.c_str();
+ bool was_busy;
+ Unmount(home_dir_.c_str(), false, &was_busy);
+ return false;
+ }
+ }
+ CopySkeleton();
+ return true;
+}
+
+string Mount::GetUserDirectory(const Credentials& credentials) {
+ return StringPrintf("%s/%s",
+ shadow_root_.c_str(),
+ credentials.GetObfuscatedUsername(system_salt_).c_str());
+}
+
+string Mount::GetUserSaltFile(const Credentials& credentials, int index) {
+ return StringPrintf("%s/%s/master.%d.salt",
+ shadow_root_.c_str(),
+ credentials.GetObfuscatedUsername(system_salt_).c_str(),
+ index);
+}
+
+string Mount::GetUserKeyFile(const Credentials& credentials, int index) {
+ return StringPrintf("%s/%s/master.%d",
+ shadow_root_.c_str(),
+ credentials.GetObfuscatedUsername(system_salt_).c_str(),
+ index);
+}
+
+string Mount::GetUserVaultPath(const Credentials& credentials) {
+ return StringPrintf("%s/%s/vault",
+ shadow_root_.c_str(),
+ credentials.GetObfuscatedUsername(system_salt_).c_str());
+}
+
+void Mount::RecursiveCopy(const FilePath& destination,
+ const FilePath& source) {
+ file_util::FileEnumerator file_enumerator(source, false,
+ file_util::FileEnumerator::FILES);
+ FilePath next_path;
+ while (!(next_path = file_enumerator.Next()).empty()) {
+ FilePath file_name = next_path.BaseName();
+ FilePath destination_file = destination.Append(file_name);
+ file_util::CopyFile(next_path, destination_file);
+ if (set_vault_ownership_) {
+ if (chown(destination_file.value().c_str(), default_user_,
+ default_group_)) {
+ LOG(ERROR) << "Couldn't change owner (" << default_user_ << ":"
+ << default_group_ << ") of skeleton path: "
+ << destination_file.value().c_str();
+ }
+ }
+ }
+ file_util::FileEnumerator dir_enumerator(source, false,
+ file_util::FileEnumerator::DIRECTORIES);
+ while (!(next_path = dir_enumerator.Next()).empty()) {
+ FilePath dir_name = next_path.BaseName();
+ FilePath destination_dir = destination.Append(dir_name);
+ file_util::CreateDirectory(destination_dir);
+ if (set_vault_ownership_) {
+ if (chown(destination_dir.value().c_str(), default_user_,
+ default_group_)) {
+ LOG(ERROR) << "Couldn't change owner (" << default_user_ << ":"
+ << default_group_ << ") of skeleton path: "
+ << destination_dir.value().c_str();
+ }
+ }
+ RecursiveCopy(destination_dir, next_path);
+ }
+}
+
+void Mount::CopySkeletonForUser(const Credentials& credentials) {
+ if (IsCryptohomeMountedForUser(credentials)) {
+ CopySkeleton();
+ }
+}
+
+void Mount::CopySkeleton() {
+ RecursiveCopy(FilePath(home_dir_), FilePath(skel_source_));
+}
+
+void Mount::GetSecureRandom(unsigned char *rand, int length) const {
+ // TODO(fes): Get assistance from the TPM when it is available
+ // Seed the OpenSSL random number generator until it is happy
+ while (!RAND_status()) {
+ char buffer[256];
+ file_util::ReadFile(FilePath(entropy_source_), buffer, sizeof(buffer));
+ RAND_add(buffer, sizeof(buffer), sizeof(buffer));
+ }
+
+ // Have OpenSSL generate the random bytes
+ RAND_bytes(rand, length);
+}
+
+bool Mount::Unmount(const std::string& path, bool lazy, bool* was_busy) {
+ if (lazy) {
+ // TODO(fes): Place platform-specific calls in a separate class so that
+ // they can be mocked.
+ if (umount2(path.c_str(), MNT_DETACH)) {
+ if (was_busy) {
+ *was_busy = (errno == EBUSY);
+ }
+ return false;
+ }
+ } else {
+ if (umount(path.c_str())) {
+ if (was_busy) {
+ *was_busy = (errno == EBUSY);
+ }
+ return false;
+ }
+ }
+ if (was_busy) {
+ *was_busy = false;
+ }
+ return true;
+}
+
+bool Mount::UnwrapMasterKey(const FilePath& path,
+ const chromeos::Blob& passkey,
+ VaultKeyset* vault_keyset) {
+ // TODO(fes): Update this with openssl/tpm_engine or opencryptoki once
+ // available
+ // Load the encrypted master key
+ SecureBlob cipher_text;
+ if (!LoadFileBytes(path, cipher_text)) {
+ LOG(ERROR) << "Unable to read master key file";
+ return false;
+ }
+
+ unsigned int header_size = kOpenSSLMagic.length() + PKCS5_SALT_LEN;
+
+ if (cipher_text.size() < header_size) {
+ LOG(ERROR) << "Master key file too short";
+ return false;
+ }
+
+ // Grab the salt used in converting the passkey to a key (OpenSSL
+ // passkey-encrypted files have the format:
+ // Salted__<8-byte-salt><ciphertext>)
+ unsigned char salt[PKCS5_SALT_LEN];
+ memcpy(salt, &cipher_text[kOpenSSLMagic.length()], PKCS5_SALT_LEN);
+
+ cipher_text.erase(cipher_text.begin(), cipher_text.begin() + header_size);
+
+ unsigned char wrapper_key[EVP_MAX_KEY_LENGTH];
+ unsigned char iv[EVP_MAX_IV_LENGTH];
+
+ // Convert the passkey to a key encryption key
+ if (!EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), salt, &passkey[0],
+ passkey.size(), 1, wrapper_key, iv)) {
+ LOG(ERROR) << "Failure converting bytes to key";
+ return false;
+ }
+
+ SecureBlob plain_text(cipher_text.size());
+
+ int final_size = 0;
+ int decrypt_size = plain_text.size();
+
+ // Do the actual decryption
+ EVP_CIPHER_CTX d_ctx;
+ EVP_CIPHER_CTX_init(&d_ctx);
+ EVP_DecryptInit_ex(&d_ctx, EVP_aes_256_ecb(), NULL, wrapper_key, iv);
+ if (!EVP_DecryptUpdate(&d_ctx, &plain_text[0], &decrypt_size,
+ &cipher_text[0],
+ cipher_text.size())) {
+ LOG(ERROR) << "DecryptUpdate failed";
+ return false;
+ }
+ if (!EVP_DecryptFinal_ex(&d_ctx, &plain_text[decrypt_size], &final_size)) {
+ unsigned long err = ERR_get_error();
+ ERR_load_ERR_strings();
+ ERR_load_crypto_strings();
+
+ LOG(ERROR) << "DecryptFinal Error: " << err
+ << ": " << ERR_lib_error_string(err)
+ << ", " << ERR_func_error_string(err)
+ << ", " << ERR_reason_error_string(err);
+
+ return false;
+ }
+ final_size += decrypt_size;
+
+ plain_text.resize(final_size);
+
+ if (plain_text.size() != VaultKeyset::SerializedSize()) {
+ LOG(ERROR) << "Plain text was not the correct size: " << plain_text.size()
+ << ", expected: " << VaultKeyset::SerializedSize();
+ return false;
+ }
+
+ (*vault_keyset).AssignBuffer(plain_text);
+
+ return true;
+}
+
+bool Mount::CreateMasterKey(const Credentials& credentials, int index) {
+ VaultKeyset vault_keyset;
+ vault_keyset.CreateRandom(*this);
+ return SaveVaultKeyset(credentials, vault_keyset, index);
+}
+
+bool Mount::SaveVaultKeyset(const Credentials& credentials,
+ const VaultKeyset& vault_keyset,
+ int index) {
+ unsigned char wrapper_key[EVP_MAX_KEY_LENGTH];
+ unsigned char iv[EVP_MAX_IV_LENGTH];
+ unsigned char salt[PKCS5_SALT_LEN];
+
+ // Create a salt for this master key
+ GetSecureRandom(salt, sizeof(salt));
+ SecureBlob user_salt = GetUserSalt(credentials, index, true);
+
+ // Convert the passkey to a passkey wrapper
+ SecureBlob passkey_wrapper =
+ PasskeyToWrapper(credentials.GetPasskey(), user_salt, 1);
+
+ // Convert the passkey wrapper to a key encryption key
+ if (!EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), salt, &passkey_wrapper[0],
+ passkey_wrapper.size(), 1, wrapper_key, iv)) {
+ LOG(ERROR) << "Failure converting bytes to key";
+ return false;
+ }
+
+ SecureBlob keyset_blob = vault_keyset.ToBuffer();
+
+ // Store the salt and encrypt the master key
+ unsigned int header_size = kOpenSSLMagic.length() + PKCS5_SALT_LEN;
+ SecureBlob cipher_text(header_size
+ + keyset_blob.size()
+ + EVP_CIPHER_block_size(EVP_aes_256_ecb()));
+ memcpy(&cipher_text[0], kOpenSSLMagic.c_str(), kOpenSSLMagic.length());
+ memcpy(&cipher_text[kOpenSSLMagic.length()], salt, PKCS5_SALT_LEN);
+
+ int current_size = header_size;
+ int encrypt_size = 0;
+ EVP_CIPHER_CTX e_ctx;
+ EVP_CIPHER_CTX_init(&e_ctx);
+
+ // Encrypt the keyset
+ EVP_EncryptInit_ex(&e_ctx, EVP_aes_256_ecb(), NULL, wrapper_key, iv);
+ if (!EVP_EncryptUpdate(&e_ctx, &cipher_text[current_size], &encrypt_size,
+ &keyset_blob[0],
+ keyset_blob.size())) {
+ LOG(ERROR) << "EncryptUpdate failed";
+ return false;
+ }
+ current_size += encrypt_size;
+ encrypt_size = 0;
+
+ // Finish the encryption
+ if (!EVP_EncryptFinal_ex(&e_ctx, &cipher_text[current_size], &encrypt_size)) {
+ LOG(ERROR) << "EncryptFinal failed";
+ return false;
+ }
+ current_size += encrypt_size;
+ cipher_text.resize(current_size);
+
+ chromeos::SecureMemset(wrapper_key, sizeof(wrapper_key), 0);
+
+ // Save the master key
+ unsigned int data_written = file_util::WriteFile(
+ FilePath(GetUserKeyFile(credentials, index)),
+ reinterpret_cast<const char*>(&cipher_text[0]),
+ cipher_text.size());
+
+ if (data_written != cipher_text.size()) {
+ LOG(ERROR) << "Write to master key failed";
+ return false;
+ }
+ return true;
+}
+
+SecureBlob Mount::PasskeyToWrapper(const chromeos::Blob& passkey,
+ const chromeos::Blob& salt, int iters) {
+ // TODO(fes): Update this when TPM support is available, or use a memory-
+ // bound strengthening algorithm.
+ int update_length = passkey.size();
+ SecureBlob holder(CRYPTOHOME_MAX(update_length, SHA_DIGEST_LENGTH));
+ memcpy(&holder[0], &passkey[0], update_length);
+
+ // Repeatedly hash the user passkey and salt to generate the wrapper
+ for (int i = 0; i < iters; ++i) {
+ SHA_CTX ctx;
+ unsigned char md_value[SHA_DIGEST_LENGTH];
+
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, &salt[0], salt.size());
+ SHA1_Update(&ctx, &holder[0], update_length);
+ SHA1_Final(md_value, &ctx);
+
+ memcpy(&holder[0], md_value, SHA_DIGEST_LENGTH);
+ update_length = SHA_DIGEST_LENGTH;
+ }
+
+ holder.resize(update_length);
+ SecureBlob wrapper(update_length * 2);
+ AsciiEncodeToBuffer(holder, reinterpret_cast<char*>(&wrapper[0]),
+ wrapper.size());
+ return wrapper;
+}
+
+SecureBlob Mount::GetSystemSalt() {
+ return system_salt_;
+}
+
+SecureBlob Mount::GetUserSalt(const Credentials& credentials, int index,
+ bool force) {
+ FilePath path(GetUserSaltFile(credentials, index));
+ return GetOrCreateSalt(path, CRYPTOHOME_DEFAULT_SALT_LENGTH, force);
+}
+
+SecureBlob Mount::GetOrCreateSalt(const FilePath& path, int length,
+ bool force) {
+ SecureBlob salt;
+ if (force || !file_util::PathExists(path)) {
+ // If this salt doesn't exist, automatically create it
+ salt.resize(length);
+ GetSecureRandom(&salt[0], salt.size());
+ int data_written = file_util::WriteFile(path,
+ reinterpret_cast<const char*>(&salt[0]),
+ length);
+ if (data_written != length) {
+ LOG(ERROR) << "Could not write user salt";
+ return SecureBlob();
+ }
+ } else {
+ // Otherwise just load the contents of the salt
+ int64 file_size;
+ if (!file_util::GetFileSize(path, &file_size)) {
+ LOG(ERROR) << "Could not get size of " << path.value();
+ return SecureBlob();
+ }
+ if (file_size > INT_MAX) {
+ LOG(ERROR) << "File " << path.value() << " is too large: " << file_size;
+ return SecureBlob();
+ }
+ salt.resize(file_size);
+ int data_read = file_util::ReadFile(path, reinterpret_cast<char*>(&salt[0]),
+ file_size);
+ if (data_read != file_size) {
+ LOG(ERROR) << "Could not read entire file " << file_size;
+ return SecureBlob();
+ }
+ }
+ return salt;
+}
+
+void Mount::AsciiEncodeToBuffer(const chromeos::Blob& blob, char* buffer,
+ int buffer_length) {
+ const char hex_chars[] = "0123456789abcdef";
+ int i = 0;
+ for (chromeos::Blob::const_iterator it = blob.begin();
+ it < blob.end() && (i + 1) < buffer_length; ++it) {
+ buffer[i++] = hex_chars[((*it) >> 4) & 0x0f];
+ buffer[i++] = hex_chars[(*it) & 0x0f];
+ }
+ if (i < buffer_length) {
+ buffer[i] = '\0';
+ }
+}
+
+bool Mount::AddKeyToEcryptfsKeyring(const VaultKeyset& vault_keyset,
+ string* key_signature,
+ string* fnek_signature) {
+ // Clear the user keyring
+ keyctl(KEYCTL_CLEAR, KEY_SPEC_USER_KEYRING);
+
+ // Add the FEK
+ *key_signature = chromeos::AsciiEncode(vault_keyset.FEK_SIG());
+ if (!PushVaultKey(vault_keyset.FEK(), *key_signature,
+ vault_keyset.FEK_SALT())) {
+ LOG(ERROR) << "Couldn't add ecryptfs key to keyring";
+ return false;
+ }
+
+ // Add the FNEK
+ *fnek_signature = chromeos::AsciiEncode(vault_keyset.FNEK_SIG());
+ if (!PushVaultKey(vault_keyset.FNEK(), *fnek_signature,
+ vault_keyset.FNEK_SALT())) {
+ LOG(ERROR) << "Couldn't add ecryptfs fnek key to keyring";
+ return false;
+ }
+
+ return true;
+}
+
+bool Mount::PushVaultKey(const SecureBlob& key, const std::string& key_sig,
+ const SecureBlob& salt) {
+ DCHECK(key.size() == ECRYPTFS_MAX_KEY_BYTES);
+ DCHECK(key_sig.length() == (ECRYPTFS_SIG_SIZE * 2));
+ DCHECK(salt.size() == ECRYPTFS_SALT_SIZE);
+
+ struct ecryptfs_auth_tok auth_token;
+
+ generate_payload(&auth_token, const_cast<char*>(key_sig.c_str()),
+ const_cast<char*>(reinterpret_cast<const char*>(&salt[0])),
+ const_cast<char*>(reinterpret_cast<const char*>(&key[0])));
+
+ if (ecryptfs_add_auth_tok_to_keyring(&auth_token,
+ const_cast<char*>(key_sig.c_str())) < 0) {
+ LOG(ERROR) << "PushVaultKey failed";
+ }
+
+ return true;
+}
+
+bool Mount::LoadFileBytes(const FilePath& path,
+ SecureBlob& blob) {
+ int64 file_size;
+ if (!file_util::PathExists(path)) {
+ LOG(ERROR) << path.value() << " does not exist!";
+ return false;
+ }
+ if (!file_util::GetFileSize(path, &file_size)) {
+ LOG(ERROR) << "Could not get size of " << path.value();
+ return false;
+ }
+ if (file_size > INT_MAX) {
+ LOG(ERROR) << "File " << path.value() << " is too large: " << file_size;
+ return false;
+ }
+ SecureBlob buf(file_size);
+ int data_read = file_util::ReadFile(path, reinterpret_cast<char*>(&buf[0]),
+ file_size);
+ // Cast is okay because of comparison to INT_MAX above
+ if (data_read != static_cast<int>(file_size)) {
+ LOG(ERROR) << "Could not read entire file " << file_size;
+ return false;
+ }
+ blob.swap(buf);
+ return true;
+}
+
+bool Mount::TerminatePidsWithOpenFiles(const std::string& path, bool hard) {
+ std::vector<pid_t> pids = LookForOpenFiles(path);
+ for (std::vector<pid_t>::iterator it = pids.begin(); it != pids.end(); it++) {
+ pid_t pid = static_cast<pid_t>(*it);
+ if (pid != getpid()) {
+ if (hard) {
+ kill(pid, SIGTERM);
+ } else {
+ kill(pid, SIGKILL);
+ }
+ }
+ }
+ return (pids.size() != 0);
+}
+
+std::vector<pid_t> Mount::LookForOpenFiles(const std::string& path_in) {
+ // Make sure that if we get a directory, it has a trailing separator
+ FilePath file_path(path_in);
+ file_util::EnsureEndsWithSeparator(&file_path);
+ std::string path = file_path.value();
+
+ std::vector<pid_t> pids;
+
+ // Open /proc
+ DIR* proc_dir = opendir(kProcDir.c_str());
+
+ if (!proc_dir) {
+ return pids;
+ }
+
+ int linkbuf_length = path.length();
+ std::vector<char> linkbuf(linkbuf_length);
+
+ // List PIDs in /proc
+ while (struct dirent* pid_dirent = readdir(proc_dir)) {
+ pid_t pid = static_cast<pid_t>(atoi(pid_dirent->d_name));
+ // Ignore PID 1 and errors
+ if (pid <= 1) {
+ continue;
+ }
+ // Open /proc/<pid>/fd
+ std::string fd_dirname = StringPrintf("%s/%d/fd", kProcDir.c_str(), pid);
+ DIR* fd_dir = opendir(fd_dirname.c_str());
+ if (!fd_dir) {
+ continue;
+ }
+
+ // List open file descriptors
+ while (struct dirent* fd_dirent = readdir(fd_dir)) {
mschilder 2010/05/27 04:20:18 readdir_r()?
+ std::string fd_path = StringPrintf("%s/%s", fd_dirname.c_str(),
+ fd_dirent->d_name);
+ ssize_t link_length = readlink(fd_path.c_str(), &linkbuf[0],
+ linkbuf.size());
+ if (link_length > 0) {
+ std::string open_file(&linkbuf[0], link_length);
+ if (open_file.length() >= path.length()) {
+ if (open_file.substr(0, path.length()).compare(path) == 0) {
+ pids.push_back(pid);
+ break;
+ }
+ }
+ }
+ }
+
+ closedir(fd_dir);
+ }
+
+ closedir(proc_dir);
+
+ return pids;
+}
+
+bool Mount::TerminatePidsForUser(const uid_t uid, bool hard) {
+ std::vector<pid_t> pids = GetPidsForUser(uid);
+ for (std::vector<pid_t>::iterator it = pids.begin(); it != pids.end(); it++) {
+ pid_t pid = static_cast<pid_t>(*it);
+ if (pid != getpid()) {
+ if (hard) {
+ kill(pid, SIGTERM);
+ } else {
+ kill(pid, SIGKILL);
+ }
+ }
+ }
+ return (pids.size() != 0);
+}
+
+// TODO(fes): Pull this into a separate helper class
+std::vector<pid_t> Mount::GetPidsForUser(uid_t uid) {
+ std::vector<pid_t> pids;
+
+ // Open /proc
+ DIR* proc_dir = opendir(kProcDir.c_str());
+
+ if (!proc_dir) {
+ return pids;
+ }
+
+ // List PIDs in /proc
+ while (struct dirent* pid_dirent = readdir(proc_dir)) {
+ pid_t pid = static_cast<pid_t>(atoi(pid_dirent->d_name));
+ if (pid <= 0) {
+ continue;
+ }
+ // Open /proc/<pid>/status
+ std::string stat_path = StringPrintf("%s/%d/status", kProcDir.c_str(),
+ pid);
+ string contents;
+ if (!file_util::ReadFileToString(FilePath(stat_path), &contents)) {
+ continue;
+ }
+
+ size_t uid_loc = contents.find("Uid:");
+ if (!uid_loc) {
+ continue;
+ }
+ uid_loc += 4;
+
+ size_t uid_end = contents.find("\n", uid_loc);
+ if (!uid_end) {
+ continue;
+ }
+
+ contents = contents.substr(uid_loc, uid_end - uid_loc);
+
+ std::vector<std::string> tokens;
+ Tokenize(contents, " \t", &tokens);
+
+ for (std::vector<std::string>::iterator it = tokens.begin();
+ it != tokens.end(); it++) {
+ std::string& value = *it;
+ if (value.length() == 0) {
+ continue;
+ }
+ uid_t check_uid = static_cast<uid_t>(atoi(value.c_str()));
+ if (check_uid == uid) {
+ pids.push_back(pid);
+ break;
+ }
+ }
+ }
+
+ closedir(proc_dir);
+
+ return pids;
+}
+
+} // cryptohome

Powered by Google App Engine
This is Rietveld 408576698