1 /*
2  * Copyright 2012, 2014 Andrew Ayer
3  *
4  * This file is part of git-crypt.
5  *
6  * git-crypt is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * git-crypt is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with git-crypt.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  * Additional permission under GNU GPL version 3 section 7:
20  *
21  * If you modify the Program, or any covered work, by linking or
22  * combining it with the OpenSSL project's OpenSSL library (or a
23  * modified version of that library), containing parts covered by the
24  * terms of the OpenSSL or SSLeay licenses, the licensors of the Program
25  * grant you additional permission to convey the resulting work.
26  * Corresponding Source for a non-source form of such a combination
27  * shall include the source code for the parts of OpenSSL used as well
28  * as that of the covered work.
29  */
30 
31 #include "commands.hpp"
32 #include "crypto.hpp"
33 #include "util.hpp"
34 #include "key.hpp"
35 #include "gpg.hpp"
36 #include "parse_options.hpp"
37 #include "coprocess.hpp"
38 #include <unistd.h>
39 #include <stdint.h>
40 #include <algorithm>
41 #include <string>
42 #include <fstream>
43 #include <sstream>
44 #include <iostream>
45 #include <cstddef>
46 #include <cstring>
47 #include <cctype>
48 #include <stdio.h>
49 #include <string.h>
50 #include <errno.h>
51 #include <exception>
52 #include <vector>
53 
attribute_name(const char * key_name)54 static std::string attribute_name (const char* key_name)
55 {
56 	if (key_name) {
57 		// named key
58 		return std::string("git-crypt-") + key_name;
59 	} else {
60 		// default key
61 		return "git-crypt";
62 	}
63 }
64 
git_version_string()65 static std::string git_version_string ()
66 {
67 	std::vector<std::string>	command;
68 	command.push_back("/usr/local/bin/git");
69 	command.push_back("version");
70 
71 	std::stringstream		output;
72 	if (!successful_exit(exec_command(command, output))) {
73 		throw Error("'git version' failed - is Git installed?");
74 	}
75 	std::string			word;
76 	output >> word; // "/usr/local/bin/git"
77 	output >> word; // "version"
78 	output >> word; // "1.7.10.4"
79 	return word;
80 }
81 
parse_version(const std::string & str)82 static std::vector<int> parse_version (const std::string& str)
83 {
84 	std::istringstream	in(str);
85 	std::vector<int>	version;
86 	std::string		component;
87 	while (std::getline(in, component, '.')) {
88 		version.push_back(std::atoi(component.c_str()));
89 	}
90 	return version;
91 }
92 
git_version()93 static const std::vector<int>& git_version ()
94 {
95 	static const std::vector<int> version(parse_version(git_version_string()));
96 	return version;
97 }
98 
make_version(int a,int b,int c)99 static std::vector<int> make_version (int a, int b, int c)
100 {
101 	std::vector<int>	version;
102 	version.push_back(a);
103 	version.push_back(b);
104 	version.push_back(c);
105 	return version;
106 }
107 
git_config(const std::string & name,const std::string & value)108 static void git_config (const std::string& name, const std::string& value)
109 {
110 	std::vector<std::string>	command;
111 	command.push_back("/usr/local/bin/git");
112 	command.push_back("config");
113 	command.push_back(name);
114 	command.push_back(value);
115 
116 	if (!successful_exit(exec_command(command))) {
117 		throw Error("'git config' failed");
118 	}
119 }
120 
git_has_config(const std::string & name)121 static bool git_has_config (const std::string& name)
122 {
123 	std::vector<std::string>	command;
124 	command.push_back("/usr/local/bin/git");
125 	command.push_back("config");
126 	command.push_back("--get-all");
127 	command.push_back(name);
128 
129 	std::stringstream		output;
130 	switch (exit_status(exec_command(command, output))) {
131 		case 0:  return true;
132 		case 1:  return false;
133 		default: throw Error("'git config' failed");
134 	}
135 }
136 
git_deconfig(const std::string & name)137 static void git_deconfig (const std::string& name)
138 {
139 	std::vector<std::string>	command;
140 	command.push_back("/usr/local/bin/git");
141 	command.push_back("config");
142 	command.push_back("--remove-section");
143 	command.push_back(name);
144 
145 	if (!successful_exit(exec_command(command))) {
146 		throw Error("'git config' failed");
147 	}
148 }
149 
configure_git_filters(const char * key_name)150 static void configure_git_filters (const char* key_name)
151 {
152 	std::string	escaped_git_crypt_path(escape_shell_arg(our_exe_path()));
153 
154 	if (key_name) {
155 		// Note: key_name contains only shell-safe characters so it need not be escaped.
156 		git_config(std::string("filter.git-crypt-") + key_name + ".smudge",
157 		           escaped_git_crypt_path + " smudge --key-name=" + key_name);
158 		git_config(std::string("filter.git-crypt-") + key_name + ".clean",
159 		           escaped_git_crypt_path + " clean --key-name=" + key_name);
160 		git_config(std::string("filter.git-crypt-") + key_name + ".required", "true");
161 		git_config(std::string("diff.git-crypt-") + key_name + ".textconv",
162 		           escaped_git_crypt_path + " diff --key-name=" + key_name);
163 	} else {
164 		git_config("filter.git-crypt.smudge", escaped_git_crypt_path + " smudge");
165 		git_config("filter.git-crypt.clean", escaped_git_crypt_path + " clean");
166 		git_config("filter.git-crypt.required", "true");
167 		git_config("diff.git-crypt.textconv", escaped_git_crypt_path + " diff");
168 	}
169 }
170 
deconfigure_git_filters(const char * key_name)171 static void deconfigure_git_filters (const char* key_name)
172 {
173 	// deconfigure the git-crypt filters
174 	if (git_has_config("filter." + attribute_name(key_name) + ".smudge") ||
175 			git_has_config("filter." + attribute_name(key_name) + ".clean") ||
176 			git_has_config("filter." + attribute_name(key_name) + ".required")) {
177 
178 		git_deconfig("filter." + attribute_name(key_name));
179 	}
180 
181 	if (git_has_config("diff." + attribute_name(key_name) + ".textconv")) {
182 		git_deconfig("diff." + attribute_name(key_name));
183 	}
184 }
185 
git_checkout(const std::vector<std::string> & paths)186 static bool git_checkout (const std::vector<std::string>& paths)
187 {
188 	std::vector<std::string>	command;
189 
190 	command.push_back("/usr/local/bin/git");
191 	command.push_back("checkout");
192 	command.push_back("--");
193 
194 	for (std::vector<std::string>::const_iterator path(paths.begin()); path != paths.end(); ++path) {
195 		command.push_back(*path);
196 	}
197 
198 	if (!successful_exit(exec_command(command))) {
199 		return false;
200 	}
201 
202 	return true;
203 }
204 
same_key_name(const char * a,const char * b)205 static bool same_key_name (const char* a, const char* b)
206 {
207 	return (!a && !b) || (a && b && std::strcmp(a, b) == 0);
208 }
209 
validate_key_name_or_throw(const char * key_name)210 static void validate_key_name_or_throw (const char* key_name)
211 {
212 	std::string			reason;
213 	if (!validate_key_name(key_name, &reason)) {
214 		throw Error(reason);
215 	}
216 }
217 
get_internal_state_path()218 static std::string get_internal_state_path ()
219 {
220 	// git rev-parse --git-dir
221 	std::vector<std::string>	command;
222 	command.push_back("/usr/local/bin/git");
223 	command.push_back("rev-parse");
224 	command.push_back("--git-dir");
225 
226 	std::stringstream		output;
227 
228 	if (!successful_exit(exec_command(command, output))) {
229 		throw Error("'git rev-parse --git-dir' failed - is this a Git repository?");
230 	}
231 
232 	std::string			path;
233 	std::getline(output, path);
234 	path += "/git-crypt";
235 
236 	return path;
237 }
238 
get_internal_keys_path(const std::string & internal_state_path)239 static std::string get_internal_keys_path (const std::string& internal_state_path)
240 {
241 	return internal_state_path + "/keys";
242 }
243 
get_internal_keys_path()244 static std::string get_internal_keys_path ()
245 {
246 	return get_internal_keys_path(get_internal_state_path());
247 }
248 
get_internal_key_path(const char * key_name)249 static std::string get_internal_key_path (const char* key_name)
250 {
251 	std::string		path(get_internal_keys_path());
252 	path += "/";
253 	path += key_name ? key_name : "default";
254 
255 	return path;
256 }
257 
get_git_config(const std::string & name)258 std::string get_git_config (const std::string& name)
259 {
260 	// git config --get
261 	std::vector<std::string>	command;
262 	command.push_back("/usr/local/bin/git");
263 	command.push_back("config");
264 	command.push_back("--get");
265 	command.push_back(name);
266 
267 	std::stringstream	output;
268 
269 	if (!successful_exit(exec_command(command, output))) {
270 		throw Error("'git config' missing value for key '" + name +"'");
271 	}
272 
273 	std::string		value;
274 	std::getline(output, value);
275 
276 	return value;
277 }
278 
get_repo_state_path()279 static std::string get_repo_state_path ()
280 {
281 	// git rev-parse --show-toplevel
282 	std::vector<std::string>	command;
283 	command.push_back("/usr/local/bin/git");
284 	command.push_back("rev-parse");
285 	command.push_back("--show-toplevel");
286 
287 	std::stringstream		output;
288 
289 	if (!successful_exit(exec_command(command, output))) {
290 		throw Error("'git rev-parse --show-toplevel' failed - is this a Git repository?");
291 	}
292 
293 	std::string			path;
294 	std::getline(output, path);
295 
296 	if (path.empty()) {
297 		// could happen for a bare repo
298 		throw Error("Could not determine Git working tree - is this a non-bare repo?");
299 	}
300 
301 	// Check if the repo state dir has been explicitly configured. If so, use that in path construction.
302 	if (git_has_config("git-crypt.repoStateDir")) {
303 		std::string		repoStateDir = get_git_config("git-crypt.repoStateDir");
304 
305 		// The repoStateDir value must always be relative to git work tree to ensure the repoStateDir can be committed
306 		// along with the remainder of the repository.
307 		path += '/' + repoStateDir;
308 	} else {
309 		// There is no explicitly configured repo state dir configured, so use the default.
310 		path += "/.git-crypt";
311 	}
312 
313 	return path;
314 }
315 
get_repo_keys_path(const std::string & repo_state_path)316 static std::string get_repo_keys_path (const std::string& repo_state_path)
317 {
318 	return repo_state_path + "/keys";
319 }
320 
get_repo_keys_path()321 static std::string get_repo_keys_path ()
322 {
323 	return get_repo_keys_path(get_repo_state_path());
324 }
325 
get_path_to_top()326 static std::string get_path_to_top ()
327 {
328 	// git rev-parse --show-cdup
329 	std::vector<std::string>	command;
330 	command.push_back("/usr/local/bin/git");
331 	command.push_back("rev-parse");
332 	command.push_back("--show-cdup");
333 
334 	std::stringstream		output;
335 
336 	if (!successful_exit(exec_command(command, output))) {
337 		throw Error("'git rev-parse --show-cdup' failed - is this a Git repository?");
338 	}
339 
340 	std::string			path_to_top;
341 	std::getline(output, path_to_top);
342 
343 	return path_to_top;
344 }
345 
get_git_status(std::ostream & output)346 static void get_git_status (std::ostream& output)
347 {
348 	// git status -uno --porcelain
349 	std::vector<std::string>	command;
350 	command.push_back("/usr/local/bin/git");
351 	command.push_back("status");
352 	command.push_back("-uno"); // don't show untracked files
353 	command.push_back("--porcelain");
354 
355 	if (!successful_exit(exec_command(command, output))) {
356 		throw Error("'git status' failed - is this a Git repository?");
357 	}
358 }
359 
360 // returns filter and diff attributes as a pair
get_file_attributes(const std::string & filename)361 static std::pair<std::string, std::string> get_file_attributes (const std::string& filename)
362 {
363 	// git check-attr filter diff -- filename
364 	std::vector<std::string>	command;
365 	command.push_back("/usr/local/bin/git");
366 	command.push_back("check-attr");
367 	command.push_back("filter");
368 	command.push_back("diff");
369 	command.push_back("--");
370 	command.push_back(filename);
371 
372 	std::stringstream		output;
373 	if (!successful_exit(exec_command(command, output))) {
374 		throw Error("'git check-attr' failed - is this a Git repository?");
375 	}
376 
377 	std::string			filter_attr;
378 	std::string			diff_attr;
379 
380 	std::string			line;
381 	// Example output:
382 	// filename: filter: git-crypt
383 	// filename: diff: git-crypt
384 	while (std::getline(output, line)) {
385 		// filename might contain ": ", so parse line backwards
386 		// filename: attr_name: attr_value
387 		//         ^name_pos  ^value_pos
388 		const std::string::size_type	value_pos(line.rfind(": "));
389 		if (value_pos == std::string::npos || value_pos == 0) {
390 			continue;
391 		}
392 		const std::string::size_type	name_pos(line.rfind(": ", value_pos - 1));
393 		if (name_pos == std::string::npos) {
394 			continue;
395 		}
396 
397 		const std::string		attr_name(line.substr(name_pos + 2, value_pos - (name_pos + 2)));
398 		const std::string		attr_value(line.substr(value_pos + 2));
399 
400 		if (attr_value != "unspecified" && attr_value != "unset" && attr_value != "set") {
401 			if (attr_name == "filter") {
402 				filter_attr = attr_value;
403 			} else if (attr_name == "diff") {
404 				diff_attr = attr_value;
405 			}
406 		}
407 	}
408 
409 	return std::make_pair(filter_attr, diff_attr);
410 }
411 
412 // returns filter and diff attributes as a pair
get_file_attributes(const std::string & filename,std::ostream & check_attr_stdin,std::istream & check_attr_stdout)413 static std::pair<std::string, std::string> get_file_attributes (const std::string& filename, std::ostream& check_attr_stdin, std::istream& check_attr_stdout)
414 {
415 	check_attr_stdin << filename << '\0' << std::flush;
416 
417 	std::string			filter_attr;
418 	std::string			diff_attr;
419 
420 	// Example output:
421 	// filename\0filter\0git-crypt\0filename\0diff\0git-crypt\0
422 	for (int i = 0; i < 2; ++i) {
423 		std::string		filename;
424 		std::string		attr_name;
425 		std::string		attr_value;
426 		std::getline(check_attr_stdout, filename, '\0');
427 		std::getline(check_attr_stdout, attr_name, '\0');
428 		std::getline(check_attr_stdout, attr_value, '\0');
429 
430 		if (attr_value != "unspecified" && attr_value != "unset" && attr_value != "set") {
431 			if (attr_name == "filter") {
432 				filter_attr = attr_value;
433 			} else if (attr_name == "diff") {
434 				diff_attr = attr_value;
435 			}
436 		}
437 	}
438 
439 	return std::make_pair(filter_attr, diff_attr);
440 }
441 
check_if_blob_is_encrypted(const std::string & object_id)442 static bool check_if_blob_is_encrypted (const std::string& object_id)
443 {
444 	// git cat-file blob object_id
445 
446 	std::vector<std::string>	command;
447 	command.push_back("/usr/local/bin/git");
448 	command.push_back("cat-file");
449 	command.push_back("blob");
450 	command.push_back(object_id);
451 
452 	// TODO: do this more efficiently - don't read entire command output into buffer, only read what we need
453 	std::stringstream		output;
454 	if (!successful_exit(exec_command(command, output))) {
455 		throw Error("'git cat-file' failed - is this a Git repository?");
456 	}
457 
458 	char				header[10];
459 	output.read(header, sizeof(header));
460 	return output.gcount() == sizeof(header) && std::memcmp(header, "\0GITCRYPT\0", 10) == 0;
461 }
462 
check_if_file_is_encrypted(const std::string & filename)463 static bool check_if_file_is_encrypted (const std::string& filename)
464 {
465 	// git ls-files -sz filename
466 	std::vector<std::string>	command;
467 	command.push_back("/usr/local/bin/git");
468 	command.push_back("ls-files");
469 	command.push_back("-sz");
470 	command.push_back("--");
471 	command.push_back(filename);
472 
473 	std::stringstream		output;
474 	if (!successful_exit(exec_command(command, output))) {
475 		throw Error("'git ls-files' failed - is this a Git repository?");
476 	}
477 
478 	if (output.peek() == -1) {
479 		return false;
480 	}
481 
482 	std::string			mode;
483 	std::string			object_id;
484 	output >> mode >> object_id;
485 
486 	return check_if_blob_is_encrypted(object_id);
487 }
488 
is_git_file_mode(const std::string & mode)489 static bool is_git_file_mode (const std::string& mode)
490 {
491 	return (std::strtoul(mode.c_str(), nullptr, 8) & 0170000) == 0100000;
492 }
493 
get_encrypted_files(std::vector<std::string> & files,const char * key_name)494 static void get_encrypted_files (std::vector<std::string>& files, const char* key_name)
495 {
496 	// git ls-files -cz -- path_to_top
497 	std::vector<std::string>	ls_files_command;
498 	ls_files_command.push_back("/usr/local/bin/git");
499 	ls_files_command.push_back("ls-files");
500 	ls_files_command.push_back("-csz");
501 	ls_files_command.push_back("--");
502 	const std::string		path_to_top(get_path_to_top());
503 	if (!path_to_top.empty()) {
504 		ls_files_command.push_back(path_to_top);
505 	}
506 
507 	Coprocess			ls_files;
508 	std::istream*			ls_files_stdout = ls_files.stdout_pipe();
509 	ls_files.spawn(ls_files_command);
510 
511 	Coprocess			check_attr;
512 	std::ostream*			check_attr_stdin = nullptr;
513 	std::istream*			check_attr_stdout = nullptr;
514 	if (git_version() >= make_version(1, 8, 5)) {
515 		// In Git 1.8.5 (released 27 Nov 2013) and higher, we use a single `git check-attr` process
516 		// to get the attributes of all files at once.  In prior versions, we have to fork and exec
517 		// a separate `git check-attr` process for each file, since -z and --stdin aren't supported.
518 		// In a repository with thousands of files, this results in an almost 100x speedup.
519 		std::vector<std::string>	check_attr_command;
520 		check_attr_command.push_back("/usr/local/bin/git");
521 		check_attr_command.push_back("check-attr");
522 		check_attr_command.push_back("--stdin");
523 		check_attr_command.push_back("-z");
524 		check_attr_command.push_back("filter");
525 		check_attr_command.push_back("diff");
526 
527 		check_attr_stdin = check_attr.stdin_pipe();
528 		check_attr_stdout = check_attr.stdout_pipe();
529 		check_attr.spawn(check_attr_command);
530 	}
531 
532 	while (ls_files_stdout->peek() != -1) {
533 		std::string		mode;
534 		std::string		object_id;
535 		std::string		stage;
536 		std::string		filename;
537 		*ls_files_stdout >> mode >> object_id >> stage >> std::ws;
538 		std::getline(*ls_files_stdout, filename, '\0');
539 
540 		if (is_git_file_mode(mode)) {
541 			std::string	filter_attribute;
542 
543 			if (check_attr_stdin) {
544 				filter_attribute = get_file_attributes(filename, *check_attr_stdin, *check_attr_stdout).first;
545 			} else {
546 				filter_attribute = get_file_attributes(filename).first;
547 			}
548 
549 			if (filter_attribute == attribute_name(key_name)) {
550 				files.push_back(filename);
551 			}
552 		}
553 	}
554 
555 	if (!successful_exit(ls_files.wait())) {
556 		throw Error("'git ls-files' failed - is this a Git repository?");
557 	}
558 
559 	if (check_attr_stdin) {
560 		check_attr.close_stdin();
561 		if (!successful_exit(check_attr.wait())) {
562 			throw Error("'git check-attr' failed - is this a Git repository?");
563 		}
564 	}
565 }
566 
load_key(Key_file & key_file,const char * key_name,const char * key_path=0,const char * legacy_path=0)567 static void load_key (Key_file& key_file, const char* key_name, const char* key_path =0, const char* legacy_path =0)
568 {
569 	if (legacy_path) {
570 		std::ifstream		key_file_in(legacy_path, std::fstream::binary);
571 		if (!key_file_in) {
572 			throw Error(std::string("Unable to open key file: ") + legacy_path);
573 		}
574 		key_file.load_legacy(key_file_in);
575 	} else if (key_path) {
576 		std::ifstream		key_file_in(key_path, std::fstream::binary);
577 		if (!key_file_in) {
578 			throw Error(std::string("Unable to open key file: ") + key_path);
579 		}
580 		key_file.load(key_file_in);
581 	} else {
582 		std::ifstream		key_file_in(get_internal_key_path(key_name).c_str(), std::fstream::binary);
583 		if (!key_file_in) {
584 			// TODO: include key name in error message
585 			throw Error("Unable to open key file - have you unlocked/initialized this repository yet?");
586 		}
587 		key_file.load(key_file_in);
588 	}
589 }
590 
decrypt_repo_key(Key_file & key_file,const char * key_name,uint32_t key_version,const std::vector<std::string> & secret_keys,const std::string & keys_path)591 static bool decrypt_repo_key (Key_file& key_file, const char* key_name, uint32_t key_version, const std::vector<std::string>& secret_keys, const std::string& keys_path)
592 {
593 	std::exception_ptr gpg_error;
594 
595 	for (std::vector<std::string>::const_iterator seckey(secret_keys.begin()); seckey != secret_keys.end(); ++seckey) {
596 		std::ostringstream		path_builder;
597 		path_builder << keys_path << '/' << (key_name ? key_name : "default") << '/' << key_version << '/' << *seckey << ".gpg";
598 		std::string			path(path_builder.str());
599 		if (access(path.c_str(), F_OK) == 0) {
600 			std::stringstream	decrypted_contents;
601 			try {
602 				gpg_decrypt_from_file(path, decrypted_contents);
603 			} catch (const Gpg_error&) {
604 				gpg_error = std::current_exception();
605 				continue;
606 			}
607 			Key_file		this_version_key_file;
608 			this_version_key_file.load(decrypted_contents);
609 			const Key_file::Entry*	this_version_entry = this_version_key_file.get(key_version);
610 			if (!this_version_entry) {
611 				throw Error("GPG-encrypted keyfile is malformed because it does not contain expected key version");
612 			}
613 			if (!same_key_name(key_name, this_version_key_file.get_key_name())) {
614 				throw Error("GPG-encrypted keyfile is malformed because it does not contain expected key name");
615 			}
616 			key_file.set_key_name(key_name);
617 			key_file.add(*this_version_entry);
618 			return true;
619 		}
620 	}
621 
622 	if (gpg_error) {
623 		std::rethrow_exception(gpg_error);
624 	}
625 
626 	return false;
627 }
628 
decrypt_repo_keys(std::vector<Key_file> & key_files,uint32_t key_version,const std::vector<std::string> & secret_keys,const std::string & keys_path)629 static bool decrypt_repo_keys (std::vector<Key_file>& key_files, uint32_t key_version, const std::vector<std::string>& secret_keys, const std::string& keys_path)
630 {
631 	bool				successful = false;
632 	std::vector<std::string>	dirents;
633 
634 	if (access(keys_path.c_str(), F_OK) == 0) {
635 		dirents = get_directory_contents(keys_path.c_str());
636 	}
637 
638 	for (std::vector<std::string>::const_iterator dirent(dirents.begin()); dirent != dirents.end(); ++dirent) {
639 		const char*		key_name = 0;
640 		if (*dirent != "default") {
641 			if (!validate_key_name(dirent->c_str())) {
642 				continue;
643 			}
644 			key_name = dirent->c_str();
645 		}
646 
647 		Key_file	key_file;
648 		if (decrypt_repo_key(key_file, key_name, key_version, secret_keys, keys_path)) {
649 			key_files.push_back(key_file);
650 			successful = true;
651 		}
652 	}
653 	return successful;
654 }
655 
encrypt_repo_key(const char * key_name,const Key_file::Entry & key,const std::vector<std::pair<std::string,bool>> & collab_keys,const std::string & keys_path,std::vector<std::string> * new_files)656 static void encrypt_repo_key (const char* key_name, const Key_file::Entry& key, const std::vector<std::pair<std::string, bool> >& collab_keys, const std::string& keys_path, std::vector<std::string>* new_files)
657 {
658 	std::string	key_file_data;
659 	{
660 		Key_file this_version_key_file;
661 		this_version_key_file.set_key_name(key_name);
662 		this_version_key_file.add(key);
663 		key_file_data = this_version_key_file.store_to_string();
664 	}
665 
666 	for (std::vector<std::pair<std::string, bool> >::const_iterator collab(collab_keys.begin()); collab != collab_keys.end(); ++collab) {
667 		const std::string&	fingerprint(collab->first);
668 		const bool		key_is_trusted(collab->second);
669 		std::ostringstream	path_builder;
670 		path_builder << keys_path << '/' << (key_name ? key_name : "default") << '/' << key.version << '/' << fingerprint << ".gpg";
671 		std::string		path(path_builder.str());
672 
673 		if (access(path.c_str(), F_OK) == 0) {
674 			continue;
675 		}
676 
677 		mkdir_parent(path);
678 		gpg_encrypt_to_file(path, fingerprint, key_is_trusted, key_file_data.data(), key_file_data.size());
679 		new_files->push_back(path);
680 	}
681 }
682 
parse_plumbing_options(const char ** key_name,const char ** key_file,int argc,const char ** argv)683 static int parse_plumbing_options (const char** key_name, const char** key_file, int argc, const char** argv)
684 {
685 	Options_list	options;
686 	options.push_back(Option_def("-k", key_name));
687 	options.push_back(Option_def("--key-name", key_name));
688 	options.push_back(Option_def("--key-file", key_file));
689 
690 	return parse_options(options, argc, argv);
691 }
692 
693 // Encrypt contents of stdin and write to stdout
clean(int argc,const char ** argv)694 int clean (int argc, const char** argv)
695 {
696 	const char*		key_name = 0;
697 	const char*		key_path = 0;
698 	const char*		legacy_key_path = 0;
699 
700 	int			argi = parse_plumbing_options(&key_name, &key_path, argc, argv);
701 	if (argc - argi == 0) {
702 	} else if (!key_name && !key_path && argc - argi == 1) { // Deprecated - for compatibility with pre-0.4
703 		legacy_key_path = argv[argi];
704 	} else {
705 		std::clog << "Usage: git-crypt clean [--key-name=NAME] [--key-file=PATH]" << std::endl;
706 		return 2;
707 	}
708 	Key_file		key_file;
709 	load_key(key_file, key_name, key_path, legacy_key_path);
710 
711 	const Key_file::Entry*	key = key_file.get_latest();
712 	if (!key) {
713 		std::clog << "git-crypt: error: key file is empty" << std::endl;
714 		return 1;
715 	}
716 
717 	// Read the entire file
718 
719 	Hmac_sha1_state	hmac(key->hmac_key, HMAC_KEY_LEN); // Calculate the file's SHA1 HMAC as we go
720 	uint64_t		file_size = 0;	// Keep track of the length, make sure it doesn't get too big
721 	std::string		file_contents;	// First 8MB or so of the file go here
722 	temp_fstream		temp_file;	// The rest of the file spills into a temporary file on disk
723 	temp_file.exceptions(std::fstream::badbit);
724 
725 	char			buffer[1024];
726 
727 	while (std::cin && file_size < Aes_ctr_encryptor::MAX_CRYPT_BYTES) {
728 		std::cin.read(buffer, sizeof(buffer));
729 
730 		const size_t	bytes_read = std::cin.gcount();
731 
732 		hmac.add(reinterpret_cast<unsigned char*>(buffer), bytes_read);
733 		file_size += bytes_read;
734 
735 		if (file_size <= 8388608) {
736 			file_contents.append(buffer, bytes_read);
737 		} else {
738 			if (!temp_file.is_open()) {
739 				temp_file.open(std::fstream::in | std::fstream::out | std::fstream::binary | std::fstream::app);
740 			}
741 			temp_file.write(buffer, bytes_read);
742 		}
743 	}
744 
745 	// Make sure the file isn't so large we'll overflow the counter value (which would doom security)
746 	if (file_size >= Aes_ctr_encryptor::MAX_CRYPT_BYTES) {
747 		std::clog << "git-crypt: error: file too long to encrypt securely" << std::endl;
748 		return 1;
749 	}
750 
751 	// We use an HMAC of the file as the encryption nonce (IV) for CTR mode.
752 	// By using a hash of the file we ensure that the encryption is
753 	// deterministic so git doesn't think the file has changed when it really
754 	// hasn't.  CTR mode with a synthetic IV is provably semantically secure
755 	// under deterministic CPA as long as the synthetic IV is derived from a
756 	// secure PRF applied to the message.  Since HMAC-SHA1 is a secure PRF, this
757 	// encryption scheme is semantically secure under deterministic CPA.
758 	//
759 	// Informally, consider that if a file changes just a tiny bit, the IV will
760 	// be completely different, resulting in a completely different ciphertext
761 	// that leaks no information about the similarities of the plaintexts.  Also,
762 	// since we're using the output from a secure hash function plus a counter
763 	// as the input to our block cipher, we should never have a situation where
764 	// two different plaintext blocks get encrypted with the same CTR value.  A
765 	// nonce will be reused only if the entire file is the same, which leaks no
766 	// information except that the files are the same.
767 	//
768 	// To prevent an attacker from building a dictionary of hash values and then
769 	// looking up the nonce (which must be stored in the clear to allow for
770 	// decryption), we use an HMAC as opposed to a straight hash.
771 
772 	// Note: Hmac_sha1_state::LEN >= Aes_ctr_encryptor::NONCE_LEN
773 
774 	unsigned char		digest[Hmac_sha1_state::LEN];
775 	hmac.get(digest);
776 
777 	// Write a header that...
778 	std::cout.write("\0GITCRYPT\0", 10); // ...identifies this as an encrypted file
779 	std::cout.write(reinterpret_cast<char*>(digest), Aes_ctr_encryptor::NONCE_LEN); // ...includes the nonce
780 
781 	// Now encrypt the file and write to stdout
782 	Aes_ctr_encryptor	aes(key->aes_key, digest);
783 
784 	// First read from the in-memory copy
785 	const unsigned char*	file_data = reinterpret_cast<const unsigned char*>(file_contents.data());
786 	size_t			file_data_len = file_contents.size();
787 	while (file_data_len > 0) {
788 		const size_t	buffer_len = std::min(sizeof(buffer), file_data_len);
789 		aes.process(file_data, reinterpret_cast<unsigned char*>(buffer), buffer_len);
790 		std::cout.write(buffer, buffer_len);
791 		file_data += buffer_len;
792 		file_data_len -= buffer_len;
793 	}
794 
795 	// Then read from the temporary file if applicable
796 	if (temp_file.is_open()) {
797 		temp_file.seekg(0);
798 		while (temp_file.peek() != -1) {
799 			temp_file.read(buffer, sizeof(buffer));
800 
801 			const size_t	buffer_len = temp_file.gcount();
802 
803 			aes.process(reinterpret_cast<unsigned char*>(buffer),
804 			            reinterpret_cast<unsigned char*>(buffer),
805 			            buffer_len);
806 			std::cout.write(buffer, buffer_len);
807 		}
808 	}
809 
810 	return 0;
811 }
812 
decrypt_file_to_stdout(const Key_file & key_file,const unsigned char * header,std::istream & in)813 static int decrypt_file_to_stdout (const Key_file& key_file, const unsigned char* header, std::istream& in)
814 {
815 	const unsigned char*	nonce = header + 10;
816 	uint32_t		key_version = 0; // TODO: get the version from the file header
817 
818 	const Key_file::Entry*	key = key_file.get(key_version);
819 	if (!key) {
820 		std::clog << "git-crypt: error: key version " << key_version << " not available - please unlock with the latest version of the key." << std::endl;
821 		return 1;
822 	}
823 
824 	Aes_ctr_decryptor	aes(key->aes_key, nonce);
825 	Hmac_sha1_state		hmac(key->hmac_key, HMAC_KEY_LEN);
826 	while (in) {
827 		unsigned char	buffer[1024];
828 		in.read(reinterpret_cast<char*>(buffer), sizeof(buffer));
829 		aes.process(buffer, buffer, in.gcount());
830 		hmac.add(buffer, in.gcount());
831 		std::cout.write(reinterpret_cast<char*>(buffer), in.gcount());
832 	}
833 
834 	unsigned char		digest[Hmac_sha1_state::LEN];
835 	hmac.get(digest);
836 	if (!leakless_equals(digest, nonce, Aes_ctr_decryptor::NONCE_LEN)) {
837 		std::clog << "git-crypt: error: encrypted file has been tampered with!" << std::endl;
838 		// Although we've already written the tampered file to stdout, exiting
839 		// with a non-zero status will tell git the file has not been filtered,
840 		// so git will not replace it.
841 		return 1;
842 	}
843 
844 	return 0;
845 }
846 
847 // Decrypt contents of stdin and write to stdout
smudge(int argc,const char ** argv)848 int smudge (int argc, const char** argv)
849 {
850 	const char*		key_name = 0;
851 	const char*		key_path = 0;
852 	const char*		legacy_key_path = 0;
853 
854 	int			argi = parse_plumbing_options(&key_name, &key_path, argc, argv);
855 	if (argc - argi == 0) {
856 	} else if (!key_name && !key_path && argc - argi == 1) { // Deprecated - for compatibility with pre-0.4
857 		legacy_key_path = argv[argi];
858 	} else {
859 		std::clog << "Usage: git-crypt smudge [--key-name=NAME] [--key-file=PATH]" << std::endl;
860 		return 2;
861 	}
862 	Key_file		key_file;
863 	load_key(key_file, key_name, key_path, legacy_key_path);
864 
865 	// Read the header to get the nonce and make sure it's actually encrypted
866 	unsigned char		header[10 + Aes_ctr_decryptor::NONCE_LEN];
867 	std::cin.read(reinterpret_cast<char*>(header), sizeof(header));
868 	if (std::cin.gcount() != sizeof(header) || std::memcmp(header, "\0GITCRYPT\0", 10) != 0) {
869 		// File not encrypted - just copy it out to stdout
870 		std::clog << "git-crypt: Warning: file not encrypted" << std::endl;
871 		std::clog << "git-crypt: Run 'git-crypt status' to make sure all files are properly encrypted." << std::endl;
872 		std::clog << "git-crypt: If 'git-crypt status' reports no problems, then an older version of" << std::endl;
873 		std::clog << "git-crypt: this file may be unencrypted in the repository's history.  If this" << std::endl;
874 		std::clog << "git-crypt: file contains sensitive information, you can use 'git filter-branch'" << std::endl;
875 		std::clog << "git-crypt: to remove its old versions from the history." << std::endl;
876 		std::cout.write(reinterpret_cast<char*>(header), std::cin.gcount()); // include the bytes which we already read
877 		std::cout << std::cin.rdbuf();
878 		return 0;
879 	}
880 
881 	return decrypt_file_to_stdout(key_file, header, std::cin);
882 }
883 
diff(int argc,const char ** argv)884 int diff (int argc, const char** argv)
885 {
886 	const char*		key_name = 0;
887 	const char*		key_path = 0;
888 	const char*		filename = 0;
889 	const char*		legacy_key_path = 0;
890 
891 	int			argi = parse_plumbing_options(&key_name, &key_path, argc, argv);
892 	if (argc - argi == 1) {
893 		filename = argv[argi];
894 	} else if (!key_name && !key_path && argc - argi == 2) { // Deprecated - for compatibility with pre-0.4
895 		legacy_key_path = argv[argi];
896 		filename = argv[argi + 1];
897 	} else {
898 		std::clog << "Usage: git-crypt diff [--key-name=NAME] [--key-file=PATH] FILENAME" << std::endl;
899 		return 2;
900 	}
901 	Key_file		key_file;
902 	load_key(key_file, key_name, key_path, legacy_key_path);
903 
904 	// Open the file
905 	std::ifstream		in(filename, std::fstream::binary);
906 	if (!in) {
907 		std::clog << "git-crypt: " << filename << ": unable to open for reading" << std::endl;
908 		return 1;
909 	}
910 	in.exceptions(std::fstream::badbit);
911 
912 	// Read the header to get the nonce and determine if it's actually encrypted
913 	unsigned char		header[10 + Aes_ctr_decryptor::NONCE_LEN];
914 	in.read(reinterpret_cast<char*>(header), sizeof(header));
915 	if (in.gcount() != sizeof(header) || std::memcmp(header, "\0GITCRYPT\0", 10) != 0) {
916 		// File not encrypted - just copy it out to stdout
917 		std::cout.write(reinterpret_cast<char*>(header), in.gcount()); // include the bytes which we already read
918 		std::cout << in.rdbuf();
919 		return 0;
920 	}
921 
922 	// Go ahead and decrypt it
923 	return decrypt_file_to_stdout(key_file, header, in);
924 }
925 
help_init(std::ostream & out)926 void help_init (std::ostream& out)
927 {
928 	//     |--------------------------------------------------------------------------------| 80 chars
929 	out << "Usage: git-crypt init [OPTIONS]" << std::endl;
930 	out << std::endl;
931 	out << "    -k, --key-name KEYNAME      Initialize the given key, instead of the default" << std::endl;
932 	out << std::endl;
933 }
934 
init(int argc,const char ** argv)935 int init (int argc, const char** argv)
936 {
937 	const char*	key_name = 0;
938 	Options_list	options;
939 	options.push_back(Option_def("-k", &key_name));
940 	options.push_back(Option_def("--key-name", &key_name));
941 
942 	int		argi = parse_options(options, argc, argv);
943 
944 	if (!key_name && argc - argi == 1) {
945 		std::clog << "Warning: 'git-crypt init' with a key file is deprecated as of git-crypt 0.4" << std::endl;
946 		std::clog << "and will be removed in a future release. Please get in the habit of using" << std::endl;
947 		std::clog << "'git-crypt unlock KEYFILE' instead." << std::endl;
948 		return unlock(argc, argv);
949 	}
950 	if (argc - argi != 0) {
951 		std::clog << "Error: git-crypt init takes no arguments" << std::endl;
952 		help_init(std::clog);
953 		return 2;
954 	}
955 
956 	if (key_name) {
957 		validate_key_name_or_throw(key_name);
958 	}
959 
960 	std::string		internal_key_path(get_internal_key_path(key_name));
961 	if (access(internal_key_path.c_str(), F_OK) == 0) {
962 		// TODO: add a -f option to reinitialize the repo anyways (this should probably imply a refresh)
963 		// TODO: include key_name in error message
964 		std::clog << "Error: this repository has already been initialized with git-crypt." << std::endl;
965 		return 1;
966 	}
967 
968 	// 1. Generate a key and install it
969 	std::clog << "Generating key..." << std::endl;
970 	Key_file		key_file;
971 	key_file.set_key_name(key_name);
972 	key_file.generate();
973 
974 	mkdir_parent(internal_key_path);
975 	if (!key_file.store_to_file(internal_key_path.c_str())) {
976 		std::clog << "Error: " << internal_key_path << ": unable to write key file" << std::endl;
977 		return 1;
978 	}
979 
980 	// 2. Configure git for git-crypt
981 	configure_git_filters(key_name);
982 
983 	return 0;
984 }
985 
help_unlock(std::ostream & out)986 void help_unlock (std::ostream& out)
987 {
988 	//     |--------------------------------------------------------------------------------| 80 chars
989 	out << "Usage: git-crypt unlock" << std::endl;
990 	out << "   or: git-crypt unlock KEY_FILE ..." << std::endl;
991 }
unlock(int argc,const char ** argv)992 int unlock (int argc, const char** argv)
993 {
994 	// 1. Make sure working directory is clean (ignoring untracked files)
995 	// We do this because we check out files later, and we don't want the
996 	// user to lose any changes.  (TODO: only care if encrypted files are
997 	// modified, since we only check out encrypted files)
998 
999 	// Running 'git status' also serves as a check that the Git repo is accessible.
1000 
1001 	std::stringstream	status_output;
1002 	get_git_status(status_output);
1003 	if (status_output.peek() != -1) {
1004 		std::clog << "Error: Working directory not clean." << std::endl;
1005 		std::clog << "Please commit your changes or 'git stash' them before running 'git-crypt unlock'." << std::endl;
1006 		return 1;
1007 	}
1008 
1009 	// 2. Load the key(s)
1010 	std::vector<Key_file>	key_files;
1011 	if (argc > 0) {
1012 		// Read from the symmetric key file(s)
1013 
1014 		for (int argi = 0; argi < argc; ++argi) {
1015 			const char*	symmetric_key_file = argv[argi];
1016 			Key_file	key_file;
1017 
1018 			try {
1019 				if (std::strcmp(symmetric_key_file, "-") == 0) {
1020 					key_file.load(std::cin);
1021 				} else {
1022 					if (!key_file.load_from_file(symmetric_key_file)) {
1023 						std::clog << "Error: " << symmetric_key_file << ": unable to read key file" << std::endl;
1024 						return 1;
1025 					}
1026 				}
1027 			} catch (Key_file::Incompatible) {
1028 				std::clog << "Error: " << symmetric_key_file << " is in an incompatible format" << std::endl;
1029 				std::clog << "Please upgrade to a newer version of git-crypt." << std::endl;
1030 				return 1;
1031 			} catch (Key_file::Malformed) {
1032 				std::clog << "Error: " << symmetric_key_file << ": not a valid git-crypt key file" << std::endl;
1033 				std::clog << "If this key was created prior to git-crypt 0.4, you need to migrate it" << std::endl;
1034 				std::clog << "by running 'git-crypt migrate-key /path/to/old_key /path/to/migrated_key'." << std::endl;
1035 				return 1;
1036 			}
1037 
1038 			key_files.push_back(key_file);
1039 		}
1040 	} else {
1041 		// Decrypt GPG key from root of repo
1042 		std::string			repo_keys_path(get_repo_keys_path());
1043 		std::vector<std::string>	gpg_secret_keys(gpg_list_secret_keys());
1044 		// TODO: command-line option to specify the precise secret key to use
1045 		// 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
1046 		// TODO: command line option to only unlock specific key instead of all of them
1047 		// TODO: avoid decrypting repo keys which are already unlocked in the .git directory
1048 		if (!decrypt_repo_keys(key_files, 0, gpg_secret_keys, repo_keys_path)) {
1049 			std::clog << "Error: no GPG secret key available to unlock this repository." << std::endl;
1050 			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;
1051 			// TODO std::clog << "To see a list of GPG keys authorized to unlock this repository, run 'git-crypt ls-gpg-users'." << std::endl;
1052 			return 1;
1053 		}
1054 	}
1055 
1056 
1057 	// 3. Install the key(s) and configure the git filters
1058 	std::vector<std::string>	encrypted_files;
1059 	for (std::vector<Key_file>::iterator key_file(key_files.begin()); key_file != key_files.end(); ++key_file) {
1060 		std::string		internal_key_path(get_internal_key_path(key_file->get_key_name()));
1061 		// TODO: croak if internal_key_path already exists???
1062 		mkdir_parent(internal_key_path);
1063 		if (!key_file->store_to_file(internal_key_path.c_str())) {
1064 			std::clog << "Error: " << internal_key_path << ": unable to write key file" << std::endl;
1065 			return 1;
1066 		}
1067 
1068 		configure_git_filters(key_file->get_key_name());
1069 		get_encrypted_files(encrypted_files, key_file->get_key_name());
1070 	}
1071 
1072 	// 4. Check out the files that are currently encrypted.
1073 	// Git won't check out a file if its mtime hasn't changed, so touch every file first.
1074 	for (std::vector<std::string>::const_iterator file(encrypted_files.begin()); file != encrypted_files.end(); ++file) {
1075 		touch_file(*file);
1076 	}
1077 	if (!git_checkout(encrypted_files)) {
1078 		std::clog << "Error: 'git checkout' failed" << std::endl;
1079 		std::clog << "git-crypt has been set up but existing encrypted files have not been decrypted" << std::endl;
1080 		return 1;
1081 	}
1082 
1083 	return 0;
1084 }
1085 
help_lock(std::ostream & out)1086 void help_lock (std::ostream& out)
1087 {
1088 	//     |--------------------------------------------------------------------------------| 80 chars
1089 	out << "Usage: git-crypt lock [OPTIONS]" << std::endl;
1090 	out << std::endl;
1091 	out << "    -a, --all                Lock all keys, instead of just the default" << std::endl;
1092 	out << "    -k, --key-name KEYNAME   Lock the given key, instead of the default" << std::endl;
1093 	out << "    -f, --force              Lock even if unclean (you may lose uncommited work)" << std::endl;
1094 	out << std::endl;
1095 }
lock(int argc,const char ** argv)1096 int lock (int argc, const char** argv)
1097 {
1098 	const char*	key_name = 0;
1099 	bool		all_keys = false;
1100 	bool		force = false;
1101 	Options_list	options;
1102 	options.push_back(Option_def("-k", &key_name));
1103 	options.push_back(Option_def("--key-name", &key_name));
1104 	options.push_back(Option_def("-a", &all_keys));
1105 	options.push_back(Option_def("--all", &all_keys));
1106 	options.push_back(Option_def("-f", &force));
1107 	options.push_back(Option_def("--force", &force));
1108 
1109 	int			argi = parse_options(options, argc, argv);
1110 
1111 	if (argc - argi != 0) {
1112 		std::clog << "Error: git-crypt lock takes no arguments" << std::endl;
1113 		help_lock(std::clog);
1114 		return 2;
1115 	}
1116 
1117 	if (all_keys && key_name) {
1118 		std::clog << "Error: -k and --all options are mutually exclusive" << std::endl;
1119 		return 2;
1120 	}
1121 
1122 	// 1. Make sure working directory is clean (ignoring untracked files)
1123 	// We do this because we check out files later, and we don't want the
1124 	// user to lose any changes.  (TODO: only care if encrypted files are
1125 	// modified, since we only check out encrypted files)
1126 
1127 	// Running 'git status' also serves as a check that the Git repo is accessible.
1128 
1129 	std::stringstream	status_output;
1130 	get_git_status(status_output);
1131 	if (!force && status_output.peek() != -1) {
1132 		std::clog << "Error: Working directory not clean." << std::endl;
1133 		std::clog << "Please commit your changes or 'git stash' them before running 'git-crypt lock'." << std::endl;
1134 		std::clog << "Or, use 'git-crypt lock --force' and possibly lose uncommitted changes." << std::endl;
1135 		return 1;
1136 	}
1137 
1138 	// 2. deconfigure the git filters and remove decrypted keys
1139 	std::vector<std::string>	encrypted_files;
1140 	if (all_keys) {
1141 		// deconfigure for all keys
1142 		std::vector<std::string> dirents = get_directory_contents(get_internal_keys_path().c_str());
1143 
1144 		for (std::vector<std::string>::const_iterator dirent(dirents.begin()); dirent != dirents.end(); ++dirent) {
1145 			const char* this_key_name = (*dirent == "default" ? 0 : dirent->c_str());
1146 			remove_file(get_internal_key_path(this_key_name));
1147 			deconfigure_git_filters(this_key_name);
1148 			get_encrypted_files(encrypted_files, this_key_name);
1149 		}
1150 	} else {
1151 		// just handle the given key
1152 		std::string	internal_key_path(get_internal_key_path(key_name));
1153 		if (access(internal_key_path.c_str(), F_OK) == -1 && errno == ENOENT) {
1154 			std::clog << "Error: this repository is already locked";
1155 			if (key_name) {
1156 				std::clog << " with key '" << key_name << "'";
1157 			}
1158 			std::clog << "." << std::endl;
1159 			return 1;
1160 		}
1161 
1162 		remove_file(internal_key_path);
1163 		deconfigure_git_filters(key_name);
1164 		get_encrypted_files(encrypted_files, key_name);
1165 	}
1166 
1167 	// 3. Check out the files that are currently decrypted but should be encrypted.
1168 	// Git won't check out a file if its mtime hasn't changed, so touch every file first.
1169 	for (std::vector<std::string>::const_iterator file(encrypted_files.begin()); file != encrypted_files.end(); ++file) {
1170 		touch_file(*file);
1171 	}
1172 	if (!git_checkout(encrypted_files)) {
1173 		std::clog << "Error: 'git checkout' failed" << std::endl;
1174 		std::clog << "git-crypt has been locked but up but existing decrypted files have not been encrypted" << std::endl;
1175 		return 1;
1176 	}
1177 
1178 	return 0;
1179 }
1180 
help_add_gpg_user(std::ostream & out)1181 void help_add_gpg_user (std::ostream& out)
1182 {
1183 	//     |--------------------------------------------------------------------------------| 80 chars
1184 	out << "Usage: git-crypt add-gpg-user [OPTIONS] GPG_USER_ID ..." << std::endl;
1185 	out << std::endl;
1186 	out << "    -k, --key-name KEYNAME      Add GPG user to given key, instead of default" << std::endl;
1187 	out << "    -n, --no-commit             Don't automatically commit" << std::endl;
1188 	out << "    --trusted                   Assume the GPG user IDs are trusted" << std::endl;
1189 	out << std::endl;
1190 }
add_gpg_user(int argc,const char ** argv)1191 int add_gpg_user (int argc, const char** argv)
1192 {
1193 	const char*		key_name = 0;
1194 	bool			no_commit = false;
1195 	bool			trusted = false;
1196 	Options_list		options;
1197 	options.push_back(Option_def("-k", &key_name));
1198 	options.push_back(Option_def("--key-name", &key_name));
1199 	options.push_back(Option_def("-n", &no_commit));
1200 	options.push_back(Option_def("--no-commit", &no_commit));
1201 	options.push_back(Option_def("--trusted", &trusted));
1202 
1203 	int			argi = parse_options(options, argc, argv);
1204 	if (argc - argi == 0) {
1205 		std::clog << "Error: no GPG user ID specified" << std::endl;
1206 		help_add_gpg_user(std::clog);
1207 		return 2;
1208 	}
1209 
1210 	// build a list of key fingerprints, and whether the key is trusted, for every collaborator specified on the command line
1211 	std::vector<std::pair<std::string, bool> >	collab_keys;
1212 
1213 	for (int i = argi; i < argc; ++i) {
1214 		std::vector<std::string> keys(gpg_lookup_key(argv[i]));
1215 		if (keys.empty()) {
1216 			std::clog << "Error: public key for '" << argv[i] << "' not found in your GPG keyring" << std::endl;
1217 			return 1;
1218 		}
1219 		if (keys.size() > 1) {
1220 			std::clog << "Error: more than one public key matches '" << argv[i] << "' - please be more specific" << std::endl;
1221 			return 1;
1222 		}
1223 
1224 		const bool is_full_fingerprint(std::strncmp(argv[i], "0x", 2) == 0 && std::strlen(argv[i]) == 42);
1225 		collab_keys.push_back(std::make_pair(keys[0], trusted || is_full_fingerprint));
1226 	}
1227 
1228 	// TODO: have a retroactive option to grant access to all key versions, not just the most recent
1229 	Key_file			key_file;
1230 	load_key(key_file, key_name);
1231 	const Key_file::Entry*		key = key_file.get_latest();
1232 	if (!key) {
1233 		std::clog << "Error: key file is empty" << std::endl;
1234 		return 1;
1235 	}
1236 
1237 	const std::string		state_path(get_repo_state_path());
1238 	std::vector<std::string>	new_files;
1239 
1240 	encrypt_repo_key(key_name, *key, collab_keys, get_repo_keys_path(state_path), &new_files);
1241 
1242 	// Add a .gitatributes file to the repo state directory to prevent files in it from being encrypted.
1243 	const std::string		state_gitattributes_path(state_path + "/.gitattributes");
1244 	if (access(state_gitattributes_path.c_str(), F_OK) != 0) {
1245 		std::ofstream		state_gitattributes_file(state_gitattributes_path.c_str());
1246 		//                          |--------------------------------------------------------------------------------| 80 chars
1247 		state_gitattributes_file << "# Do not edit this file.  To specify the files to encrypt, create your own\n";
1248 		state_gitattributes_file << "# .gitattributes file in the directory where your files are.\n";
1249 		state_gitattributes_file << "* !filter !diff\n";
1250 		state_gitattributes_file << "*.gpg binary\n";
1251 		state_gitattributes_file.close();
1252 		if (!state_gitattributes_file) {
1253 			std::clog << "Error: unable to write " << state_gitattributes_path << std::endl;
1254 			return 1;
1255 		}
1256 		new_files.push_back(state_gitattributes_path);
1257 	}
1258 
1259 	// add/commit the new files
1260 	if (!new_files.empty()) {
1261 		// git add NEW_FILE ...
1262 		std::vector<std::string>	command;
1263 		command.push_back("/usr/local/bin/git");
1264 		command.push_back("add");
1265 		command.push_back("--");
1266 		command.insert(command.end(), new_files.begin(), new_files.end());
1267 		if (!successful_exit(exec_command(command))) {
1268 			std::clog << "Error: 'git add' failed" << std::endl;
1269 			return 1;
1270 		}
1271 
1272 		// git commit ...
1273 		if (!no_commit) {
1274 			// TODO: include key_name in commit message
1275 			std::ostringstream	commit_message_builder;
1276 			commit_message_builder << "Add " << collab_keys.size() << " git-crypt collaborator" << (collab_keys.size() != 1 ? "s" : "") << "\n\nNew collaborators:\n\n";
1277 			for (std::vector<std::pair<std::string, bool> >::const_iterator collab(collab_keys.begin()); collab != collab_keys.end(); ++collab) {
1278 				commit_message_builder << '\t' << gpg_shorten_fingerprint(collab->first) << ' ' << gpg_get_uid(collab->first) << '\n';
1279 			}
1280 
1281 			// git commit -m MESSAGE NEW_FILE ...
1282 			command.clear();
1283 			command.push_back("/usr/local/bin/git");
1284 			command.push_back("commit");
1285 			command.push_back("-m");
1286 			command.push_back(commit_message_builder.str());
1287 			command.push_back("--");
1288 			command.insert(command.end(), new_files.begin(), new_files.end());
1289 
1290 			if (!successful_exit(exec_command(command))) {
1291 				std::clog << "Error: 'git commit' failed" << std::endl;
1292 				return 1;
1293 			}
1294 		}
1295 	}
1296 
1297 	return 0;
1298 }
1299 
help_rm_gpg_user(std::ostream & out)1300 void help_rm_gpg_user (std::ostream& out)
1301 {
1302 	//     |--------------------------------------------------------------------------------| 80 chars
1303 	out << "Usage: git-crypt rm-gpg-user [OPTIONS] GPG_USER_ID ..." << std::endl;
1304 	out << std::endl;
1305 	out << "    -k, --key-name KEYNAME      Remove user from given key, instead of default" << std::endl;
1306 	out << "    -n, --no-commit             Don't automatically commit" << std::endl;
1307 	out << std::endl;
1308 }
rm_gpg_user(int argc,const char ** argv)1309 int rm_gpg_user (int argc, const char** argv) // TODO
1310 {
1311 	std::clog << "Error: rm-gpg-user is not yet implemented." << std::endl;
1312 	return 1;
1313 }
1314 
help_ls_gpg_users(std::ostream & out)1315 void help_ls_gpg_users (std::ostream& out)
1316 {
1317 	//     |--------------------------------------------------------------------------------| 80 chars
1318 	out << "Usage: git-crypt ls-gpg-users" << std::endl;
1319 }
ls_gpg_users(int argc,const char ** argv)1320 int ls_gpg_users (int argc, const char** argv) // TODO
1321 {
1322 	// Sketch:
1323 	// Scan the sub-directories in .git-crypt/keys, outputting something like this:
1324 	// ====
1325 	// Key version 0:
1326 	//  0x143DE9B3F7316900 Andrew Ayer <andrew@example.com>
1327 	//  0x4E386D9C9C61702F ???
1328 	// Key version 1:
1329 	//  0x143DE9B3F7316900 Andrew Ayer <andrew@example.com>
1330 	//  0x1727274463D27F40 John Smith <smith@example.com>
1331 	//  0x4E386D9C9C61702F ???
1332 	// ====
1333 	// To resolve a long hex ID, use a command like this:
1334 	//  gpg --options /dev/null --fixed-list-mode --batch --with-colons --list-keys 0x143DE9B3F7316900
1335 
1336 	std::clog << "Error: ls-gpg-users is not yet implemented." << std::endl;
1337 	return 1;
1338 }
1339 
help_export_key(std::ostream & out)1340 void help_export_key (std::ostream& out)
1341 {
1342 	//     |--------------------------------------------------------------------------------| 80 chars
1343 	out << "Usage: git-crypt export-key [OPTIONS] FILENAME" << std::endl;
1344 	out << std::endl;
1345 	out << "    -k, --key-name KEYNAME      Export the given key, instead of the default" << std::endl;
1346 	out << std::endl;
1347 	out << "When FILENAME is -, export to standard out." << std::endl;
1348 }
export_key(int argc,const char ** argv)1349 int export_key (int argc, const char** argv)
1350 {
1351 	// TODO: provide options to export only certain key versions
1352 	const char*		key_name = 0;
1353 	Options_list		options;
1354 	options.push_back(Option_def("-k", &key_name));
1355 	options.push_back(Option_def("--key-name", &key_name));
1356 
1357 	int			argi = parse_options(options, argc, argv);
1358 
1359 	if (argc - argi != 1) {
1360 		std::clog << "Error: no filename specified" << std::endl;
1361 		help_export_key(std::clog);
1362 		return 2;
1363 	}
1364 
1365 	Key_file		key_file;
1366 	load_key(key_file, key_name);
1367 
1368 	const char*		out_file_name = argv[argi];
1369 
1370 	if (std::strcmp(out_file_name, "-") == 0) {
1371 		key_file.store(std::cout);
1372 	} else {
1373 		if (!key_file.store_to_file(out_file_name)) {
1374 			std::clog << "Error: " << out_file_name << ": unable to write key file" << std::endl;
1375 			return 1;
1376 		}
1377 	}
1378 
1379 	return 0;
1380 }
1381 
help_keygen(std::ostream & out)1382 void help_keygen (std::ostream& out)
1383 {
1384 	//     |--------------------------------------------------------------------------------| 80 chars
1385 	out << "Usage: git-crypt keygen FILENAME" << std::endl;
1386 	out << std::endl;
1387 	out << "When FILENAME is -, write to standard out." << std::endl;
1388 }
keygen(int argc,const char ** argv)1389 int keygen (int argc, const char** argv)
1390 {
1391 	if (argc != 1) {
1392 		std::clog << "Error: no filename specified" << std::endl;
1393 		help_keygen(std::clog);
1394 		return 2;
1395 	}
1396 
1397 	const char*		key_file_name = argv[0];
1398 
1399 	if (std::strcmp(key_file_name, "-") != 0 && access(key_file_name, F_OK) == 0) {
1400 		std::clog << key_file_name << ": File already exists" << std::endl;
1401 		return 1;
1402 	}
1403 
1404 	std::clog << "Generating key..." << std::endl;
1405 	Key_file		key_file;
1406 	key_file.generate();
1407 
1408 	if (std::strcmp(key_file_name, "-") == 0) {
1409 		key_file.store(std::cout);
1410 	} else {
1411 		if (!key_file.store_to_file(key_file_name)) {
1412 			std::clog << "Error: " << key_file_name << ": unable to write key file" << std::endl;
1413 			return 1;
1414 		}
1415 	}
1416 	return 0;
1417 }
1418 
help_migrate_key(std::ostream & out)1419 void help_migrate_key (std::ostream& out)
1420 {
1421 	//     |--------------------------------------------------------------------------------| 80 chars
1422 	out << "Usage: git-crypt migrate-key OLDFILENAME NEWFILENAME" << std::endl;
1423 	out << std::endl;
1424 	out << "Use - to read from standard in/write to standard out." << std::endl;
1425 }
migrate_key(int argc,const char ** argv)1426 int migrate_key (int argc, const char** argv)
1427 {
1428 	if (argc != 2) {
1429 		std::clog << "Error: filenames not specified" << std::endl;
1430 		help_migrate_key(std::clog);
1431 		return 2;
1432 	}
1433 
1434 	const char*		key_file_name = argv[0];
1435 	const char*		new_key_file_name = argv[1];
1436 	Key_file		key_file;
1437 
1438 	try {
1439 		if (std::strcmp(key_file_name, "-") == 0) {
1440 			key_file.load_legacy(std::cin);
1441 		} else {
1442 			std::ifstream	in(key_file_name, std::fstream::binary);
1443 			if (!in) {
1444 				std::clog << "Error: " << key_file_name << ": unable to open for reading" << std::endl;
1445 				return 1;
1446 			}
1447 			key_file.load_legacy(in);
1448 		}
1449 
1450 		if (std::strcmp(new_key_file_name, "-") == 0) {
1451 			key_file.store(std::cout);
1452 		} else {
1453 			if (!key_file.store_to_file(new_key_file_name)) {
1454 				std::clog << "Error: " << new_key_file_name << ": unable to write key file" << std::endl;
1455 				return 1;
1456 			}
1457 		}
1458 	} catch (Key_file::Malformed) {
1459 		std::clog << "Error: " << key_file_name << ": not a valid legacy git-crypt key file" << std::endl;
1460 		return 1;
1461 	}
1462 
1463 	return 0;
1464 }
1465 
help_refresh(std::ostream & out)1466 void help_refresh (std::ostream& out)
1467 {
1468 	//     |--------------------------------------------------------------------------------| 80 chars
1469 	out << "Usage: git-crypt refresh" << std::endl;
1470 }
refresh(int argc,const char ** argv)1471 int refresh (int argc, const char** argv) // TODO: do a force checkout, much like in unlock
1472 {
1473 	std::clog << "Error: refresh is not yet implemented." << std::endl;
1474 	return 1;
1475 }
1476 
help_status(std::ostream & out)1477 void help_status (std::ostream& out)
1478 {
1479 	//     |--------------------------------------------------------------------------------| 80 chars
1480 	out << "Usage: git-crypt status [OPTIONS] [FILE ...]" << std::endl;
1481 	//out << "   or: git-crypt status -r [OPTIONS]" << std::endl;
1482 	//out << "   or: git-crypt status -f" << std::endl;
1483 	out << std::endl;
1484 	out << "    -e             Show encrypted files only" << std::endl;
1485 	out << "    -u             Show unencrypted files only" << std::endl;
1486 	//out << "    -r             Show repository status only" << std::endl;
1487 	out << "    -f, --fix      Fix problems with the repository" << std::endl;
1488 	//out << "    -z             Machine-parseable output" << std::endl;
1489 	out << std::endl;
1490 }
status(int argc,const char ** argv)1491 int status (int argc, const char** argv)
1492 {
1493 	// Usage:
1494 	//  git-crypt status -r [-z]			Show repo status
1495 	//  git-crypt status [-e | -u] [-z] [FILE ...]	Show encrypted status of files
1496 	//  git-crypt status -f				Fix unencrypted blobs
1497 
1498 	bool		repo_status_only = false;	// -r show repo status only
1499 	bool		show_encrypted_only = false;	// -e show encrypted files only
1500 	bool		show_unencrypted_only = false;	// -u show unencrypted files only
1501 	bool		fix_problems = false;		// -f fix problems
1502 	bool		machine_output = false;		// -z machine-parseable output
1503 
1504 	Options_list	options;
1505 	options.push_back(Option_def("-r", &repo_status_only));
1506 	options.push_back(Option_def("-e", &show_encrypted_only));
1507 	options.push_back(Option_def("-u", &show_unencrypted_only));
1508 	options.push_back(Option_def("-f", &fix_problems));
1509 	options.push_back(Option_def("--fix", &fix_problems));
1510 	options.push_back(Option_def("-z", &machine_output));
1511 
1512 	int		argi = parse_options(options, argc, argv);
1513 
1514 	if (repo_status_only) {
1515 		if (show_encrypted_only || show_unencrypted_only) {
1516 			std::clog << "Error: -e and -u options cannot be used with -r" << std::endl;
1517 			return 2;
1518 		}
1519 		if (fix_problems) {
1520 			std::clog << "Error: -f option cannot be used with -r" << std::endl;
1521 			return 2;
1522 		}
1523 		if (argc - argi != 0) {
1524 			std::clog << "Error: filenames cannot be specified when -r is used" << std::endl;
1525 			return 2;
1526 		}
1527 	}
1528 
1529 	if (show_encrypted_only && show_unencrypted_only) {
1530 		std::clog << "Error: -e and -u options are mutually exclusive" << std::endl;
1531 		return 2;
1532 	}
1533 
1534 	if (fix_problems && (show_encrypted_only || show_unencrypted_only)) {
1535 		std::clog << "Error: -e and -u options cannot be used with -f" << std::endl;
1536 		return 2;
1537 	}
1538 
1539 	if (machine_output) {
1540 		// TODO: implement machine-parseable output
1541 		std::clog << "Sorry, machine-parseable output is not yet implemented" << std::endl;
1542 		return 2;
1543 	}
1544 
1545 	if (argc - argi == 0) {
1546 		// TODO: check repo status:
1547 		//	is it set up for git-crypt?
1548 		//	which keys are unlocked?
1549 		//	--> check for filter config (see configure_git_filters()) and corresponding internal key
1550 
1551 		if (repo_status_only) {
1552 			return 0;
1553 		}
1554 	}
1555 
1556 	// git ls-files -cotsz --exclude-standard ...
1557 	std::vector<std::string>	command;
1558 	command.push_back("/usr/local/bin/git");
1559 	command.push_back("ls-files");
1560 	command.push_back("-cotsz");
1561 	command.push_back("--exclude-standard");
1562 	command.push_back("--");
1563 	if (argc - argi == 0) {
1564 		const std::string	path_to_top(get_path_to_top());
1565 		if (!path_to_top.empty()) {
1566 			command.push_back(path_to_top);
1567 		}
1568 	} else {
1569 		for (int i = argi; i < argc; ++i) {
1570 			command.push_back(argv[i]);
1571 		}
1572 	}
1573 
1574 	std::stringstream		output;
1575 	if (!successful_exit(exec_command(command, output))) {
1576 		throw Error("'git ls-files' failed - is this a Git repository?");
1577 	}
1578 
1579 	// Output looks like (w/o newlines):
1580 	// ? .gitignore\0
1581 	// H 100644 06ec22e5ed0de9280731ef000a10f9c3fbc26338 0     afile\0
1582 
1583 	std::vector<std::string>	files;
1584 	bool				attribute_errors = false;
1585 	bool				unencrypted_blob_errors = false;
1586 	unsigned int			nbr_of_fixed_blobs = 0;
1587 	unsigned int			nbr_of_fix_errors = 0;
1588 
1589 	while (output.peek() != -1) {
1590 		std::string		tag;
1591 		std::string		object_id;
1592 		std::string		filename;
1593 		output >> tag;
1594 		if (tag != "?") {
1595 			std::string	mode;
1596 			std::string	stage;
1597 			output >> mode >> object_id >> stage;
1598 			if (!is_git_file_mode(mode)) {
1599 				continue;
1600 			}
1601 		}
1602 		output >> std::ws;
1603 		std::getline(output, filename, '\0');
1604 
1605 		// TODO: get file attributes en masse for efficiency... unfortunately this requires machine-parseable output from git check-attr to be workable, and this is only supported in Git 1.8.5 and above (released 27 Nov 2013)
1606 		const std::pair<std::string, std::string> file_attrs(get_file_attributes(filename));
1607 
1608 		if (file_attrs.first == "git-crypt" || std::strncmp(file_attrs.first.c_str(), "git-crypt-", 10) == 0) {
1609 			// File is encrypted
1610 			const bool	blob_is_unencrypted = !object_id.empty() && !check_if_blob_is_encrypted(object_id);
1611 
1612 			if (fix_problems && blob_is_unencrypted) {
1613 				if (access(filename.c_str(), F_OK) != 0) {
1614 					std::clog << "Error: " << filename << ": cannot stage encrypted version because not present in working tree - please 'git rm' or 'git checkout' it" << std::endl;
1615 					++nbr_of_fix_errors;
1616 				} else {
1617 					touch_file(filename);
1618 					std::vector<std::string>	git_add_command;
1619 					git_add_command.push_back("/usr/local/bin/git");
1620 					git_add_command.push_back("add");
1621 					git_add_command.push_back("--");
1622 					git_add_command.push_back(filename);
1623 					if (!successful_exit(exec_command(git_add_command))) {
1624 						throw Error("'git-add' failed");
1625 					}
1626 					if (check_if_file_is_encrypted(filename)) {
1627 						std::cout << filename << ": staged encrypted version" << std::endl;
1628 						++nbr_of_fixed_blobs;
1629 					} else {
1630 						std::clog << "Error: " << filename << ": still unencrypted even after staging" << std::endl;
1631 						++nbr_of_fix_errors;
1632 					}
1633 				}
1634 			} else if (!fix_problems && !show_unencrypted_only) {
1635 				// TODO: output the key name used to encrypt this file
1636 				std::cout << "    encrypted: " << filename;
1637 				if (file_attrs.second != file_attrs.first) {
1638 					// but diff filter is not properly set
1639 					std::cout << " *** WARNING: diff=" << file_attrs.first << " attribute not set ***";
1640 					attribute_errors = true;
1641 				}
1642 				if (blob_is_unencrypted) {
1643 					// File not actually encrypted
1644 					std::cout << " *** WARNING: staged/committed version is NOT ENCRYPTED! ***";
1645 					unencrypted_blob_errors = true;
1646 				}
1647 				std::cout << std::endl;
1648 			}
1649 		} else {
1650 			// File not encrypted
1651 			if (!fix_problems && !show_encrypted_only) {
1652 				std::cout << "not encrypted: " << filename << std::endl;
1653 			}
1654 		}
1655 	}
1656 
1657 	int				exit_status = 0;
1658 
1659 	if (attribute_errors) {
1660 		std::cout << std::endl;
1661 		std::cout << "Warning: one or more files has a git-crypt filter attribute but not a" << std::endl;
1662 		std::cout << "corresponding git-crypt diff attribute.  For proper 'git diff' operation" << std::endl;
1663 		std::cout << "you should fix the .gitattributes file to specify the correct diff attribute." << std::endl;
1664 		std::cout << "Consult the git-crypt documentation for help." << std::endl;
1665 		exit_status = 1;
1666 	}
1667 	if (unencrypted_blob_errors) {
1668 		std::cout << std::endl;
1669 		std::cout << "Warning: one or more files is marked for encryption via .gitattributes but" << std::endl;
1670 		std::cout << "was staged and/or committed before the .gitattributes file was in effect." << std::endl;
1671 		std::cout << "Run 'git-crypt status' with the '-f' option to stage an encrypted version." << std::endl;
1672 		exit_status = 1;
1673 	}
1674 	if (nbr_of_fixed_blobs) {
1675 		std::cout << "Staged " << nbr_of_fixed_blobs << " encrypted file" << (nbr_of_fixed_blobs != 1 ? "s" : "") << "." << std::endl;
1676 		std::cout << "Warning: if these files were previously committed, unencrypted versions still exist in the repository's history." << std::endl;
1677 	}
1678 	if (nbr_of_fix_errors) {
1679 		std::cout << "Unable to stage " << nbr_of_fix_errors << " file" << (nbr_of_fix_errors != 1 ? "s" : "") << "." << std::endl;
1680 		exit_status = 1;
1681 	}
1682 
1683 	return exit_status;
1684 }
1685 
1686