Chromium Code Reviews| 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 |