Check HMAC in smudge and diff commands

Git-crypt's position has always been that authentication is best left
to Git, since 1) Git provides immutable history based on SHA-1 hashes
as well as GPG-signed commits and tags, and 2) git-crypt can't be used
safely anyways unless the overall integrity of your repository is assured.

But, since git-crypt already has easy access to a (truncated) HMAC of the
file when decrypting, there's really no reason why git-crypt shouldn't
just verify it and provide an additional layer of protection.
This commit is contained in:
Andrew Ayer
2014-07-23 19:58:13 -07:00
parent 9e791d97ee
commit 01f152b746
3 changed files with 38 additions and 1 deletions

View File

@@ -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<char*>(buffer), sizeof(buffer));
aes.process(buffer, buffer, in.gcount());
hmac.add(buffer, in.gcount());
std::cout.write(reinterpret_cast<char*>(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;
}

View File

@@ -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<const unsigned char*>(a), reinterpret_cast<const unsigned char*>(b), len);
}
static void init_std_streams_platform (); // platform-specific initialization
void init_std_streams ()

View File

@@ -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*);