diff --git a/INSTALL b/INSTALL index 41049f1..e6c9471 100644 --- a/INSTALL +++ b/INSTALL @@ -2,19 +2,19 @@ DEPENDENCIES To build git-crypt, you need: - Debian/Ubuntu package RHEL/CentOS package + Debian/Ubuntu package RHEL/CentOS package ----------------------------------------------------------------------------- - Make make make - A C++ compiler (e.g. gcc) g++ gcc-c++ - OpenSSL development files libssl-dev openssl-devel + Make make make + A C++11 compiler (e.g. gcc 4.9+) g++ gcc-c++ + OpenSSL development files libssl-dev openssl-devel To use git-crypt, you need: - Debian/Ubuntu package RHEL/CentOS package + Debian/Ubuntu package RHEL/CentOS package ----------------------------------------------------------------------------- - Git 1.7.2 or newer git git - OpenSSL openssl openssl + Git 1.7.2 or newer git git + OpenSSL openssl openssl Note: Git 1.8.5 or newer is recommended for best performance. diff --git a/INSTALL.md b/INSTALL.md index 7fdb577..5a04138 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -2,19 +2,19 @@ To build git-crypt, you need: - | Debian/Ubuntu package | RHEL/CentOS package ----------------------------|-----------------------|------------------------ -Make | make | make -A C++ compiler (e.g. gcc) | g++ | gcc-c++ -OpenSSL development files | libssl-dev | openssl-devel +| Software | Debian/Ubuntu package | RHEL/CentOS package| +|---------------------------------|-----------------------|--------------------| +|Make | make | make | +|A C++11 compiler (e.g. gcc 4.9+) | g++ | gcc-c++ | +|OpenSSL development files | libssl-dev | openssl-devel | To use git-crypt, you need: - | Debian/Ubuntu package | RHEL/CentOS package ----------------------------|-----------------------|------------------------ -Git 1.7.2 or newer | git | git -OpenSSL | openssl | openssl +| Software | Debian/Ubuntu package | RHEL/CentOS package| +|---------------------------------|-----------------------|--------------------| +|Git 1.7.2 or newer | git | git | +|OpenSSL | openssl | openssl | Note: Git 1.8.5 or newer is recommended for best performance. diff --git a/Makefile b/Makefile index bcc7516..68eb9db 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ # CXXFLAGS ?= -Wall -pedantic -Wno-long-long -O2 +CXXFLAGS += -std=c++11 PREFIX ?= /usr/local BINDIR ?= $(PREFIX)/bin MANDIR ?= $(PREFIX)/share/man @@ -23,7 +24,7 @@ OBJFILES = \ coprocess.o \ fhstream.o -OBJFILES += crypto-openssl.o +OBJFILES += crypto-openssl-10.o crypto-openssl-11.o LDFLAGS += -lcrypto XSLTPROC ?= xsltproc @@ -54,7 +55,7 @@ coprocess.o: coprocess.cpp coprocess-unix.cpp coprocess-win32.cpp build-man: man/man1/git-crypt.1 man/man1/git-crypt.1: man/git-crypt.xml - $(XSLTPROC) $(DOCBOOK_FLAGS) $(DOCBOOK_XSL) $< + $(XSLTPROC) $(DOCBOOK_FLAGS) $(DOCBOOK_XSL) man/git-crypt.xml # # Clean diff --git a/NEWS b/NEWS index d01a975..41e2bd5 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,16 @@ +v0.6.0 (2017-11-26) + * Add support for OpenSSL 1.1 (still works with OpenSSL 1.0). + * Switch to C++11 (gcc 4.9 or higher now required to build). + * Allow GPG to fail on some keys (makes unlock work better if there are + multiple keys that can unlock the repo but only some are available). + * Allow the repo state directory to be configured with the + git-crypt.repoStateDir git config option. + * Respect the gpg.program git config option. + * Don't hard code path to git-crypt in .git/config on Linux (ensures + repo continues to work if git-crypt is moved). + * Ensure git-crypt's gpg files won't be treated as text by Git. + * Minor improvements to build system, documentation. + v0.5.0 (2015-05-30) * Drastically speed up lock/unlock when used with Git 1.8.5 or newer. * Add git-crypt(1) man page (pass ENABLE_MAN=yes to make to build). diff --git a/NEWS.md b/NEWS.md index dd8772f..080035f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,19 @@ News ==== +######v0.6.0 (2017-11-26) +* Add support for OpenSSL 1.1 (still works with OpenSSL 1.0). +* Switch to C++11 (gcc 4.9 or higher now required to build). +* Allow GPG to fail on some keys (makes unlock work better if there are + multiple keys that can unlock the repo but only some are available). +* Allow the repo state directory to be configured with the + git-crypt.repoStateDir git config option. +* Respect the gpg.program git config option. +* Don't hard code path to git-crypt in .git/config on Linux (ensures + repo continues to work if git-crypt is moved). +* Ensure git-crypt's gpg files won't be treated as text by Git. +* Minor improvements to build system, documentation. + ######v0.5.0 (2015-05-30) * Drastically speed up lock/unlock when used with Git 1.8.5 or newer. * Add git-crypt(1) man page (pass `ENABLE_MAN=yes` to make to build). diff --git a/README b/README index fd982ac..8c4177c 100644 --- a/README +++ b/README @@ -69,7 +69,7 @@ encryption and decryption happen transparently. CURRENT STATUS -The latest version of git-crypt is 0.5.0, released on 2015-05-30. +The latest version of git-crypt is 0.6.0, released on 2017-11-26. git-crypt aims to be bug-free and reliable, meaning it shouldn't crash, malfunction, or expose your confidential data. However, it has not yet reached maturity, meaning it is not as documented, @@ -79,7 +79,7 @@ backwards-incompatible changes introduced before version 1.0. SECURITY -git-crypt is more secure that other transparent git encryption systems. +git-crypt is more secure than other transparent git encryption systems. git-crypt encrypts files using AES-256 in CTR mode with a synthetic IV derived from the SHA-1 HMAC of the file. This mode of operation is provably semantically secure under deterministic chosen-plaintext attack. @@ -98,7 +98,7 @@ all of the files in a repository. Where git-crypt really shines is where most of your repository is public, but you have a few files (perhaps private keys named *.key, or a file with API credentials) which you need to encrypt. For encrypting an entire repository, consider using a -system like git-remote-gcrypt +system like git-remote-gcrypt instead. (Note: no endorsement is made of git-remote-gcrypt's security.) git-crypt does not encrypt file names, commit messages, symlink targets, diff --git a/README.md b/README.md index 1aeccc8..bd0592b 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,8 @@ encryption and decryption happen transparently. Current Status -------------- -The latest version of git-crypt is [0.5.0](NEWS.md), released on -2015-05-30. git-crypt aims to be bug-free and reliable, meaning it +The latest version of git-crypt is [0.6.0](NEWS.md), released on +2017-11-26. git-crypt aims to be bug-free and reliable, meaning it shouldn't crash, malfunction, or expose your confidential data. However, it has not yet reached maturity, meaning it is not as documented, featureful, or easy-to-use as it should be. Additionally, @@ -81,7 +81,7 @@ there may be backwards-incompatible changes introduced before version Security -------- -git-crypt is more secure that other transparent git encryption systems. +git-crypt is more secure than other transparent git encryption systems. git-crypt encrypts files using AES-256 in CTR mode with a synthetic IV derived from the SHA-1 HMAC of the file. This mode of operation is provably semantically secure under deterministic chosen-plaintext attack. @@ -100,7 +100,7 @@ all of the files in a repository. Where git-crypt really shines is where most of your repository is public, but you have a few files (perhaps private keys named *.key, or a file with API credentials) which you need to encrypt. For encrypting an entire repository, consider using a -system like [git-remote-gcrypt](https://github.com/joeyh/git-remote-gcrypt) +system like [git-remote-gcrypt](https://spwhitton.name/tech/code/git-remote-gcrypt/) instead. (Note: no endorsement is made of git-remote-gcrypt's security.) git-crypt does not encrypt file names, commit messages, symlink targets, diff --git a/THANKS.md b/THANKS.md index 4c0d53e..eaeb789 100644 --- a/THANKS.md +++ b/THANKS.md @@ -12,6 +12,10 @@ For their contributions to git-crypt, I thank: * Linus G Thiel * Michael Schout * Simon Kotlinski + * Kevin Menard + * Wael M. Nasreddine + * Kevin Borgolte + * Adrian Cohea * And everyone who has tested git-crypt, provided feedback, reported bugs, and participated in discussions about new features. diff --git a/commands.cpp b/commands.cpp index 2b86930..5ac0b47 100644 --- a/commands.cpp +++ b/commands.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include static std::string attribute_name (const char* key_name) @@ -254,6 +255,27 @@ static std::string get_internal_key_path (const char* key_name) return path; } +std::string get_git_config (const std::string& name) +{ + // git config --get + std::vector command; + command.push_back("git"); + command.push_back("config"); + command.push_back("--get"); + command.push_back(name); + + std::stringstream output; + + if (!successful_exit(exec_command(command, output))) { + throw Error("'git config' missing value for key '" + name +"'"); + } + + std::string value; + std::getline(output, value); + + return value; +} + static std::string get_repo_state_path () { // git rev-parse --show-toplevel @@ -276,7 +298,18 @@ static std::string get_repo_state_path () throw Error("Could not determine Git working tree - is this a non-bare repo?"); } - path += "/.git-crypt"; + // Check if the repo state dir has been explicitly configured. If so, use that in path construction. + if (git_has_config("git-crypt.repoStateDir")) { + std::string repoStateDir = get_git_config("git-crypt.repoStateDir"); + + // The repoStateDir value must always be relative to git work tree to ensure the repoStateDir can be committed + // along with the remainder of the repository. + path += '/' + repoStateDir; + } else { + // There is no explicitly configured repo state dir configured, so use the default. + path += "/.git-crypt"; + } + return path; } @@ -455,7 +488,7 @@ static bool check_if_file_is_encrypted (const std::string& filename) static bool is_git_file_mode (const std::string& mode) { - return (std::strtoul(mode.c_str(), NULL, 8) & 0170000) == 0100000; + return (std::strtoul(mode.c_str(), nullptr, 8) & 0170000) == 0100000; } static void get_encrypted_files (std::vector& files, const char* key_name) @@ -476,8 +509,8 @@ static void get_encrypted_files (std::vector& files, const char* ke ls_files.spawn(ls_files_command); Coprocess check_attr; - std::ostream* check_attr_stdin = NULL; - std::istream* check_attr_stdout = NULL; + std::ostream* check_attr_stdin = nullptr; + std::istream* check_attr_stdout = nullptr; if (git_version() >= make_version(1, 8, 5)) { // In Git 1.8.5 (released 27 Nov 2013) and higher, we use a single `git check-attr` process // to get the attributes of all files at once. In prior versions, we have to fork and exec @@ -557,13 +590,20 @@ static void load_key (Key_file& key_file, const char* key_name, const char* key_ static bool decrypt_repo_key (Key_file& key_file, const char* key_name, uint32_t key_version, const std::vector& secret_keys, const std::string& keys_path) { + std::exception_ptr gpg_error; + for (std::vector::const_iterator seckey(secret_keys.begin()); seckey != secret_keys.end(); ++seckey) { std::ostringstream path_builder; path_builder << keys_path << '/' << (key_name ? key_name : "default") << '/' << key_version << '/' << *seckey << ".gpg"; std::string path(path_builder.str()); if (access(path.c_str(), F_OK) == 0) { std::stringstream decrypted_contents; - gpg_decrypt_from_file(path, decrypted_contents); + try { + gpg_decrypt_from_file(path, decrypted_contents); + } catch (const Gpg_error&) { + gpg_error = std::current_exception(); + continue; + } Key_file this_version_key_file; this_version_key_file.load(decrypted_contents); const Key_file::Entry* this_version_entry = this_version_key_file.get(key_version); @@ -578,6 +618,11 @@ static bool decrypt_repo_key (Key_file& key_file, const char* key_name, uint32_t return true; } } + + if (gpg_error) { + std::rethrow_exception(gpg_error); + } + return false; } @@ -1202,6 +1247,7 @@ int add_gpg_user (int argc, const char** argv) state_gitattributes_file << "# Do not edit this file. To specify the files to encrypt, create your own\n"; state_gitattributes_file << "# .gitattributes file in the directory where your files are.\n"; state_gitattributes_file << "* !filter !diff\n"; + state_gitattributes_file << "*.gpg binary\n"; state_gitattributes_file.close(); if (!state_gitattributes_file) { std::clog << "Error: unable to write " << state_gitattributes_path << std::endl; diff --git a/commands.hpp b/commands.hpp index 32caa0f..f441e93 100644 --- a/commands.hpp +++ b/commands.hpp @@ -70,4 +70,7 @@ void help_migrate_key (std::ostream&); void help_refresh (std::ostream&); void help_status (std::ostream&); +// other +std::string get_git_config (const std::string& name); + #endif diff --git a/coprocess-unix.cpp b/coprocess-unix.cpp index f9577e5..45be332 100644 --- a/coprocess-unix.cpp +++ b/coprocess-unix.cpp @@ -41,7 +41,7 @@ static int execvp (const std::string& file, const std::vector& args for (std::vector::const_iterator arg(args.begin()); arg != args.end(); ++arg) { args_c_str.push_back(arg->c_str()); } - args_c_str.push_back(NULL); + args_c_str.push_back(nullptr); return execvp(file.c_str(), const_cast(&args_c_str[0])); } @@ -50,10 +50,10 @@ Coprocess::Coprocess () pid = -1; stdin_pipe_reader = -1; stdin_pipe_writer = -1; - stdin_pipe_ostream = NULL; + stdin_pipe_ostream = nullptr; stdout_pipe_reader = -1; stdout_pipe_writer = -1; - stdout_pipe_istream = NULL; + stdout_pipe_istream = nullptr; } Coprocess::~Coprocess () @@ -79,7 +79,7 @@ std::ostream* Coprocess::stdin_pipe () void Coprocess::close_stdin () { delete stdin_pipe_ostream; - stdin_pipe_ostream = NULL; + stdin_pipe_ostream = nullptr; if (stdin_pipe_writer != -1) { close(stdin_pipe_writer); stdin_pipe_writer = -1; @@ -107,7 +107,7 @@ std::istream* Coprocess::stdout_pipe () void Coprocess::close_stdout () { delete stdout_pipe_istream; - stdout_pipe_istream = NULL; + stdout_pipe_istream = nullptr; if (stdout_pipe_writer != -1) { close(stdout_pipe_writer); stdout_pipe_writer = -1; diff --git a/coprocess-win32.cpp b/coprocess-win32.cpp index 46e21d0..556c873 100644 --- a/coprocess-win32.cpp +++ b/coprocess-win32.cpp @@ -96,14 +96,14 @@ static HANDLE spawn_command (const std::vector& command, HANDLE std std::string cmdline(format_cmdline(command)); - if (!CreateProcessA(NULL, // application name (NULL to use command line) + if (!CreateProcessA(nullptr, // application name (nullptr to use command line) const_cast(cmdline.c_str()), - NULL, // process security attributes - NULL, // primary thread security attributes + nullptr, // process security attributes + nullptr, // primary thread security attributes TRUE, // handles are inherited 0, // creation flags - NULL, // use parent's environment - NULL, // use parent's current directory + nullptr, // use parent's environment + nullptr, // use parent's current directory &start_info, &proc_info)) { throw System_error("CreateProcess", cmdline, GetLastError()); @@ -117,13 +117,13 @@ static HANDLE spawn_command (const std::vector& command, HANDLE std Coprocess::Coprocess () { - proc_handle = NULL; - stdin_pipe_reader = NULL; - stdin_pipe_writer = NULL; - stdin_pipe_ostream = NULL; - stdout_pipe_reader = NULL; - stdout_pipe_writer = NULL; - stdout_pipe_istream = NULL; + proc_handle = nullptr; + stdin_pipe_reader = nullptr; + stdin_pipe_writer = nullptr; + stdin_pipe_ostream = nullptr; + stdout_pipe_reader = nullptr; + stdout_pipe_writer = nullptr; + stdout_pipe_istream = nullptr; } Coprocess::~Coprocess () @@ -143,7 +143,7 @@ std::ostream* Coprocess::stdin_pipe () // Set the bInheritHandle flag so pipe handles are inherited. sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); sec_attr.bInheritHandle = TRUE; - sec_attr.lpSecurityDescriptor = NULL; + sec_attr.lpSecurityDescriptor = nullptr; // Create a pipe for the child process's STDIN. if (!CreatePipe(&stdin_pipe_reader, &stdin_pipe_writer, &sec_attr, 0)) { @@ -163,14 +163,14 @@ std::ostream* Coprocess::stdin_pipe () void Coprocess::close_stdin () { delete stdin_pipe_ostream; - stdin_pipe_ostream = NULL; + stdin_pipe_ostream = nullptr; if (stdin_pipe_writer) { CloseHandle(stdin_pipe_writer); - stdin_pipe_writer = NULL; + stdin_pipe_writer = nullptr; } if (stdin_pipe_reader) { CloseHandle(stdin_pipe_reader); - stdin_pipe_reader = NULL; + stdin_pipe_reader = nullptr; } } @@ -182,7 +182,7 @@ std::istream* Coprocess::stdout_pipe () // Set the bInheritHandle flag so pipe handles are inherited. sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); sec_attr.bInheritHandle = TRUE; - sec_attr.lpSecurityDescriptor = NULL; + sec_attr.lpSecurityDescriptor = nullptr; // Create a pipe for the child process's STDOUT. if (!CreatePipe(&stdout_pipe_reader, &stdout_pipe_writer, &sec_attr, 0)) { @@ -202,27 +202,27 @@ std::istream* Coprocess::stdout_pipe () void Coprocess::close_stdout () { delete stdout_pipe_istream; - stdout_pipe_istream = NULL; + stdout_pipe_istream = nullptr; if (stdout_pipe_writer) { CloseHandle(stdout_pipe_writer); - stdout_pipe_writer = NULL; + stdout_pipe_writer = nullptr; } if (stdout_pipe_reader) { CloseHandle(stdout_pipe_reader); - stdout_pipe_reader = NULL; + stdout_pipe_reader = nullptr; } } void Coprocess::spawn (const std::vector& args) { - proc_handle = spawn_command(args, stdin_pipe_reader, stdout_pipe_writer, NULL); + proc_handle = spawn_command(args, stdin_pipe_reader, stdout_pipe_writer, nullptr); if (stdin_pipe_reader) { CloseHandle(stdin_pipe_reader); - stdin_pipe_reader = NULL; + stdin_pipe_reader = nullptr; } if (stdout_pipe_writer) { CloseHandle(stdout_pipe_writer); - stdout_pipe_writer = NULL; + stdout_pipe_writer = nullptr; } } @@ -243,7 +243,7 @@ int Coprocess::wait () size_t Coprocess::write_stdin (void* handle, const void* buf, size_t count) { DWORD bytes_written; - if (!WriteFile(static_cast(handle)->stdin_pipe_writer, buf, count, &bytes_written, NULL)) { + if (!WriteFile(static_cast(handle)->stdin_pipe_writer, buf, count, &bytes_written, nullptr)) { throw System_error("WriteFile", "", GetLastError()); } return bytes_written; @@ -257,7 +257,7 @@ size_t Coprocess::read_stdout (void* handle, void* buf, size_t count) // fails with ERROR_BROKEN_PIPE. DWORD bytes_read; do { - if (!ReadFile(static_cast(handle)->stdout_pipe_reader, buf, count, &bytes_read, NULL)) { + if (!ReadFile(static_cast(handle)->stdout_pipe_reader, buf, count, &bytes_read, nullptr)) { const DWORD read_error = GetLastError(); if (read_error != ERROR_BROKEN_PIPE) { throw System_error("ReadFile", "", read_error); diff --git a/crypto-openssl.cpp b/crypto-openssl-10.cpp similarity index 92% rename from crypto-openssl.cpp rename to crypto-openssl-10.cpp index 6483e86..f0f2c53 100644 --- a/crypto-openssl.cpp +++ b/crypto-openssl-10.cpp @@ -28,6 +28,10 @@ * as that of the covered work. */ +#include + +#if !defined(OPENSSL_API_COMPAT) + #include "crypto.hpp" #include "key.hpp" #include "util.hpp" @@ -59,8 +63,8 @@ Aes_ecb_encryptor::Aes_ecb_encryptor (const unsigned char* raw_key) Aes_ecb_encryptor::~Aes_ecb_encryptor () { - // Note: Explicit destructor necessary because class contains an auto_ptr - // which contains an incomplete type when the auto_ptr is declared. + // Note: Explicit destructor necessary because class contains an unique_ptr + // which contains an incomplete type when the unique_ptr is declared. explicit_memset(&impl->key, '\0', sizeof(impl->key)); } @@ -82,8 +86,8 @@ Hmac_sha1_state::Hmac_sha1_state (const unsigned char* key, size_t key_len) Hmac_sha1_state::~Hmac_sha1_state () { - // Note: Explicit destructor necessary because class contains an auto_ptr - // which contains an incomplete type when the auto_ptr is declared. + // Note: Explicit destructor necessary because class contains an unique_ptr + // which contains an incomplete type when the unique_ptr is declared. HMAC_cleanup(&(impl->ctx)); } @@ -113,3 +117,4 @@ void random_bytes (unsigned char* buffer, size_t len) } } +#endif diff --git a/crypto-openssl-11.cpp b/crypto-openssl-11.cpp new file mode 100644 index 0000000..adf03bb --- /dev/null +++ b/crypto-openssl-11.cpp @@ -0,0 +1,119 @@ +/* + * Copyright 2012, 2014 Andrew Ayer + * + * This file is part of git-crypt. + * + * git-crypt is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * git-crypt is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with git-crypt. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify the Program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, the licensors of the Program + * grant you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include + +#if defined(OPENSSL_API_COMPAT) + +#include "crypto.hpp" +#include "key.hpp" +#include "util.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +void init_crypto () +{ + ERR_load_crypto_strings(); +} + +struct Aes_ecb_encryptor::Aes_impl { + AES_KEY key; +}; + +Aes_ecb_encryptor::Aes_ecb_encryptor (const unsigned char* raw_key) +: impl(new Aes_impl) +{ + if (AES_set_encrypt_key(raw_key, KEY_LEN * 8, &(impl->key)) != 0) { + throw Crypto_error("Aes_ctr_encryptor::Aes_ctr_encryptor", "AES_set_encrypt_key failed"); + } +} + +Aes_ecb_encryptor::~Aes_ecb_encryptor () +{ + // Note: Explicit destructor necessary because class contains an unique_ptr + // which contains an incomplete type when the unique_ptr is declared. + + explicit_memset(&impl->key, '\0', sizeof(impl->key)); +} + +void Aes_ecb_encryptor::encrypt(const unsigned char* plain, unsigned char* cipher) +{ + AES_encrypt(plain, cipher, &(impl->key)); +} + +struct Hmac_sha1_state::Hmac_impl { + HMAC_CTX *ctx; +}; + +Hmac_sha1_state::Hmac_sha1_state (const unsigned char* key, size_t key_len) +: impl(new Hmac_impl) +{ + + impl->ctx = HMAC_CTX_new(); + HMAC_Init_ex(impl->ctx, key, key_len, EVP_sha1(), nullptr); +} + +Hmac_sha1_state::~Hmac_sha1_state () +{ + HMAC_CTX_free(impl->ctx); +} + +void Hmac_sha1_state::add (const unsigned char* buffer, size_t buffer_len) +{ + HMAC_Update(impl->ctx, buffer, buffer_len); +} + +void Hmac_sha1_state::get (unsigned char* digest) +{ + unsigned int len; + HMAC_Final(impl->ctx, digest, &len); +} + + +void random_bytes (unsigned char* buffer, size_t len) +{ + if (RAND_bytes(buffer, len) != 1) { + std::ostringstream message; + while (unsigned long code = ERR_get_error()) { + char error_string[120]; + ERR_error_string_n(code, error_string, sizeof(error_string)); + message << "OpenSSL Error: " << error_string << "; "; + } + throw Crypto_error("random_bytes", message.str()); + } +} + +#endif diff --git a/crypto.hpp b/crypto.hpp index db03241..a7d7edb 100644 --- a/crypto.hpp +++ b/crypto.hpp @@ -57,7 +57,7 @@ public: private: struct Aes_impl; - std::auto_ptr impl; + std::unique_ptr impl; public: Aes_ecb_encryptor (const unsigned char* key); @@ -102,7 +102,7 @@ public: private: struct Hmac_impl; - std::auto_ptr impl; + std::unique_ptr impl; public: Hmac_sha1_state (const unsigned char* key, size_t key_len); diff --git a/git-crypt.hpp b/git-crypt.hpp index bb4beaa..ce41dfa 100644 --- a/git-crypt.hpp +++ b/git-crypt.hpp @@ -31,7 +31,7 @@ #ifndef GIT_CRYPT_GIT_CRYPT_HPP #define GIT_CRYPT_GIT_CRYPT_HPP -#define VERSION "0.5.0" +#define VERSION "0.6.0" extern const char* argv0; // initialized in main() to argv[0] diff --git a/gpg.cpp b/gpg.cpp index 04f3f60..bec5892 100644 --- a/gpg.cpp +++ b/gpg.cpp @@ -30,8 +30,18 @@ #include "gpg.hpp" #include "util.hpp" +#include "commands.hpp" #include +static std::string gpg_get_executable() +{ + std::string gpgbin = "gpg"; + try { + gpgbin = get_git_config("gpg.program"); + } catch (...) { + } + return gpgbin; +} static std::string gpg_nth_column (const std::string& line, unsigned int col) { std::string::size_type pos = 0; @@ -62,7 +72,7 @@ std::string gpg_get_uid (const std::string& fingerprint) { // gpg --batch --with-colons --fixed-list-mode --list-keys 0x7A399B2DB06D039020CD1CE1D0F3702D61489532 std::vector command; - command.push_back("gpg"); + command.push_back(gpg_get_executable()); command.push_back("--batch"); command.push_back("--with-colons"); command.push_back("--fixed-list-mode"); @@ -94,7 +104,7 @@ std::vector gpg_lookup_key (const std::string& query) // gpg --batch --with-colons --fingerprint --list-keys jsmith@example.com std::vector command; - command.push_back("gpg"); + command.push_back(gpg_get_executable()); command.push_back("--batch"); command.push_back("--with-colons"); command.push_back("--fingerprint"); @@ -125,7 +135,7 @@ std::vector gpg_list_secret_keys () { // gpg --batch --with-colons --list-secret-keys --fingerprint std::vector command; - command.push_back("gpg"); + command.push_back(gpg_get_executable()); command.push_back("--batch"); command.push_back("--with-colons"); command.push_back("--list-secret-keys"); @@ -154,7 +164,7 @@ void gpg_encrypt_to_file (const std::string& filename, const std::string& recipi { // gpg --batch -o FILENAME -r RECIPIENT -e std::vector command; - command.push_back("gpg"); + command.push_back(gpg_get_executable()); command.push_back("--batch"); if (key_is_trusted) { command.push_back("--trust-model"); @@ -174,7 +184,7 @@ void gpg_decrypt_from_file (const std::string& filename, std::ostream& output) { // gpg -q -d FILENAME std::vector command; - command.push_back("gpg"); + command.push_back(gpg_get_executable()); command.push_back("-q"); command.push_back("-d"); command.push_back(filename); diff --git a/man/git-crypt.xml b/man/git-crypt.xml index 7a20569..96f53d7 100644 --- a/man/git-crypt.xml +++ b/man/git-crypt.xml @@ -7,8 +7,8 @@ --> git-crypt - 2015-05-30 - git-crypt 0.5.0 + 2017-11-26 + git-crypt 0.6.0 Andrew Ayer diff --git a/util-unix.cpp b/util-unix.cpp index 7c7c05b..3f23d44 100644 --- a/util-unix.cpp +++ b/util-unix.cpp @@ -125,40 +125,20 @@ void mkdir_parent (const std::string& path) } } -static std::string readlink (const char* pathname) -{ - std::vector buffer(64); - ssize_t len; - - while ((len = ::readlink(pathname, &buffer[0], buffer.size())) == static_cast(buffer.size())) { - // buffer may have been truncated - grow and try again - buffer.resize(buffer.size() * 2); - } - if (len == -1) { - throw System_error("readlink", pathname, errno); - } - - return std::string(buffer.begin(), buffer.begin() + len); -} - std::string our_exe_path () { - try { - return readlink("/proc/self/exe"); - } catch (const System_error&) { - if (argv0[0] == '/') { - // argv[0] starts with / => it's an absolute path - return argv0; - } else if (std::strchr(argv0, '/')) { - // argv[0] contains / => it a relative path that should be resolved - char* resolved_path_p = realpath(argv0, NULL); - std::string resolved_path(resolved_path_p); - free(resolved_path_p); - return resolved_path; - } else { - // argv[0] is just a bare filename => not much we can do - return argv0; - } + if (argv0[0] == '/') { + // argv[0] starts with / => it's an absolute path + return argv0; + } else if (std::strchr(argv0, '/')) { + // argv[0] contains / => it a relative path that should be resolved + char* resolved_path_p = realpath(argv0, nullptr); + std::string resolved_path(resolved_path_p); + free(resolved_path_p); + return resolved_path; + } else { + // argv[0] is just a bare filename => not much we can do + return argv0; } } @@ -169,7 +149,7 @@ int exit_status (int wait_status) void touch_file (const std::string& filename) { - if (utimes(filename.c_str(), NULL) == -1 && errno != ENOENT) { + if (utimes(filename.c_str(), nullptr) == -1 && errno != ENOENT) { throw System_error("utimes", filename, errno); } } @@ -199,19 +179,6 @@ int util_rename (const char* from, const char* to) return rename(from, to); } -static size_t sizeof_dirent_for (DIR* p) -{ - long name_max = fpathconf(dirfd(p), _PC_NAME_MAX); - if (name_max == -1) { - #ifdef NAME_MAX - name_max = NAME_MAX; - #else - name_max = 255; - #endif - } - return offsetof(struct dirent, d_name) + name_max + 1; // final +1 is for d_name's null terminator -} - std::vector get_directory_contents (const char* path) { std::vector contents; @@ -221,19 +188,23 @@ std::vector get_directory_contents (const char* path) throw System_error("opendir", path, errno); } try { - std::vector buffer(sizeof_dirent_for(dir)); - struct dirent* dirent_buffer = reinterpret_cast(&buffer[0]); - struct dirent* ent = NULL; - int err = 0; - while ((err = readdir_r(dir, dirent_buffer, &ent)) == 0 && ent != NULL) { - if (std::strcmp(ent->d_name, ".") == 0 || std::strcmp(ent->d_name, "..") == 0) { - continue; + errno = 0; + // Note: readdir is reentrant in new implementations. In old implementations, + // it might not be, but git-crypt isn't multi-threaded so that's OK. + // We don't use readdir_r because it's buggy and deprecated: + // https://womble.decadent.org.uk/readdir_r-advisory.html + // http://austingroupbugs.net/view.php?id=696 + // http://man7.org/linux/man-pages/man3/readdir_r.3.html + while (struct dirent* ent = readdir(dir)) { + if (!(std::strcmp(ent->d_name, ".") == 0 || std::strcmp(ent->d_name, "..") == 0)) { + contents.push_back(ent->d_name); } - contents.push_back(ent->d_name); } - if (err != 0) { - throw System_error("readdir_r", path, errno); + + if (errno) { + throw System_error("readdir", path, errno); } + } catch (...) { closedir(dir); throw; diff --git a/util-win32.cpp b/util-win32.cpp index 445d185..4c47a2d 100644 --- a/util-win32.cpp +++ b/util-win32.cpp @@ -46,12 +46,12 @@ std::string System_error::message () const LPTSTR error_message; FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, + nullptr, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&error_message), 0, - NULL); + nullptr); mesg += error_message; LocalFree(error_message); } @@ -100,7 +100,7 @@ void mkdir_parent (const std::string& path) std::string prefix(path.substr(0, slash)); if (GetFileAttributes(prefix.c_str()) == INVALID_FILE_ATTRIBUTES) { // prefix does not exist, so try to create it - if (!CreateDirectory(prefix.c_str(), NULL)) { + if (!CreateDirectory(prefix.c_str(), nullptr)) { throw System_error("CreateDirectory", prefix, GetLastError()); } } @@ -114,7 +114,7 @@ std::string our_exe_path () std::vector buffer(128); size_t len; - while ((len = GetModuleFileNameA(NULL, &buffer[0], buffer.size())) == buffer.size()) { + while ((len = GetModuleFileNameA(nullptr, &buffer[0], buffer.size())) == buffer.size()) { // buffer may have been truncated - grow and try again buffer.resize(buffer.size() * 2); } @@ -132,7 +132,7 @@ int exit_status (int status) void touch_file (const std::string& filename) { - HANDLE fh = CreateFileA(filename.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + HANDLE fh = CreateFileA(filename.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); if (fh == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND) { @@ -146,7 +146,7 @@ void touch_file (const std::string& filename) FILETIME file_time; SystemTimeToFileTime(&system_time, &file_time); - if (!SetFileTime(fh, NULL, NULL, &file_time)) { + if (!SetFileTime(fh, nullptr, nullptr, &file_time)) { DWORD error = GetLastError(); CloseHandle(fh); throw System_error("SetFileTime", filename, error);