Skip to content

Commit 8ba75c4

Browse files
committed
Don't encrypt empty files in new repositories
git has several problems with using smudge/clean filters on empty files (see issue #53). The easiest fix is to just not encrypt empty files. Since it was already obvious from the encrypted file length that a file was empty, skipping empty files does not decrease security. Since skipping empty files is a breaking change to the git-crypt file format, we only do this on new repositories. Specifically, we add a new critical header field to the key file called skip_empty which is set in new keys. We skip empty files if and only if this field is present. Closes: #53 Closes: #162
1 parent 7c129cd commit 8ba75c4

3 files changed

Lines changed: 26 additions & 1 deletion

File tree

commands.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,10 @@ int clean (int argc, const char** argv)
770770
return 1;
771771
}
772772

773+
if (file_size == 0 && key_file.get_skip_empty()) {
774+
return 0;
775+
}
776+
773777
// We use an HMAC of the file as the encryption nonce (IV) for CTR mode.
774778
// By using a hash of the file we ensure that the encryption is
775779
// deterministic so git doesn't think the file has changed when it really
@@ -887,6 +891,11 @@ int smudge (int argc, const char** argv)
887891
// Read the header to get the nonce and make sure it's actually encrypted
888892
unsigned char header[10 + Aes_ctr_decryptor::NONCE_LEN];
889893
std::cin.read(reinterpret_cast<char*>(header), sizeof(header));
894+
895+
if (std::cin.gcount() == 0 && key_file.get_skip_empty()) {
896+
return 0;
897+
}
898+
890899
if (std::cin.gcount() != sizeof(header) || std::memcmp(header, "\0GITCRYPT\0", 10) != 0) {
891900
// File not encrypted - just copy it out to stdout
892901
std::clog << "git-crypt: Warning: file not encrypted" << std::endl;
@@ -991,6 +1000,7 @@ int init (int argc, const char** argv)
9911000
std::clog << "Generating key..." << std::endl;
9921001
Key_file key_file;
9931002
key_file.set_key_name(key_name);
1003+
key_file.set_skip_empty(true);
9941004
key_file.generate();
9951005

9961006
mkdir_parent(internal_key_path);
@@ -1425,6 +1435,7 @@ int keygen (int argc, const char** argv)
14251435

14261436
std::clog << "Generating key..." << std::endl;
14271437
Key_file key_file;
1438+
key_file.set_skip_empty(true);
14281439
key_file.generate();
14291440

14301441
if (std::strcmp(key_file_name, "-") == 0) {

key.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,11 @@ void Key_file::load_header (std::istream& in)
232232
key_name.clear();
233233
throw Malformed();
234234
}
235+
} else if (field_id == HEADER_FIELD_SKIP_EMPTY) {
236+
if (field_len != 0) {
237+
throw Malformed();
238+
}
239+
skip_empty = true;
235240
} else if (field_id & 1) { // unknown critical field
236241
throw Incompatible();
237242
} else {
@@ -256,6 +261,10 @@ void Key_file::store (std::ostream& out) const
256261
write_be32(out, key_name.size());
257262
out.write(key_name.data(), key_name.size());
258263
}
264+
if (skip_empty) {
265+
write_be32(out, HEADER_FIELD_SKIP_EMPTY);
266+
write_be32(out, 0);
267+
}
259268
write_be32(out, HEADER_FIELD_END);
260269
for (Map::const_iterator it(entries.begin()); it != entries.end(); ++it) {
261270
it->second.store(out);

key.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,23 @@ struct Key_file {
8383

8484
void set_key_name (const char* k) { key_name = k ? k : ""; }
8585
const char* get_key_name () const { return key_name.empty() ? 0 : key_name.c_str(); }
86+
87+
void set_skip_empty (bool v) { skip_empty = v; }
88+
bool get_skip_empty () const { return skip_empty; }
8689
private:
8790
typedef std::map<uint32_t, Entry, std::greater<uint32_t> > Map;
8891
enum { FORMAT_VERSION = 2 };
8992

9093
Map entries;
9194
std::string key_name;
95+
bool skip_empty = false;
9296

9397
void load_header (std::istream&);
9498

9599
enum {
96100
HEADER_FIELD_END = 0,
97-
HEADER_FIELD_KEY_NAME = 1
101+
HEADER_FIELD_KEY_NAME = 1,
102+
HEADER_FIELD_SKIP_EMPTY = 3 // If this field is present, empty files are left unencrypted (see issue #53)
98103
};
99104
enum {
100105
KEY_FIELD_END = 0,

0 commit comments

Comments
 (0)