15 Commits
0.1 ... 0.3

Author SHA1 Message Date
Andrew Ayer
4d94b44be1 Enhance documentation
Add NEWS file, spin installation instructions into INSTALL file, and add
useful information from the git-crypt website to the README.
2013-04-05 17:00:03 -07:00
Andrew Ayer
2b936c74f1 Escape arguments to filter commands
This will allow both the path to git-crypt and the path to the key file
to contain arbitrary characters, notably spaces.
2013-04-04 18:53:03 -07:00
Andrew Ayer
b10fbcd299 Fix 'git-crypt init' for newer versions of Git
At some point between Git 1.7.1 and Git 1.8.1.3, both 'git reset' and
'git status' stopped noticing that files were modified after their
smudge filter changed.  Consequentially, 'git reset --hard HEAD' would
not decrypt existing encrypted files in the repo.

This commit changes 'git-crypt init' to use 'git checkout -f HEAD
/top/of/repo' instead, which does the job.
2013-04-04 17:43:38 -07:00
Andrew Ayer
e184b39eaa git-crypt init: Ignore untracked files when running git status
Untracked files are not touched by git reset, so git-crypt init
is safe even with untracked files present.

This relies on the -u option to git-status, which was added in Git
1.6.0, which was released in 2008.  Add Git 1.6.0 as a requirement in
the README.
2013-03-07 15:34:23 -08:00
Andrew Ayer
490b7143b1 Update copyright notice to include OpenSSL linking exception 2013-03-05 12:02:49 -08:00
Andrew Ayer
fb2e08d7c2 Add AUTHORS file 2013-02-07 08:08:21 -08:00
Andrew Ayer
5e89e6d4c7 Add 'make install' target 2013-02-07 08:08:21 -08:00
Andrew Ayer
84b4f7ca1a Improve 'git-crypt init' usability
* Correctly check for existence of HEAD (use 'git rev-parse' instead
    of 'git show-ref').  Fixes bug where hard reset might be skipped
    after running 'git init'.
  * Don't require working directory to be clean if HEAD doesn't exist.
    (If HEAD doesn't exist, we won't be hard resetting so the working
    directory doesn't need to be clean.)
  * Overwrite existing git config values (instead of --add'ing them) so
    'git-crypt init' can be idempotent.
  * In the error message for a disrty working directory, advise user to
    commit changes or 'git stash' them.
2013-02-06 16:14:57 -08:00
Andrew Ayer
826f746a3c Fix gitattributes example in README
There should NOT be a colon after the pattern.
2013-02-06 15:45:05 -08:00
Linus G Thiel
60d96ecf80 Include unistd.h for gcc 4.7
In gcc 4.7, some includes were removed. This fixes the build.

Signed-off-by: Andrew Ayer <agwa@andrewayer.name>
2013-02-06 14:52:43 -08:00
Andrew Ayer
3680884767 Restore original umask after running keygen 2013-01-24 22:02:42 -08:00
Andrew Ayer
8b5c3d5c88 Compile with 'c++' instead of 'g++'
We're not relying on any g++-specific features.
2013-01-24 22:00:12 -08:00
Andrew Ayer
b2164be760 Use arpa/inet.h functions instead of endian.h
Even though arpa/inet.h is "networky" and this isn't a network
application, arpa/inet.h is in POSIX whereas endian.h is non-standard.

This should let git-crypt build on Mac OS X.
2013-01-24 21:57:49 -08:00
Andrew Ayer
d3dcc7da64 Set a safe umask before creating temporary files
Although glibc's implementation of mkstemp creates temporary files with
a safe (i.e. 0600) mode, POSIX does not mandate any particular mode.  So
to ensure maximum cross-platform safety, we must set a umask of 0077
before calling mkstemp.
2013-01-03 15:23:35 -08:00
Andrew Ayer
42c365c77f New website URL in README 2012-12-19 09:50:04 -08:00
12 changed files with 260 additions and 60 deletions

1
AUTHORS Normal file
View File

@@ -0,0 +1 @@
Andrew Ayer <agwa@andrewayer.name>

22
INSTALL Normal file
View File

@@ -0,0 +1,22 @@
DEPENDENCIES
To use git-crypt, you need:
* Git 1.6.0 or newer
* OpenSSL
* For decrypted git diff output, Git 1.6.1 or newer
* For decrypted git blame output, Git 1.7.2 or newer
To build git-crypt, you need a C++ compiler and OpenSSL development
headers.
BUILDING GIT-CRYPT
The Makefile is tailored for g++, but should work with other compilers.
$ make
$ cp git-crypt /usr/local/bin/
It doesn't matter where you install the git-crypt binary - choose wherever
is most convenient for you.

View File

@@ -1,6 +1,7 @@
CXX := g++
CXX := c++
CXXFLAGS := -Wall -pedantic -ansi -Wno-long-long -O2
LDFLAGS := -lcrypto
PREFIX := /usr/local
OBJFILES = git-crypt.o commands.o crypto.o util.o
@@ -12,4 +13,7 @@ git-crypt: $(OBJFILES)
clean:
rm -f *.o git-crypt
.PHONY: all clean
install:
install -m 755 git-crypt $(PREFIX)/bin/
.PHONY: all clean install

18
NEWS Normal file
View File

@@ -0,0 +1,18 @@
v0.3 (2013-04-05)
* Fix 'git-crypt init' on newer versions of Git. Previously,
encrypted files were not being automatically decrypted after
running 'git-crypt init' with recent versions of Git.
* Allow 'git-crypt init' to be run even if the working tree contains
untracked files.
* 'git-crypt init' now properly escapes arguments to the filter
commands it configures, allowing both the path to git-crypt and the
path to the key file to contain arbitrary characters such as spaces.
v0.2 (2013-01-25)
* Numerous improvements to 'git-crypt init' usability.
* Fix gitattributes example in README: the old example showed a colon
after the filename where there shouldn't be one.
* Various build fixes and improvements.
v0.1 (2012-11-29)
* Initial release.

77
README
View File

@@ -11,29 +11,12 @@ repository as your code, without requiring you to lock down your entire
repository.
git-crypt was written by Andrew Ayer <agwa at andrewayer dot name>.
For more information, see <http://www.andrewayer.name/projects/git-crypt>.
For more information, see <http://www.agwa.name/projects/git-crypt>.
BUILDING GIT-CRYPT
See below for dependencies. The Makefile is tailored for g++. If you
have a different compiler, edit Makefile and change the CXX and CFLAGS
variables. This will be made easier in a future release.
$ make
$ cp git-crypt /usr/local/bin/
It doesn't matter where you install the git-crypt binary - choose wherever
is most convenient for you.
DEPENDENCIES
To use git-crypt, you need:
* OpenSSL
* For decrypted git diff output, Git 1.6.1 or later
* For decrypted git blame output, Git 1.7.2 or later
See the INSTALL file.
USING GIT-CRYPT
@@ -49,8 +32,8 @@ Configure a repository to use encryption:
Specify files to encrypt by creating a .gitattributes file:
secretfile: filter=git-crypt diff=git-crypt
*.key: filter=git-crypt diff=git-crypt
secretfile filter=git-crypt diff=git-crypt
*.key filter=git-crypt diff=git-crypt
Like a .gitignore file, it can match wildcards and should be checked
into the repository. Make sure you don't accidentally encrypt the
@@ -64,3 +47,55 @@ Cloning a repository with encrypted files:
That's all you need to do - after running git-crypt init, you can use
git normally - encryption and decryption happen transparently.
CURRENT STATUS
The latest version of git-crypt is 0.3, released on 2013-04-05.
git-crypt is not yet feature complete and the user experience is rough
in places. There may also be compatibility-breaking changes introduced
before version 1.0. That said, git-crypt is reliable and secure and
used to protect content in real world repositories.
Development on git-crypt is currently focused on improving the user
experience, especially around setting up repositories. There are also
plans to add additional key management schemes, such as passphrase-derived
keys and keys encrypted with PGP.
SECURITY
git-crypt is more secure that 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 is provably
semantically secure under deterministic chosen-plaintext attack.
That means that although the encryption is deterministic (which is
required so git can distinguish when a file has and hasn't changed),
it leaks no information beyond whether two files are identical or not.
Other proposals for transparent git encryption use ECB or CBC with no
IV. These systems are not semantically secure and leak information.
The AES key is stored unencrypted on disk. The user is responsible for
protecting it and ensuring it's safely distributed only to authorized
people. A future version of git-crypt may support encrypting the key
with a passphrase.
LIMITATIONS
git-crypt is not designed to encrypt an entire repository. Not only does
that defeat the aim of git-crypt, which is the ability to selectively
encrypt files and share the repository with less-trusted developers, there
are probably better, more efficient ways to encrypt an entire repository,
such as by storing it on an encrypted filesystem. Also note that
git-crypt is somewhat of an abuse of git's smudge, clean, and textconv
features. Junio Hamano, git's maintainer, has said not to do this
<http://thread.gmane.org/gmane.comp.version-control.git/113124/focus=113221>,
though his main objection ("making a pair of similar 'smudged' contents
totally dissimilar in their 'clean' counterparts.") does not apply here
since git-crypt uses deterministic encryption.
git-crypt does not itself provide any authentication. It assumes that
either the master copy of your repository is stored securely, or that
you are using git's existing facilities to ensure integrity (signed tags,
remembering commit hashes, etc.).

View File

@@ -15,6 +15,17 @@
*
* You should have received a copy of the GNU General Public License
* along with git-crypt. If not, see <http://www.gnu.org/licenses/>.
*
* 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 "commands.hpp"
@@ -22,10 +33,12 @@
#include "util.hpp"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdint.h>
#include <algorithm>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <cstddef>
#include <cstring>
@@ -179,55 +192,64 @@ void init (const char* argv0, const char* keyfile)
perror(keyfile);
std::exit(1);
}
// 0. Check to see if HEAD exists. See below why we do this.
bool head_exists = system("git rev-parse HEAD >/dev/null 2>/dev/null") == 0;
// 1. Make sure working directory is clean
int status;
std::string status_output;
status = exec_command("git status --porcelain", status_output);
// 1. Make sure working directory is clean (ignoring untracked files)
// We do this because we run 'git checkout -f HEAD' later and we don't
// want the user to lose any changes. 'git checkout -f HEAD' doesn't touch
// untracked files so it's safe to ignore those.
int status;
std::stringstream status_output;
status = exec_command("git status -uno --porcelain", status_output);
if (status != 0) {
std::clog << "git status failed - is this a git repository?\n";
std::exit(1);
} else if (!status_output.empty()) {
} else if (status_output.peek() != -1 && head_exists) {
// We only care that the working directory is dirty if HEAD exists.
// If HEAD doesn't exist, we won't be resetting to it (see below) so
// it doesn't matter that the working directory is dirty.
std::clog << "Working directory not clean.\n";
std::clog << "Please commit your changes or 'git stash' them before setting up git-crypt.\n";
std::exit(1);
}
// 2. Determine the path to the top of the repository. We pass this as the argument
// to 'git checkout' below. (Determine the path now so in case it fails we haven't already
// mucked with the git config.)
std::stringstream cdup_output;
if (exec_command("git rev-parse --show-cdup", cdup_output) != 0) {
std::clog << "git rev-parse --show-cdup failed\n";
std::exit(1);
}
// 3. Add config options to git
std::string git_crypt_path(std::strchr(argv0, '/') ? resolve_path(argv0) : argv0);
std::string keyfile_path(resolve_path(keyfile));
// 2. Add config options to git
// git config --add filter.git-crypt.smudge "git-crypt smudge /path/to/key"
std::string command("git config --add filter.git-crypt.smudge \"");
command += git_crypt_path;
command += " smudge ";
command += keyfile_path;
command += "\"";
// git config filter.git-crypt.smudge "git-crypt smudge /path/to/key"
std::string command("git config filter.git-crypt.smudge ");
command += escape_shell_arg(escape_shell_arg(git_crypt_path) + " smudge " + escape_shell_arg(keyfile_path));
if (system(command.c_str()) != 0) {
std::clog << "git config failed\n";
std::exit(1);
}
// git config --add filter.git-crypt.clean "git-crypt clean /path/to/key"
command = "git config --add filter.git-crypt.clean \"";
command += git_crypt_path;
command += " clean ";
command += keyfile_path;
command += "\"";
// git config filter.git-crypt.clean "git-crypt clean /path/to/key"
command = "git config filter.git-crypt.clean ";
command += escape_shell_arg(escape_shell_arg(git_crypt_path) + " clean " + escape_shell_arg(keyfile_path));
if (system(command.c_str()) != 0) {
std::clog << "git config failed\n";
std::exit(1);
}
// git config --add diff.git-crypt.textconv "git-crypt diff /path/to/key"
command = "git config --add diff.git-crypt.textconv \"";
command += git_crypt_path;
command += " diff ";
command += keyfile_path;
command += "\"";
// git config diff.git-crypt.textconv "git-crypt diff /path/to/key"
command = "git config diff.git-crypt.textconv ";
command += escape_shell_arg(escape_shell_arg(git_crypt_path) + " diff " + escape_shell_arg(keyfile_path));
if (system(command.c_str()) != 0) {
std::clog << "git config failed\n";
@@ -235,24 +257,38 @@ void init (const char* argv0, const char* keyfile)
}
// 3. Do a hard reset so any files that were previously checked out encrypted
// 4. Do a force checkout so any files that were previously checked out encrypted
// will now be checked out decrypted.
// If HEAD doesn't exist (perhaps because this repo doesn't have any files yet)
// just skip the reset.
if (system("! git show-ref HEAD > /dev/null || git reset --hard HEAD") != 0) {
std::clog << "git reset --hard failed\n";
std::exit(1);
// just skip the checkout.
if (head_exists) {
std::string path_to_top;
std::getline(cdup_output, path_to_top);
command = "git checkout -f HEAD -- ";
if (path_to_top.empty()) {
command += ".";
} else {
command += escape_shell_arg(path_to_top);
}
if (system(command.c_str()) != 0) {
std::clog << "git checkout failed\n";
std::clog << "git-crypt has been set up but existing encrypted files have not been decrypted\n";
std::exit(1);
}
}
}
void keygen (const char* keyfile)
{
umask(0077); // make sure key file is protected
mode_t old_umask = umask(0077); // make sure key file is protected
std::ofstream keyout(keyfile);
if (!keyout) {
perror(keyfile);
std::exit(1);
}
umask(old_umask);
std::ifstream randin("/dev/random");
if (!randin) {
perror("/dev/random");

View File

@@ -15,6 +15,17 @@
*
* You should have received a copy of the GNU General Public License
* along with git-crypt. If not, see <http://www.gnu.org/licenses/>.
*
* 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.
*/
#ifndef _COMMANDS_H

View File

@@ -15,6 +15,17 @@
*
* You should have received a copy of the GNU General Public License
* along with git-crypt. If not, see <http://www.gnu.org/licenses/>.
*
* 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.
*/
#define _BSD_SOURCE
@@ -27,7 +38,7 @@
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <endian.h>
#include <arpa/inet.h>
void load_keys (const char* filepath, keys_t* keys)
{
@@ -71,7 +82,7 @@ void aes_ctr_state::process (const AES_KEY* key, const uint8_t* in, uint8_t* out
// first 12 bytes - nonce
// last 4 bytes - block number (sequentially increasing with each block)
uint8_t ctr[16];
uint32_t blockno = htole32(byte_counter / 16);
uint32_t blockno = htonl(byte_counter / 16);
memcpy(ctr, nonce, 12);
memcpy(ctr + 12, &blockno, 4);
AES_encrypt(ctr, otp, key);

View File

@@ -15,6 +15,17 @@
*
* You should have received a copy of the GNU General Public License
* along with git-crypt. If not, see <http://www.gnu.org/licenses/>.
*
* 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.
*/
#ifndef _CRYPTO_H

View File

@@ -15,6 +15,17 @@
*
* You should have received a copy of the GNU General Public License
* along with git-crypt. If not, see <http://www.gnu.org/licenses/>.
*
* 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 "commands.hpp"

View File

@@ -15,6 +15,17 @@
*
* You should have received a copy of the GNU General Public License
* along with git-crypt. If not, see <http://www.gnu.org/licenses/>.
*
* 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 "util.hpp"
@@ -24,11 +35,12 @@
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fstream>
int exec_command (const char* command, std::string& output)
int exec_command (const char* command, std::ostream& output)
{
int pipefd[2];
if (pipe(pipefd) == -1) {
@@ -53,7 +65,7 @@ int exec_command (const char* command, std::string& output)
char buffer[1024];
ssize_t bytes_read;
while ((bytes_read = read(pipefd[0], buffer, sizeof(buffer))) > 0) {
output.append(buffer, bytes_read);
output.write(buffer, bytes_read);
}
close(pipefd[0]);
int status = 0;
@@ -82,11 +94,13 @@ void open_tempfile (std::fstream& file, std::ios_base::openmode mode)
char* path = new char[tmpdir_len + 18];
strcpy(path, tmpdir);
strcpy(path + tmpdir_len, "/git-crypt.XXXXXX");
mode_t old_umask = umask(0077);
int fd = mkstemp(path);
if (fd == -1) {
perror("mkstemp");
std::exit(9);
}
umask(old_umask);
file.open(path, mode);
if (!file.is_open()) {
perror("open");
@@ -98,3 +112,17 @@ void open_tempfile (std::fstream& file, std::ios_base::openmode mode)
delete[] path;
}
std::string escape_shell_arg (const std::string& str)
{
std::string new_str;
new_str.push_back('"');
for (std::string::const_iterator it(str.begin()); it != str.end(); ++it) {
if (*it == '"' || *it == '\\' || *it == '$' || *it == '`') {
new_str.push_back('\\');
}
new_str.push_back(*it);
}
new_str.push_back('"');
return new_str;
}

View File

@@ -15,6 +15,17 @@
*
* You should have received a copy of the GNU General Public License
* along with git-crypt. If not, see <http://www.gnu.org/licenses/>.
*
* 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.
*/
#ifndef _UTIL_H
@@ -24,9 +35,10 @@
#include <ios>
#include <iosfwd>
int exec_command (const char* command, std::string& output);
int exec_command (const char* command, std::ostream& output);
std::string resolve_path (const char* path);
void open_tempfile (std::fstream&, std::ios_base::openmode);
std::string escape_shell_arg (const std::string&);
#endif