From 2ba7f0e3749e3a37c262a8156f13cd42dde8dbc1 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Wed, 2 Jul 2014 22:12:13 -0700 Subject: [PATCH] unlock: decrypt all possible keys when using GPG It's no longer necessary to specify the -k option to unlock when using GPG. unlock will automatically decrypt all keys which the user can access. --- commands.cpp | 127 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 49 deletions(-) diff --git a/commands.cpp b/commands.cpp index d534276..87d222d 100644 --- a/commands.cpp +++ b/commands.cpp @@ -81,6 +81,11 @@ static void configure_git_filters (const char* key_name) } } +static bool same_key_name (const char* a, const char* b) +{ + return (!a && !b) || (a && b && std::strcmp(a, b) == 0); +} + static void validate_key_name_or_throw (const char* key_name) { std::string reason; @@ -321,6 +326,10 @@ static bool decrypt_repo_key (Key_file& key_file, const char* key_name, uint32_t if (!this_version_entry) { throw Error("GPG-encrypted keyfile is malformed because it does not contain expected key version"); } + if (!same_key_name(key_name, this_version_key_file.get_key_name())) { + throw Error("GPG-encrypted keyfile is malformed because it does not contain expected key name"); + } + key_file.set_key_name(key_name); key_file.add(*this_version_entry); return true; } @@ -328,6 +337,33 @@ static bool decrypt_repo_key (Key_file& key_file, const char* key_name, uint32_t return false; } +static bool decrypt_repo_keys (std::vector& key_files, uint32_t key_version, const std::vector& secret_keys, const std::string& keys_path) +{ + bool successful = false; + std::vector dirents; + + if (access(keys_path.c_str(), F_OK) == 0) { + dirents = get_directory_contents(keys_path.c_str()); + } + + for (std::vector::const_iterator dirent(dirents.begin()); dirent != dirents.end(); ++dirent) { + const char* key_name = 0; + if (*dirent != "default") { + if (!validate_key_name(dirent->c_str())) { + continue; + } + key_name = dirent->c_str(); + } + + Key_file key_file; + if (decrypt_repo_key(key_file, key_name, key_version, secret_keys, keys_path)) { + key_files.push_back(key_file); + successful = true; + } + } + return successful; +} + static void encrypt_repo_key (const char* key_name, const Key_file::Entry& key, const std::vector& collab_keys, const std::string& keys_path, std::vector* new_files) { std::string key_file_data; @@ -627,21 +663,6 @@ int init (int argc, char** argv) int unlock (int argc, char** argv) { - const char* symmetric_key_file = 0; - const char* key_name = 0; - Options_list options; - options.push_back(Option_def("-k", &key_name)); - options.push_back(Option_def("--key-name", &key_name)); - - int argi = parse_options(options, argc, argv); - if (argc - argi == 0) { - } else if (argc - argi == 1) { - symmetric_key_file = argv[argi]; - } else { - std::clog << "Usage: git-crypt unlock [-k KEYNAME] [KEYFILE]" << std::endl; - return 2; - } - // 0. Make sure working directory is clean (ignoring untracked files) // We do this because we run 'git checkout -f HEAD' later and we don't // want the user to lose any changes. 'git checkout -f HEAD' doesn't touch @@ -669,35 +690,37 @@ int unlock (int argc, char** argv) // mucked with the git config.) std::string path_to_top(get_path_to_top()); - // 3. Install the key - Key_file key_file; - if (symmetric_key_file) { - // Read from the symmetric key file + // 3. Load the key(s) + std::vector key_files; + if (argc > 0) { + // Read from the symmetric key file(s) // TODO: command line flag to accept legacy key format? - if (key_name) { - std::clog << "Error: key name should not be specified when unlocking with symmetric key." << std::endl; - return 1; - } + for (int argi = 0; argi < argc; ++argi) { + const char* symmetric_key_file = argv[argi]; + Key_file key_file; - try { - if (std::strcmp(symmetric_key_file, "-") == 0) { - key_file.load(std::cin); - } else { - if (!key_file.load_from_file(symmetric_key_file)) { - std::clog << "Error: " << symmetric_key_file << ": unable to read key file" << std::endl; - return 1; + try { + if (std::strcmp(symmetric_key_file, "-") == 0) { + key_file.load(std::cin); + } else { + if (!key_file.load_from_file(symmetric_key_file)) { + std::clog << "Error: " << symmetric_key_file << ": unable to read key file" << std::endl; + return 1; + } } + } catch (Key_file::Incompatible) { + std::clog << "Error: " << symmetric_key_file << " is in an incompatible format" << std::endl; + std::clog << "Please upgrade to a newer version of git-crypt." << std::endl; + return 1; + } catch (Key_file::Malformed) { + std::clog << "Error: " << symmetric_key_file << ": not a valid git-crypt key file" << std::endl; + std::clog << "If this key was created prior to git-crypt 0.4, you need to migrate it" << std::endl; + std::clog << "by running 'git-crypt migrate-key /path/to/key/file'." << std::endl; + return 1; } - } catch (Key_file::Incompatible) { - std::clog << "Error: " << symmetric_key_file << " is in an incompatible format" << std::endl; - std::clog << "Please upgrade to a newer version of git-crypt." << std::endl; - return 1; - } catch (Key_file::Malformed) { - std::clog << "Error: " << symmetric_key_file << ": not a valid git-crypt key file" << std::endl; - std::clog << "If this key was created prior to git-crypt 0.4, you need to migrate it" << std::endl; - std::clog << "by running 'git-crypt migrate-key /path/to/key/file'." << std::endl; - return 1; + + key_files.push_back(key_file); } } else { // Decrypt GPG key from root of repo @@ -705,23 +728,29 @@ int unlock (int argc, char** argv) std::vector gpg_secret_keys(gpg_list_secret_keys()); // TODO: command-line option to specify the precise secret key to use // TODO: don't hard code key version 0 here - instead, determine the most recent version and try to decrypt that, or decrypt all versions if command-line option specified - if (!decrypt_repo_key(key_file, key_name, 0, gpg_secret_keys, repo_keys_path)) { + // TODO: command line option to only unlock specific key instead of all of them + // TODO: avoid decrypting repo keys which are already unlocked in the .git directory + if (!decrypt_repo_keys(key_files, 0, gpg_secret_keys, repo_keys_path)) { std::clog << "Error: no GPG secret key available to unlock this repository." << std::endl; std::clog << "To unlock with a shared symmetric key instead, specify the path to the symmetric key as an argument to 'git-crypt unlock'." << std::endl; std::clog << "To see a list of GPG keys authorized to unlock this repository, run 'git-crypt ls-collabs'." << std::endl; return 1; } } - std::string internal_key_path(get_internal_key_path(key_file.get_key_name())); - // TODO: croak if internal_key_path already exists??? - mkdir_parent(internal_key_path); - if (!key_file.store_to_file(internal_key_path.c_str())) { - std::clog << "Error: " << internal_key_path << ": unable to write key file" << std::endl; - return 1; - } - // 4. Configure git for git-crypt - configure_git_filters(key_file.get_key_name()); + + // 4. Install the key(s) and configure the git filters + for (std::vector::iterator key_file(key_files.begin()); key_file != key_files.end(); ++key_file) { + std::string internal_key_path(get_internal_key_path(key_file->get_key_name())); + // TODO: croak if internal_key_path already exists??? + mkdir_parent(internal_key_path); + if (!key_file->store_to_file(internal_key_path.c_str())) { + std::clog << "Error: " << internal_key_path << ": unable to write key file" << std::endl; + return 1; + } + + configure_git_filters(key_file->get_key_name()); + } // 5. Do a force checkout so any files that were previously checked out encrypted // will now be checked out decrypted.