diff --git a/commands.cpp b/commands.cpp index 2ed5254..0e5f7df 100644 --- a/commands.cpp +++ b/commands.cpp @@ -532,7 +532,26 @@ static int decrypt_file_to_stdout (const Key_file& key_file, const unsigned char return 1; } - Aes_ctr_decryptor::process_stream(in, std::cout, key->aes_key, nonce); + Aes_ctr_decryptor aes(key->aes_key, nonce); + Hmac_sha1_state hmac(key->hmac_key, HMAC_KEY_LEN); + while (in) { + unsigned char buffer[1024]; + in.read(reinterpret_cast(buffer), sizeof(buffer)); + aes.process(buffer, buffer, in.gcount()); + hmac.add(buffer, in.gcount()); + std::cout.write(reinterpret_cast(buffer), in.gcount()); + } + + unsigned char digest[Hmac_sha1_state::LEN]; + hmac.get(digest); + if (!leakless_equals(digest, nonce, Aes_ctr_decryptor::NONCE_LEN)) { + std::clog << "git-crypt: error: encrypted file has been tampered with!" << std::endl; + // Although we've already written the tampered file to stdout, exiting + // with a non-zero status will tell git the file has not been filtered, + // so git will not replace it. + return 1; + } + return 0; } diff --git a/util.cpp b/util.cpp index 189e52a..2da0622 100644 --- a/util.cpp +++ b/util.cpp @@ -92,6 +92,23 @@ void* explicit_memset (void* s, int c, std::size_t n) return s; } +static bool leakless_equals_char (const unsigned char* a, const unsigned char* b, std::size_t len) +{ + volatile int diff = 0; + + while (len > 0) { + diff |= *a++ ^ *b++; + --len; + } + + return diff == 0; +} + +bool leakless_equals (const void* a, const void* b, std::size_t len) +{ + return leakless_equals_char(reinterpret_cast(a), reinterpret_cast(b), len); +} + static void init_std_streams_platform (); // platform-specific initialization void init_std_streams () diff --git a/util.hpp b/util.hpp index e79d805..8281294 100644 --- a/util.hpp +++ b/util.hpp @@ -71,6 +71,7 @@ void store_be32 (unsigned char*, uint32_t); bool read_be32 (std::istream& in, uint32_t&); void write_be32 (std::ostream& out, uint32_t); void* explicit_memset (void* s, int c, size_t n); // memset that won't be optimized away +bool leakless_equals (const void* a, const void* b, size_t len); // compare bytes w/o leaking timing void init_std_streams (); mode_t util_umask (mode_t); int util_rename (const char*, const char*);