149 Commits

Author SHA1 Message Date
Andrew Ayer
0377659d18 debian packaging: bump standards version (no changes needed) 2016-11-23 12:52:01 -08:00
Andrew Ayer
381b3ca406 debian packaging: build against OpenSSL 1.0.x
Longer term, I will port git-crypt to OpenSSL 1.1.x.  This suffices for
now - git-crypt wouldn't have benefited from any of the 1.1 features
anyways.
2016-11-23 12:51:23 -08:00
Andrew Ayer
f741d1aded Update Debian packaging for 0.5.0-1 2015-05-30 20:22:29 -07:00
Andrew Ayer
6bf8cb262c Add debian/watch file 2015-05-30 20:22:19 -07:00
Andrew Ayer
3513c22082 Merge tag '0.5.0' into debian
git-crypt 0.5.0

git-crypt 0.5.0 brings a substantial performance boost to 'git-crypt
unlock' and 'git-crypt lock' under Git 1.8.5 and higher.  The improvement
should be particularly noticeable in repositories with lots of files.

In addition, there are some minor bug fixes and usability improvements.
'git-crypt gpg-add-user' now has a --trusted option which you can use
to force the addition of a user even if GPG doesn't trust the key.

Finally, git-crypt now has a man page!  To install it, pass ENABLE_MAN=yes
to 'make' and 'make install' (this will be the default once I iron out
the build system).

See the NEWS file, or the Git commit history, for a more detailed list
of changes.
2015-05-30 20:01:44 -07:00
Andrew Ayer
bc7e55b68f Prepare 0.5.0 release 2015-05-30 19:53:04 -07:00
Andrew Ayer
f56911726a Makefile: refine man page rules
Rename HAS_DOCBOOK option to ENABLE_MAN.

Allow xsltproc to fetch the Docbook stylesheet from the Internet if it's
not installed locally.  This will hopefully make it easier for folks
to build the man page.
2015-05-30 19:52:29 -07:00
Andrew Ayer
9bbd39c491 Add instructions for building man page to INSTALL 2015-05-30 19:09:17 -07:00
Andrew Ayer
5dc55c3b2a Makefile: refine man page rules 2015-05-30 19:09:01 -07:00
Andrew Ayer
ba250b87ad Add copyright notice to Makefile 2015-05-30 19:09:01 -07:00
Andrew Ayer
83b58eafa7 Overhaul Makefile
Support building the man page, but only if HAS_DOCBOOK variable set to "yes"
2015-05-30 19:09:01 -07:00
Andrew Ayer
b4569ae6c1 Add man page 2015-05-28 21:24:16 -07:00
Andrew Ayer
0a2e633d7f Recommend Git 1.8.5 or newer for best performance
Since older versions of Git have to spawn a separate
check-attr process for every file in the repository,
which is slow.
2015-05-25 15:26:32 -07:00
Andrew Ayer
ca98d1aed1 Minor README tweaks 2015-05-25 15:25:44 -07:00
Andrew Ayer
4acb4205eb git_version: cache the Git version
(to avoid repeated invocations of `git version`)
2015-05-24 18:58:13 -07:00
Andrew Ayer
1988ee3819 Speed up lock/unlock by using single git check-attr process
Previously, lock/unlock needed to spawn a separate `git check-attr`
process for every single file in the repository (whether encrypted
or not).  This was extremely inefficient, especially on Windows.

Now, git-crypt spawns a single `git check-attr` process and communicates
with it over stdin.  In a repository with thousands of files, this
results in a speedup of nearly 100x.

This relies on the --stdin and -z options to `git check-attr`, which
were only added in Git 1.8.5 (released 27 Nov 2013).  With older versions
of Git, git-crypt falls back to the old and slower code.
2015-05-24 18:55:58 -07:00
Andrew Ayer
44f70e6b48 Add Coprocess class
It provides a convenient way to spawn a process and read from/write to
its stdin/stdout.
2015-05-24 18:54:11 -07:00
Andrew Ayer
3db6271492 Re-license parse_options under X11 license 2015-05-17 14:38:12 -07:00
Andrew Ayer
c279a6a20a Add helpers to faciliate Git version comparison
This will be useful as we start to gate code on the version of Git that's installed.
2015-05-16 21:10:44 -07:00
Andrew Ayer
439bcd852d Write a helper function to get the version of Git
This will be useful as we start to gate code on the version of Git that's installed.

A lot of code out in the wild seems to assume that the output of `git version`
is "git version $VERSION", so I'm assuming it's safe for git-crypt to rely
on that too.
2015-05-14 22:23:21 -07:00
Andrew Ayer
7880b30e8c Update INSTALL file
Streamline the instructions a bit.  Mention the Debian/Ubuntu and
RHEL/CentOS packages for each dependency.
2015-04-24 20:53:19 -07:00
Andrew Ayer
3104508adf Add --trusted option to gpg-add-user
If this option is specified, then the GPG users are added even
if their keys are not trusted by GPG.

In addition, if a full fingerprint, prefixed by 0x, is specified,
it is assumed to be trusted, regardless of its trust level in the
GPG trustdb.
2015-03-31 20:24:13 -07:00
Andrew Ayer
ad71c7ffae FIx GPG key lookup with with-fingerprint enabled in gpg.conf
When the with-fingerprint option is enabled, the gpg command invoked by
git-crypt to look up a GPG user ID returns a fingerprint for both primary
keys and sub-keys.  Previously, this misled git-crypt into thinking that
the user ID matched more than one public key.  Now, git-crypt ignores
fingerprints for sub-keys.
2015-03-31 19:41:19 -07:00
Andrew Ayer
3ce5c83b2d Wrap long lines in README 2015-03-17 15:08:58 -07:00
Andrew Ayer
6d6e96f82b Clarify security section in README 2015-03-17 15:08:35 -07:00
Andrew Ayer
c3ab393f17 Add notes to the README about symlinks, sub-modules 2015-03-15 13:55:18 -07:00
Andrew Ayer
6abf9c6956 Ignore non-files when running git ls-files
Non-files (symlinks and gitlinks (used by sub-modules)) cannot be
encrypted, so we shouldn't try messing with them.  This fixes `git-crypt
status` when used on a repository with sub-modules or symlinks when the
path to the sub-module or symlink has the git-crypt attribute (which
can happen inadvertently when using wildcards in .gitattributes).
2015-03-15 13:34:31 -07:00
Andrew Ayer
ede1461563 Makefile: create destination directories in make install 2015-03-10 08:29:31 -07:00
Andrew Ayer
1b3f13643c Use opendir/readdir instead of scandir
scandir is a relatively recent addition to POSIX and is not available on
older versions of Mac OS X.  opendir/readdir should be more portable.
2015-02-26 22:51:40 -08:00
Andrew Ayer
8c130d3a00 Fix syntax error in commands.cpp 2015-02-09 11:42:22 -08:00
Andrew Ayer
012d78e1fe Add "do not edit" comment to .git-crypt/.gitattributes file 2015-02-09 11:37:50 -08:00
Andrew Ayer
99d4408f9e README: Add note about GitHub for Mac breakage 2015-02-09 11:37:19 -08:00
Andrew Ayer
c2de1e2194 Add --force option to 'git-crypt lock'
It will force a lock even if working directory is unclean.  Useful
for deconfiguring git-crypt if you've accidentally unlocked with the
wrong key or gotten into a similarly sticky situation.
2015-02-07 13:27:58 -08:00
Andrew Ayer
85635ae0b1 touch_file, remove_file: ignore non-existent files 2015-02-07 13:22:30 -08:00
Andrew Ayer
18d3cfeca9 Remove some dead code 2015-02-07 12:57:52 -08:00
Andrew Ayer
0c8dae2375 Only run git_deconfig if Git configuration exists
This will let us run 'git lock' even if no filters are configured.

This logic is more complicated than I would like because running
'git config --remove-section' on a non-existent section results in
a noisy error (with text printed to stderr and an exit code of 128)
instead of a quiet error like the other 'git config' commands.
2015-02-07 12:54:07 -08:00
Andrew Ayer
fc583c7d4f Add helper to get exit status of command 2015-02-07 12:43:17 -08:00
Andrew Ayer
c850d65242 Change "unconfigure" to "deconfigure" 2015-02-07 12:15:53 -08:00
Andrew Ayer
f5d36f0008 Fix wording of 'git-crypt lock' error message.
Thanks to Elliot Saba.
2015-02-07 08:52:07 -08:00
Andrew Ayer
f3890bed32 Update debian/changelog for 0.4.2-1~agwa1 2015-01-31 19:44:08 -08:00
Andrew Ayer
78fb6ab052 debian packaging: install additional docs 2015-01-31 19:42:45 -08:00
Andrew Ayer
1fc9664036 Merge tag '0.4.2' into debian
git-crypt 0.4.2

git-crypt 0.4.2 is a bugfix-only release that fixes the unlock (and lock)
commands under Git 2.2.2 and newer.  Due to an optimization introduced in
Git 2.2.2, 'git-crypt unlock' would not decrypt files in the working tree.

In addition, this version of git-crypt officially drops support for
versions of Git older than 1.7.2.
2015-01-31 19:41:21 -08:00
Andrew Ayer
1ca8f89602 Prepare for 0.4.2 release 2015-01-31 19:20:05 -08:00
Andrew Ayer
2fa2cffba6 Freshen Debian packaging 2015-01-27 21:36:55 -08:00
Andrew Ayer
5fd36a7ac5 Increase minimum supported Git version to 1.7.2
Previously, git-crypt claimed to support Git as old as 1.6.0 (albeit
with degraded operation).  However, this has not been true for some time,
since Git 1.6.0 does not support the --porcelain option to `git status`.

Since Git 1.7.2 was the first version of Git to support filters with
`git blame`, was released almost five years ago (in July 2010), and is
even in Debian Squeeze, it seems like a good minimum version to require.
2015-01-27 21:26:51 -08:00
Andrew Ayer
d5670c9552 Force Git to check out files by touching their mtimes
Starting with Git 2.2.2, `git checkout -f HEAD` no longer checks out
files if their mtimes haven't changed.  This causes files to remain
encrypted in the work tree after running `git-crypt unlock`, and to
remain decrypted after running `git-crypt lock`'.

To fix this, git-crypt now figures out what files are encrypted (by
checking `git check-attr` on every file output by `git ls-files`),
touches those files, and then runs `git checkout` on them.
2015-01-27 21:15:07 -08:00
Andrew Ayer
2d2053296f Fix placement of quotes in an error message 2015-01-27 21:06:29 -08:00
Andrew Ayer
216aa27009 Add helper function to get attribute name for a given key 2015-01-27 21:04:58 -08:00
Andrew Ayer
02c52ab21a Disable message about unimplemented ls-gpg-users command 2015-01-27 21:04:22 -08:00
Andrew Ayer
849401d733 Update for git-crypt 0.4.1 2015-01-07 20:23:28 -08:00
Andrew Ayer
12881f65fd Add 'git-crypt version' command 2015-01-07 20:23:07 -08:00
Wael M. Nasreddine
280bd43ac7 Makefile: The install target should depend git-crypt.
Signed-off-by: Andrew Ayer <agwa@andrewayer.name>
2015-01-07 20:06:45 -08:00
Andrew Ayer
b7c608da25 Add .gitattributes file to .git-crypt dir to prevent encryption
Previously, if you had a .gitattributes file in the root of your
repository that matched `*`, the files under .git-crypt would also be
encrypted, rendering the repository un-decryptable, unless you explicitly
excluded the .git-crypt directory, which was easy to overlook.

Now, `git-crypt add-gpg-user` automatically adds a .gitattributes file
to the .git-crypt directory to prevent its encryption.

IMPORTANT: If you are currently using GPG mode to encrypt an entire
repository, it is strongly advised that you upgrade git-crypt and then
do the following to ensure that the files inside .git-crypt are stored
properly:

 1. Remove existing key files: `rm .git-crypt/keys/*/0/*`
 2. Re-add GPG user(s): `git-crypt add-gpg-user GPG_USER_ID ...`
2015-01-02 10:35:57 -08:00
Andrew Ayer
9cb1ad3c33 Add some helpers for getting paths to state directories 2015-01-02 10:30:47 -08:00
Andrew Ayer
1b1715b5ec README: use https URLs for mailing lists 2014-11-26 09:42:10 -08:00
Andrew Ayer
2b0cc1b5ac Update README, NEWS, write release notes for 0.4 2014-11-16 17:29:17 -08:00
Andrew Ayer
3d53bce1a8 Add .gitattributes file to ignore .git files when creating archive 2014-11-16 17:29:09 -08:00
Andrew Ayer
be237fe27d Fix formatting in NEWS.md 2014-11-16 17:25:02 -08:00
Andrew Ayer
6520746bce Update 'git-crypt help' message
Documented new arguments to 'git-crypt migrate-key' and adjusted spacing.
2014-11-15 20:31:01 -08:00
Andrew Ayer
3bf7d8e512 migrate-key: take separate arguments for old key and new key
I don't want to encourage people to overwrite their old keys
until they've successfully unlocked their repository with the
migrated key.
2014-11-15 20:30:35 -08:00
Andrew Ayer
bd262f6126 Add documentation for multiple keys 2014-11-15 18:06:36 -08:00
Andrew Ayer
cf990dc9df Minor formatting updates to README 2014-11-15 18:06:23 -08:00
Andrew Ayer
e70d067b48 Rearrange a couple paragraphs in the README 2014-09-21 12:00:31 -07:00
Andrew Ayer
4796a1e288 Clarify some wording in README 2014-09-21 12:00:31 -07:00
Andrew Ayer
e4d1091e97 Rename add-gpg-key command, etc. to add-gpg-user, etc.
While writing the documention, I found that "GPG user" was less confusing
terminology than "GPG key," since you aren't really adding a "key"
to git-crypt, and git-crypt already uses "key" to refer to other concepts
(cf. the -k/--key-name options).
2014-09-21 12:00:31 -07:00
Andrew Ayer
04906c5355 Write a usage message for every command
You can run 'git-crypt help COMMAND' to see it.
2014-09-21 12:00:31 -07:00
Andrew Ayer
24fff1ce6f Document experimental Windows support 2014-09-21 12:00:31 -07:00
Andrew Ayer
acc3d2ecb3 Fix capitalization of git-crypt in INSTALL.md 2014-09-21 12:00:31 -07:00
Andrew Ayer
9e340b510d Document GPG mode in README 2014-09-21 12:00:31 -07:00
Andrew Ayer
0538d111fc Usage message: refer to gpg key argument as "user ID" not "key ID"
This is the terminology that the gpg man page uses.
2014-09-21 12:00:31 -07:00
Andrew Ayer
746bb5def3 Remove unlink_internal_key function
I think it's simpler this way.
2014-09-21 12:00:31 -07:00
Andrew Ayer
16c4a827c0 Error message if you try to lock repository that's not locked 2014-09-21 12:00:31 -07:00
Andrew Ayer
3799a23aa7 Add missing argument when throwing System_error 2014-09-21 12:00:31 -07:00
Andrew Ayer
e9e90fc873 For consistency, always use NULL internally to represent the default key 2014-09-21 12:00:31 -07:00
Andrew Ayer
88e8e3a265 Display error if both -k and -a options passed to git-crypt lock 2014-09-21 12:00:31 -07:00
Andrew Ayer
690dba2f14 Add multi-platform remove_file helper
And use it for deleting internal keys
2014-09-21 12:00:31 -07:00
Andrew Ayer
70879eaf57 Tweak git-crypt usage message
* Change the wording for 'git-crypt lock'.
 * Move 'git-crypt lock' to 'Common commands' section since it's
   common to both GPG and symmetric mode.
 * Reduce whitespace in the output so it fits in 80 characters.
2014-09-21 12:00:31 -07:00
Andrew Ayer
42aa7db245 Credit Michael Schout in THANKS file 2014-09-21 12:00:31 -07:00
Michael Schout
3726df181d add support for "git-crypt lock"
This does the reverse of what git-crypt unlock does:
    - unconfigures the git filters
    - forcibly checks out HEAD version

Usage:
    git crypt lock                  locks repo using the "default" key

    git crypt lock -k NAME          locks the repo, using unlocked key named NAME
    git crypt lock --key-name=NAME

    git crypt lock -a               locks the repo, removing ALL unlocked keys
    git crypt lock --all

Result is that you can now decrypt and then revert back to encrypted
form of files and vice versa.

Modified-by: Andrew Ayer <agwa@andrewayer.name>

  * Make argv argument to lock() const.
  * Minor whitespace/style fixes to conform to project conventions.

Signed-off-by: Andrew Ayer <agwa@andrewayer.name>
2014-09-21 12:00:31 -07:00
Andrew Ayer
316e194f84 README: document problems with Atlassian SourceTree 2014-09-21 12:00:31 -07:00
Andrew Ayer
8460d00bbf README: add notes about gitattributes 2014-09-21 12:00:31 -07:00
Andrew Ayer
4495af1274 README: update security and limitations sections 2014-09-21 12:00:31 -07:00
Andrew Ayer
9c190a5a89 Add CONTRIBUTING and THANKS files 2014-09-09 09:00:35 -07:00
Andrew Ayer
9f59cc23b9 Merge branch 'revamp' into 'master'
Conflicts:
	Makefile
2014-09-06 19:32:55 -07:00
Andrew Ayer
725f442ce4 Remove a TODO comment
I've decided not to do it
2014-09-06 17:25:31 -07:00
Andrew Ayer
adaea41d4e add-gpg-key: add -n/--no-commit option to inhibit committing 2014-09-06 17:25:05 -07:00
Andrew Ayer
e37566f180 status: properly detect files encrypted with alternative key names 2014-09-06 15:43:00 -07:00
Andrew Ayer
10622f6dcc Raise an error if legacy key file has trailing data 2014-09-06 14:59:16 -07:00
Andrew Ayer
f50feec2dd Display helpful information when smudge detects an unencrypted file 2014-09-06 14:59:12 -07:00
Andrew Ayer
8b159b543f Avoid possible undefined behavior with empty std::vector
In particular, &bytes[0] is undefined if bytes is empty.
2014-08-18 14:12:34 -07:00
Andrew Ayer
b07f49b9b3 smudge: if file is not encrypted, just copy through clear text
Since Git consults the checked-out .gitattributes instead of the
.gitattributes in effect at the time the file was committed, Git
may invoke the smudge filter on old versions of a file that were
committed without encryption.
2014-08-06 19:04:17 -07:00
Andrew Ayer
07231c1630 Set 'required' option on Git filter to true
This signals to Git that the filter must complete successfully for the
content to be usable.
2014-08-02 21:34:17 -07:00
Andrew Ayer
da25322dbc Remove stubs for profile support
Multiple key support provides the functionality I was planning to provide
with profiles.
2014-08-02 21:23:52 -07:00
Andrew Ayer
47e810d592 Write and use create_protected_file() helper
Instead of using umask to ensure sensitive files are created with
restrictive permissions, git-crypt now does:

	create_protected_file(filename);
	std::ofstream out(filename);
	// ...

create_protected_file can have different Unix and Windows implementations.
create_protected_file should be easier to implement on Windows than a
umask equivalent, and this pattern keeps the amount of platform-specific
code to a minimum and avoids #ifdefs.
2014-08-02 21:18:28 -07:00
Andrew Ayer
01f152b746 Check HMAC in smudge and diff commands
Git-crypt's position has always been that authentication is best left
to Git, since 1) Git provides immutable history based on SHA-1 hashes
as well as GPG-signed commits and tags, and 2) git-crypt can't be used
safely anyways unless the overall integrity of your repository is assured.

But, since git-crypt already has easy access to a (truncated) HMAC of the
file when decrypting, there's really no reason why git-crypt shouldn't
just verify it and provide an additional layer of protection.
2014-08-02 21:17:17 -07:00
Andrew Ayer
9e791d97ee Factor out some common code into a helper function 2014-07-23 19:55:50 -07:00
Andrew Ayer
477983f4bc Ensure memsets of sensitive memory aren't optimized away 2014-07-23 19:32:30 -07:00
Andrew Ayer
23ff272f7d Simplify CTR code 2014-07-23 19:23:39 -07:00
Andrew Ayer
8de40f40b3 Wipe AES key from memory after using it 2014-07-23 19:23:39 -07:00
Andrew Ayer
22bae167b0 Make Aes_impl and Hmac_impl private member classes 2014-07-23 19:23:39 -07:00
Andrew Ayer
0210fd7541 Use auto_ptr instead of explicit memory management 2014-07-23 19:23:39 -07:00
Jon Sailor
66a2266968 Pull out openssl code into separate crypto-openssl.cpp file
This will allow the use of different crypto libraries in the future.

Modified-by: Andrew Ayer <agwa@andrewayer.name>

  * Don't include openssl/err.h from git-crypt.cpp
  * Fix whitespace and other style to conform to project conventions
  * Remove unnecessary operators from Aes_ctr_encryptor
  * Rename crypto_init to init_crypto, for consistency with init_std_streams()
2014-07-23 19:22:48 -07:00
Andrew Ayer
f6e3b63a93 Makefile: avoid use of non-standard $^ 2014-07-07 21:49:12 -07:00
Andrew Ayer
d417f97f8e Make argv arrays const 2014-07-05 14:22:55 -07:00
Andrew Ayer
3d0e7570ed Update usage message 2014-07-05 11:46:58 -07:00
Andrew Ayer
3fe85bc928 Rename add-collab, etc. to add-gpg-key, etc.
Since GPG support might be used by a single user and not necessarily
among collaborators.
2014-07-05 11:46:51 -07:00
Andrew Ayer
2ba7f0e374 unlock: decrypt all possible keys when using GPG
It's no longer necessary to specify the -k option to unlock when
using GPG.  unlock will automatically decrypt all keys which the user
can access.
2014-07-02 22:12:18 -07:00
Andrew Ayer
f03d972937 Add get_directory_contents utility function 2014-07-02 22:10:09 -07:00
Andrew Ayer
4af0a0cfc1 Avoid unsafe integer signedness conversions when loading key file 2014-07-02 22:09:13 -07:00
Andrew Ayer
3511033f7f Make key files extensible, store key name in key file
Storing the key name in the key file makes it unnecessary to pass the
--key-name option to git-crypt unlock.

This breaks compatibility with post-revamp keys.  On the plus side,
keys are now extensible so in the future it will be easier to make
changes to the format without breaking compatibility.
2014-06-29 22:10:04 -07:00
Andrew Ayer
3c8273cd4b Add .gpg filename extension to in-repo encrypted keys
This will help distinguish keys encrypted with GPG from keys encrypted by
other means.  (For example, a future version of git-crypt might support
passphrase-encrypted keys.)
2014-06-29 16:14:16 -07:00
Andrew Ayer
1afa71183e Initial implementation of multiple key support
The init, export-key, add-collab, and unlock commands now
take an optional -k (equivalently, --key-name) option to
specify an alternative key.  Files can be encrypted with
the alternative key by specifying the git-crypt-KEYNAME filter
in .gitattributes.  Alternative key support makes it possible
to encrypt different files with different keys.

Note that the -k option to unlock is temporary.  Unlock
will eventually auto-detect the name of the key you're
unlocking, either by looking in the symmetric key file,
or by scanning the .git-crypt/keys directory.

Note that the layout of the .git/git-crypt and .git-crypt
directories has changed as follows:

 * .git/git-crypt/key is now .git/git-crypt/keys/default
 * .git-crypt/keys is now .git-crypt/keys/default
2014-06-29 16:00:27 -07:00
Andrew Ayer
bec9e7f318 Add parse_options helper for parsing cmd line args 2014-06-29 13:49:10 -07:00
Andrew Ayer
f3390ff7ff Initial implementation of 'git-crypt status'
'git-crypt status' tells you which files are and aren't encrypted and
detects other problems with your git-crypt setup.

'git-crypt status -f' can be used to re-stage files that were incorrectly
staged unencrypted.

The UI needs work, and it needs to also output the overall repository
status (such as, is git-crypt even configured yet?), but this is a
good start.
2014-06-26 23:03:30 -07:00
Andrew Ayer
e6bb66b93a Add touch_file() utility function 2014-06-26 23:03:30 -07:00
Andrew Ayer
38b43a4415 Make 'add-collab' safe with filenames starting with '-' 2014-06-26 23:03:30 -07:00
Andrew Ayer
20c0b18fa2 Add a minor TODO comment 2014-06-26 23:03:30 -07:00
Andrew Ayer
188a8c15fc Minor pedantic changes to I/O code
Don't bother checking for !in because the gcount() check is quite
sufficient and having both checks was confusing.

Make some variables const because they can be.
2014-06-26 23:03:30 -07:00
Cyril Cleaud
df2b472cd9 Add umask and rename compatibility wrappers for Windows
umask() doesn't exist on Windows and is thus a no-op.

rename() only works if the destination doesn't already exist,
so we must unlink before renaming.
2014-06-26 23:03:30 -07:00
Andrew Ayer
dcea03f0d7 Finish implementing Windows utility functions
This completes Windows support, except for the build system and
documentation.
2014-06-15 12:07:22 -07:00
Andrew Ayer
6e43b2a1cd New exec_command() that takes command as array instead of string
This abstracts away the details of argument quoting, which differs
between Unix and Windows.

Also replace all uses of the system() library call with exec_command().
Although system() exists on Windows, it executes the command via cmd.exe,
which has ridiculous escaping rules.
2014-06-12 21:23:04 -07:00
Andrew Ayer
0774ed018c Lay groundwork for Windows support
Move Unix-specific code to util-unix.cpp, and place Windows equivalents
in util-win32.cpp.  Most of the Windows functions are just stubs at
the moment, and we need a build system that works on Windows.
2014-06-12 21:23:02 -07:00
Simon Kotlinski
c2a9e48de5 Makefile: don't compile with -ansi
Fixes build on Cygwin due to [1].  Closes #19 on GitHub.

[1] https://cygwin.com/ml/cygwin/2014-01/msg00130.html
2014-06-08 15:57:19 -07:00
Simon Kotlinski
19ea278a31 Makefile: don't compile with -ansi
Fixes build on Cygwin due to [1].  Closes #19 on GitHub.

[1] https://cygwin.com/ml/cygwin/2014-01/msg00130.html
2014-06-03 09:02:40 -07:00
Andrew Ayer
22323bc3a5 In README, use HTTPS URI for git-crypt's website 2014-06-02 17:27:43 -07:00
Caleb Maclennan
79263fc57c fix link and header formatting; re-wrap text 2014-06-02 17:13:52 -07:00
Darayus Nanavati
29e589da3f cross-link documentation files using Markdown relative links 2014-06-02 17:13:52 -07:00
Darayus Nanavati
1843104015 convert documentation files to markdown
* format section headings, links and code snippets
* add .md file extension to trigger pretty rendering on Github
* standardize on lowercase typesetting for git-crypt
2014-06-02 17:13:49 -07:00
Andrew Ayer
d43a26ab0d Document Debian package building in INSTALL file 2014-04-01 21:54:45 -07:00
Andrew Ayer
8c77209d40 Fix include guards to not start with _
Since such names are reserved, technically.
2014-04-01 16:18:28 -07:00
Andrew Ayer
b3e843cfc4 Fix include guards to not start with _
Since such names are reserved, technically.
2014-04-01 16:17:22 -07:00
Andrew Ayer
7b2604e79e Document git-crypt's new mailing lists in the README 2014-04-01 09:57:40 -07:00
Andrew Ayer
7fe8443238 Minor tidy-up of INSTALL file 2014-03-31 21:31:49 -07:00
Adam Nelson
5638aa0e0e INSTALL updated for using homebrew on OS X 2014-03-31 13:55:30 +03:00
Andrew Ayer
6b78ef0548 Add initial Debian packaging 2014-03-29 17:39:53 -07:00
Andrew Ayer
7687d11219 Initial GPG support
Run 'git-crypt add-collab KEYID' to authorize the holder of the given
GPG secret key to access the encrypted files.  The secret git-crypt key
will be encrypted with the corresponding GPG public key and stored in the
root of the Git repository under .git-crypt/keys.

After cloning a repo with encrypted files, run 'git-crypt unlock'
(with no arguments) to use a secret key in your GPG keyring to unlock
the repository.

Multiple collaborators are supported, however commands to list the
collaborators ('git-crypt ls-collabs') and to remove a collaborator
('git-crypt rm-collab') are not yet supported.
2014-03-28 14:02:25 -07:00
Andrew Ayer
2b5e4a752e Plug a file descriptor leak if fork() fails
(Not that we really care if that happens ;-) but it's good to be correct.)
2014-03-28 13:54:18 -07:00
Andrew Ayer
b2bdc11330 Fix a typo in an error message 2014-03-28 13:53:12 -07:00
Andrew Ayer
df838947a0 Use successful_exit() helper for testing system() return value 2014-03-28 13:52:33 -07:00
Andrew Ayer
cd5f3534aa Rename some functions instead of overloading them
It's more clear this way.
2014-03-28 13:51:10 -07:00
Andrew Ayer
6a454b1fa1 Major revamp: new key paradigm, groundwork for GPG support
The active key is now stored in .git/git-crypt/key instead of being
stored outside the repo.  This will facilitate GPG support, where the
user may never interact directly with a key file.  It's also more
convenient, because it means you don't have to keep the key file
around in a fixed location (which can't be moved without breaking
git-crypt).

'git-crypt init' now takes no arguments and is used only when initializing
git-crypt for the very first time.  It generates a brand-new key, so
there's no longer a separate keygen step.

To export the key (for conveyance to another system or to a collaborator),
run 'git-crypt export-key FILENAME'.

To decrypt an existing repo using an exported key, run 'git-crypt unlock
KEYFILE'.  After running unlock, you can delete the key file you passed
to unlock.

Key files now use a new format that supports key versioning (which will
facilitate secure revocation in the future).

I've made these changes as backwards-compatible as possible.  Repos
already configured with git-crypt will continue to work without changes.
However, 'git-crypt unlock' expects a new format key.  You can use
the 'git-crypt migrate-key KEYFILE' command to migrate old keys to the
new format.

Note that old repos won't be able to use the new commands, like
export-key, or the future GPG support.  To migrate an old repo, migrate
its key file and then unlock the repo using the unlock command, as
described above.

While making these changes, I cleaned up the code significantly, adding
better error handling and improving robustness.

Next up: GPG support.
2014-03-23 11:40:29 -07:00
Andrew Ayer
2f02161042 Add utility functions for big-endian int storage
Use instead of htonl().
2014-03-22 11:41:18 -07:00
Andrew Ayer
73bf395b3b Makefile: add DESTDIR support to 'make install' 2014-01-04 10:51:12 -08:00
Andrew Ayer
34432e915e Use OpenSSL's RNG instead of /dev/random
Rationale:

 * /dev/random blocks unpredictably on Linux, leading to slow
   key generation.
 * OpenSSL's RNG is more cross-platform than /dev/(u)random.
   Some platforms might not have a (u)random device, or worse,
   have a /dev/(u)random that produces insecure random numbers
   (like Cygwin, apparently).
2013-12-30 14:37:21 -08:00
Andrew Ayer
d1aad00a59 Load OpenSSL error strings in main()
So we can report errors from OpenSSL.
2013-12-30 14:33:51 -08:00
Andrew Ayer
cbc2c6d388 Add missing return statement in main() 2013-12-30 14:33:31 -08:00
Andrew Ayer
9f20b8719c Fix a typo in a comment 2013-04-28 09:38:05 -07:00
Andrew Ayer
33f6d73a0c Improve usability of 'git-crypt keygen'
* Display message asking user to move the mouse, etc. to generate more
   entropy.
 * Disable buffering on the fstream so we don't read more randomness
   than we have to.
 * Refuse to overwrite an existing key file.
2013-04-28 09:36:17 -07:00
Andrew Ayer
9654cb6044 Tweak wording in README 2013-04-27 18:09:07 -07:00
52 changed files with 5950 additions and 463 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
.git* export-ignore

25
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,25 @@
Thanks for your interest in contributing to git-crypt! git-crypt is
open source software and welcomes contributions in the form of code,
documentation, bug reports, or anything else that improves git-crypt.
When contributing code, please consider the following guidelines:
* You are encouraged to open an issue on GitHub or send mail to
git-crypt-discuss@lists.cloudmutt.com to discuss any non-trivial
changes before you start coding.
* Please mimic the existing code style as much as possible. In
particular, please indent code using tab characters with a width
of 8.
* To minimize merge commits, please rebase your changes before opening
a pull request.
* To submit your patch, open a pull request on GitHub or send a
properly-formatted patch to git-crypt-discuss@lists.cloudmutt.com.
Finally, be aware that since git-crypt is security-sensitive software,
the bar for contributions is higher than average. Please don't be
discouraged by this, but be prepared for patches to possibly go through
several rounds of feedback and improvement before being accepted.
Your patience and understanding is appreciated.

70
INSTALL
View File

@@ -1,22 +1,72 @@
DEPENDENCIES
To build git-crypt, you need:
Debian/Ubuntu package RHEL/CentOS package
-----------------------------------------------------------------------------
Make make make
A C++ compiler (e.g. gcc) g++ gcc-c++
OpenSSL development files libssl-dev openssl-devel
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
Debian/Ubuntu package RHEL/CentOS package
-----------------------------------------------------------------------------
Git 1.7.2 or newer git git
OpenSSL openssl openssl
To build git-crypt, you need a C++ compiler and OpenSSL development
headers.
Note: Git 1.8.5 or newer is recommended for best performance.
BUILDING GIT-CRYPT
The Makefile is tailored for g++, but should work with other compilers.
Run:
$ make
$ cp git-crypt /usr/local/bin/
$ make install
It doesn't matter where you install the git-crypt binary - choose wherever
is most convenient for you.
To install to a specific location:
$ make install PREFIX=/usr/local
Or, just copy the git-crypt binary to wherever is most convenient for you.
BUILDING THE MAN PAGE
To build and install the git-crypt(1) man page, pass ENABLE_MAN=yes to make:
$ make ENABLE_MAN=yes
$ make ENABLE_MAN=yes install
xsltproc is required to build the man page. Note that xsltproc will access
the Internet to retrieve its stylesheet unless the Docbook stylesheet is
installed locally and registered in the system's XML catalog.
BUILDING A DEBIAN PACKAGE
Debian packaging can be found in the 'debian' branch of the project
Git repository. The package is built using git-buildpackage as follows:
$ git checkout debian
$ git-buildpackage -uc -us
INSTALLING ON MAC OS X
Using the brew package manager, simply run:
$ brew install git-crypt
EXPERIMENTAL WINDOWS SUPPORT
git-crypt should build on Windows with MinGW, although the build system
is not yet finalized so you will need to pass your own CXX, CXXFLAGS, and
LDFLAGS variables to make. Additionally, Windows support is less tested
and does not currently create key files with restrictive permissions,
making it unsuitable for use on a multi-user system. Windows support
will mature in a future version of git-crypt. Bug reports and patches
are most welcome!

71
INSTALL.md Normal file
View File

@@ -0,0 +1,71 @@
### Dependencies
To build git-crypt, you need:
| Debian/Ubuntu package | RHEL/CentOS package
---------------------------|-----------------------|------------------------
Make | make | make
A C++ compiler (e.g. gcc) | g++ | gcc-c++
OpenSSL development files | libssl-dev | openssl-devel
To use git-crypt, you need:
| Debian/Ubuntu package | RHEL/CentOS package
---------------------------|-----------------------|------------------------
Git 1.7.2 or newer | git | git
OpenSSL | openssl | openssl
Note: Git 1.8.5 or newer is recommended for best performance.
### Building git-crypt
Run:
make
make install
To install to a specific location:
make install PREFIX=/usr/local
Or, just copy the git-crypt binary to wherever is most convenient for you.
### Building The Man Page
To build and install the git-crypt(1) man page, pass `ENABLE_MAN=yes` to make:
make ENABLE_MAN=yes
make ENABLE_MAN=yes install
xsltproc is required to build the man page. Note that xsltproc will access
the Internet to retrieve its stylesheet unless the Docbook stylesheet is
installed locally and registered in the system's XML catalog.
### Building A Debian Package
Debian packaging can be found in the 'debian' branch of the project Git
repository. The package is built using git-buildpackage as follows:
git checkout debian
git-buildpackage -uc -us
### Installing On Mac OS X
Using the brew package manager, simply run:
brew install git-crypt
### Experimental Windows Support
git-crypt should build on Windows with MinGW, although the build system
is not yet finalized so you will need to pass your own CXX, CXXFLAGS, and
LDFLAGS variables to make. Additionally, Windows support is less tested
and does not currently create key files with restrictive permissions,
making it unsuitable for use on a multi-user system. Windows support
will mature in a future version of git-crypt. Bug reports and patches
are most welcome!

View File

@@ -1,19 +1,94 @@
CXX := c++
CXXFLAGS := -Wall -pedantic -ansi -Wno-long-long -O2
LDFLAGS := -lcrypto
PREFIX := /usr/local
#
# Copyright (c) 2015 Andrew Ayer
#
# See COPYING file for license information.
#
OBJFILES = git-crypt.o commands.o crypto.o util.o
CXXFLAGS ?= -Wall -pedantic -Wno-long-long -O2
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
MANDIR ?= $(PREFIX)/share/man
all: git-crypt
ENABLE_MAN ?= no
DOCBOOK_XSL ?= http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl
OBJFILES = \
git-crypt.o \
commands.o \
crypto.o \
gpg.o \
key.o \
util.o \
parse_options.o \
coprocess.o \
fhstream.o
OBJFILES += crypto-openssl.o
LDFLAGS += -lcrypto
XSLTPROC ?= xsltproc
DOCBOOK_FLAGS += --param man.output.in.separate.dir 1 \
--stringparam man.output.base.dir man/ \
--param man.output.subdirs.enabled 1 \
--param man.authors.section.enabled 1
all: build
#
# Build
#
BUILD_MAN_TARGETS-yes = build-man
BUILD_MAN_TARGETS-no =
BUILD_TARGETS := build-bin $(BUILD_MAN_TARGETS-$(ENABLE_MAN))
build: $(BUILD_TARGETS)
build-bin: git-crypt
git-crypt: $(OBJFILES)
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
$(CXX) $(CXXFLAGS) -o $@ $(OBJFILES) $(LDFLAGS)
clean:
rm -f *.o git-crypt
util.o: util.cpp util-unix.cpp util-win32.cpp
coprocess.o: coprocess.cpp coprocess-unix.cpp coprocess-win32.cpp
install:
install -m 755 git-crypt $(PREFIX)/bin/
build-man: man/man1/git-crypt.1
.PHONY: all clean install
man/man1/git-crypt.1: man/git-crypt.xml
$(XSLTPROC) $(DOCBOOK_FLAGS) $(DOCBOOK_XSL) $<
#
# Clean
#
CLEAN_MAN_TARGETS-yes = clean-man
CLEAN_MAN_TARGETS-no =
CLEAN_TARGETS := clean-bin $(CLEAN_MAN_TARGETS-$(ENABLE_MAN))
clean: $(CLEAN_TARGETS)
clean-bin:
rm -f $(OBJFILES) git-crypt
clean-man:
rm -f man/man1/git-crypt.1
#
# Install
#
INSTALL_MAN_TARGETS-yes = install-man
INSTALL_MAN_TARGETS-no =
INSTALL_TARGETS := install-bin $(INSTALL_MAN_TARGETS-$(ENABLE_MAN))
install: $(INSTALL_TARGETS)
install-bin: build-bin
install -d $(DESTDIR)$(BINDIR)
install -m 755 git-crypt $(DESTDIR)$(BINDIR)/
install-man: build-man
install -d $(DESTDIR)$(MANDIR)/man1
install -m 644 man/man1/git-crypt.1 $(DESTDIR)$(MANDIR)/man1/
.PHONY: all \
build build-bin build-man \
clean clean-bin clean-man \
install install-bin install-man

39
NEWS
View File

@@ -1,3 +1,42 @@
v0.5.0 (2015-05-30)
* Drastically speed up lock/unlock when used with Git 1.8.5 or newer.
* Add git-crypt(1) man page (pass ENABLE_MAN=yes to make to build).
* Add --trusted option to 'git-crypt gpg-add-user' to add user even if
GPG doesn't trust user's key.
* Improve 'git-crypt lock' usability, add --force option.
* Ignore symlinks and other non-files when running 'git-crypt status'.
* Fix compilation on old versions of Mac OS X.
* Fix GPG mode when with-fingerprint enabled in gpg.conf.
* Minor bug fixes and improvements to help/error messages.
v0.4.2 (2015-01-31)
* Fix unlock and lock under Git 2.2.2 and higher.
* Drop support for versions of Git older than 1.7.2.
* Minor improvements to some help/error messages.
v0.4.1 (2015-01-08)
* Important usability fix to ensure that the .git-crypt directory
can't be encrypted by accident (see RELEASE_NOTES-0.4.1.md for
more information).
v0.4 (2014-11-16)
(See RELEASE_NOTES-0.4.md for important details.)
* Add optional GPG support: GPG can be used to share the repository
between one or more users in lieu of sharing a secret key.
* New workflow: the symmetric key is now stored inside the .git
directory. Although backwards compatibility has been preserved
with repositories created by old versions of git-crypt, the
commands for setting up a repository have changed. See the
release notes file for details.
* Multiple key support: it's now possible to encrypt different parts
of a repository with different keys.
* Initial 'git-crypt status' command to report which files are
encrypted and to fix problems that are detected.
* Numerous usability, documentation, and error reporting improvements.
* Major internal code improvements that will make future development
easier.
* Initial experimental Windows support.
v0.3 (2013-04-05)
* Fix 'git-crypt init' on newer versions of Git. Previously,
encrypted files were not being automatically decrypted after

60
NEWS.md Normal file
View File

@@ -0,0 +1,60 @@
News
====
######v0.5.0 (2015-05-30)
* Drastically speed up lock/unlock when used with Git 1.8.5 or newer.
* Add git-crypt(1) man page (pass `ENABLE_MAN=yes` to make to build).
* Add --trusted option to `git-crypt gpg-add-user` to add user even if
GPG doesn't trust user's key.
* Improve `git-crypt lock` usability, add --force option.
* Ignore symlinks and other non-files when running `git-crypt status`.
* Fix compilation on old versions of Mac OS X.
* Fix GPG mode when with-fingerprint enabled in gpg.conf.
* Minor bug fixes and improvements to help/error messages.
######v0.4.2 (2015-01-31)
* Fix unlock and lock under Git 2.2.2 and higher.
* Drop support for versions of Git older than 1.7.2.
* Minor improvements to some help/error messages.
######v0.4.1 (2015-01-08)
* Important usability fix to ensure that the .git-crypt directory
can't be encrypted by accident (see
[the release notes](RELEASE_NOTES-0.4.1.md) for more information).
######v0.4 (2014-11-16)
(See [the release notes](RELEASE_NOTES-0.4.md) for important details.)
* Add optional GPG support: GPG can be used to share the repository
between one or more users in lieu of sharing a secret key.
* New workflow: the symmetric key is now stored inside the .git
directory. Although backwards compatibility has been preserved
with repositories created by old versions of git-crypt, the
commands for setting up a repository have changed. See the
release notes file for details.
* Multiple key support: it's now possible to encrypt different parts
of a repository with different keys.
* Initial `git-crypt status` command to report which files are
encrypted and to fix problems that are detected.
* Numerous usability, documentation, and error reporting improvements.
* Major internal code improvements that will make future development
easier.
* Initial experimental Windows support.
######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](README.md): 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.

160
README
View File

@@ -10,8 +10,8 @@ you store your secret material (such as keys or passwords) in the same
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.agwa.name/projects/git-crypt>.
git-crypt was written by Andrew Ayer <agwa@andrewayer.name>. For more
information, see <https://www.agwa.name/projects/git-crypt>.
BUILDING GIT-CRYPT
@@ -21,81 +21,137 @@ See the INSTALL file.
USING GIT-CRYPT
Generate a secret key:
$ git-crypt keygen /path/to/keyfile
Configure a repository to use encryption:
Configure a repository to use git-crypt:
$ cd repo
$ git-crypt init /path/to/keyfile
$ git-crypt init
Specify files to encrypt by creating a .gitattributes file:
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
.gitattributes file itself!
Like a .gitignore file, it can match wildcards and should be checked into
the repository. See below for more information about .gitattributes.
Make sure you don't accidentally encrypt the .gitattributes file itself
(or other git files like .gitignore or .gitmodules). Make sure your
.gitattributes rules are in place *before* you add sensitive files, or
those files won't be encrypted!
Cloning a repository with encrypted files:
Share the repository with others (or with yourself) using GPG:
$ git clone /path/to/repo
$ cd repo
$ git-crypt init /path/to/keyfile
$ git-crypt add-gpg-user USER_ID
That's all you need to do - after running git-crypt init, you can use
git normally - encryption and decryption happen transparently.
USER_ID can be a key ID, a full fingerprint, an email address, or anything
else that uniquely identifies a public key to GPG (see "HOW TO SPECIFY
A USER ID" in the gpg man page). Note: `git-crypt add-gpg-user` will
add and commit a GPG-encrypted key file in the .git-crypt directory of
the root of your repository.
Alternatively, you can export a symmetric secret key, which you must
securely convey to collaborators (GPG is not required, and no files
are added to your repository):
$ git-crypt export-key /path/to/key
After cloning a repository with encrypted files, unlock with with GPG:
$ git-crypt unlock
Or with a symmetric key:
$ git-crypt unlock /path/to/key
That's all you need to do - after git-crypt is set up (either with
`git-crypt init` or `git-crypt unlock`), 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.
The latest version of git-crypt is 0.5.0, released on 2015-05-30.
git-crypt aims to be bug-free and reliable, meaning it shouldn't
crash, malfunction, or expose your confidential data. However,
it has not yet reached maturity, meaning it is not as documented,
featureful, or easy-to-use as it should be. Additionally, there may be
backwards-incompatible changes introduced before version 1.0.
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.
git-crypt encrypts files using AES-256 in CTR mode with a synthetic IV
derived from the SHA-1 HMAC of the file. This mode of operation 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.
Other proposals for transparent git encryption use ECB or CBC with a
fixed IV. These systems are not semantically secure and leak information.
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 relies on git filters, which were not designed with encryption
in mind. As such, git-crypt is not the best tool for encrypting most or
all of the files in a repository. Where git-crypt really shines is where
most of your repository is public, but you have a few files (perhaps
private keys named *.key, or a file with API credentials) which you
need to encrypt. For encrypting an entire repository, consider using a
system like git-remote-gcrypt <https://github.com/joeyh/git-remote-gcrypt>
instead. (Note: no endorsement is made of git-remote-gcrypt's security.)
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.).
git-crypt does not encrypt file names, commit messages, symlink targets,
gitlinks, or other metadata.
git-crypt does not hide when a file does or doesn't change, the length
of a file, or the fact that two files are identical (see "Security"
section above).
Files encrypted with git-crypt are not compressible. Even the smallest
change to an encrypted file requires git to store the entire changed file,
instead of just a delta.
Although git-crypt protects individual file contents with a SHA-1
HMAC, git-crypt cannot be used securely unless the entire repository is
protected against tampering (an attacker who can mutate your repository
can alter your .gitattributes file to disable encryption). If necessary,
use git features such as signed tags instead of relying solely on
git-crypt for integrity.
Files encrypted with git-crypt cannot be patched with git-apply, unless
the patch itself is encrypted. To generate an encrypted patch, use `git
diff --no-textconv --binary`. Alternatively, you can apply a plaintext
patch outside of git using the patch command.
git-crypt does not work reliably with some third-party git GUIs, such
as Atlassian SourceTree <https://jira.atlassian.com/browse/SRCTREE-2511>
and GitHub for Mac. Files might be left in an unencrypted state.
GITATTRIBUTES FILE
The .gitattributes file is documented in the gitattributes(5) man page.
The file pattern format is the same as the one used by .gitignore,
as documented in the gitignore(5) man page, with the exception that
specifying merely a directory (e.g. `/dir/`) is NOT sufficient to
encrypt all files beneath it.
Also note that the pattern `dir/*` does not match files under
sub-directories of dir/. To encrypt an entire sub-tree dir/, place the
following in dir/.gitattributes:
* filter=git-crypt diff=git-crypt
.gitattributes !filter !diff
The second pattern is essential for ensuring that .gitattributes itself
is not encrypted.
MAILING LISTS
To stay abreast of, and provide input to, git-crypt development, consider
subscribing to one or both of our mailing lists:
Announcements: https://lists.cloudmutt.com/mailman/listinfo/git-crypt-announce
Discussion: https://lists.cloudmutt.com/mailman/listinfo/git-crypt-discuss

159
README.md Normal file
View File

@@ -0,0 +1,159 @@
git-crypt - transparent file encryption in git
==============================================
git-crypt enables transparent encryption and decryption of files in a
git repository. Files which you choose to protect are encrypted when
committed, and decrypted when checked out. git-crypt lets you freely
share a repository containing a mix of public and private content.
git-crypt gracefully degrades, so developers without the secret key can
still clone and commit to a repository with encrypted files. This lets
you store your secret material (such as keys or passwords) in the same
repository as your code, without requiring you to lock down your entire
repository.
git-crypt was written by [Andrew Ayer](https://www.agwa.name) (agwa@andrewayer.name).
For more information, see <https://www.agwa.name/projects/git-crypt>.
Building git-crypt
------------------
See the [INSTALL.md](INSTALL.md) file.
Using git-crypt
---------------
Configure a repository to use git-crypt:
cd repo
git-crypt init
Specify files to encrypt by creating a .gitattributes file:
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. See below for more information about .gitattributes.
Make sure you don't accidentally encrypt the .gitattributes file itself
(or other git files like .gitignore or .gitmodules). Make sure your
.gitattributes rules are in place *before* you add sensitive files, or
those files won't be encrypted!
Share the repository with others (or with yourself) using GPG:
git-crypt add-gpg-user USER_ID
`USER_ID` can be a key ID, a full fingerprint, an email address, or
anything else that uniquely identifies a public key to GPG (see "HOW TO
SPECIFY A USER ID" in the gpg man page). Note: `git-crypt add-gpg-user`
will add and commit a GPG-encrypted key file in the .git-crypt directory
of the root of your repository.
Alternatively, you can export a symmetric secret key, which you must
securely convey to collaborators (GPG is not required, and no files
are added to your repository):
git-crypt export-key /path/to/key
After cloning a repository with encrypted files, unlock with with GPG:
git-crypt unlock
Or with a symmetric key:
git-crypt unlock /path/to/key
That's all you need to do - after git-crypt is set up (either with
`git-crypt init` or `git-crypt unlock`), you can use git normally -
encryption and decryption happen transparently.
Current Status
--------------
The latest version of git-crypt is [0.5.0](NEWS.md), released on
2015-05-30. git-crypt aims to be bug-free and reliable, meaning it
shouldn't crash, malfunction, or expose your confidential data.
However, it has not yet reached maturity, meaning it is not as
documented, featureful, or easy-to-use as it should be. Additionally,
there may be backwards-incompatible changes introduced before version
1.0.
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 mode of operation 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 a
fixed IV. These systems are not semantically secure and leak information.
Limitations
-----------
git-crypt relies on git filters, which were not designed with encryption
in mind. As such, git-crypt is not the best tool for encrypting most or
all of the files in a repository. Where git-crypt really shines is where
most of your repository is public, but you have a few files (perhaps
private keys named *.key, or a file with API credentials) which you
need to encrypt. For encrypting an entire repository, consider using a
system like [git-remote-gcrypt](https://github.com/joeyh/git-remote-gcrypt)
instead. (Note: no endorsement is made of git-remote-gcrypt's security.)
git-crypt does not encrypt file names, commit messages, symlink targets,
gitlinks, or other metadata.
git-crypt does not hide when a file does or doesn't change, the length
of a file, or the fact that two files are identical (see "Security"
section above).
Files encrypted with git-crypt are not compressible. Even the smallest
change to an encrypted file requires git to store the entire changed file,
instead of just a delta.
Although git-crypt protects individual file contents with a SHA-1
HMAC, git-crypt cannot be used securely unless the entire repository is
protected against tampering (an attacker who can mutate your repository
can alter your .gitattributes file to disable encryption). If necessary,
use git features such as signed tags instead of relying solely on
git-crypt for integrity.
Files encrypted with git-crypt cannot be patched with git-apply, unless
the patch itself is encrypted. To generate an encrypted patch, use `git
diff --no-textconv --binary`. Alternatively, you can apply a plaintext
patch outside of git using the patch command.
git-crypt does not work reliably with some third-party git GUIs, such
as [Atlassian SourceTree](https://jira.atlassian.com/browse/SRCTREE-2511)
and GitHub for Mac. Files might be left in an unencrypted state.
Gitattributes File
------------------
The .gitattributes file is documented in the gitattributes(5) man page.
The file pattern format is the same as the one used by .gitignore,
as documented in the gitignore(5) man page, with the exception that
specifying merely a directory (e.g. `/dir/`) is *not* sufficient to
encrypt all files beneath it.
Also note that the pattern `dir/*` does not match files under
sub-directories of dir/. To encrypt an entire sub-tree dir/, place the
following in dir/.gitattributes:
* filter=git-crypt diff=git-crypt
.gitattributes !filter !diff
The second pattern is essential for ensuring that .gitattributes itself
is not encrypted.
Mailing Lists
-------------
To stay abreast of, and provide input to, git-crypt development,
consider subscribing to one or both of our mailing lists:
* [Announcements](https://lists.cloudmutt.com/mailman/listinfo/git-crypt-announce)
* [Discussion](https://lists.cloudmutt.com/mailman/listinfo/git-crypt-discuss)

21
RELEASE_NOTES-0.4.1.md Normal file
View File

@@ -0,0 +1,21 @@
git-crypt 0.4.1 is a bugfix-only release that contains an important
usability fix for users who use GPG mode to encrypt an entire repository.
Previously, if you used a '*' pattern in the top-level .gitattributes
file, and you did not explicitly add a pattern to exclude the .git-crypt
directory, the files contained therein would be encrypted, rendering
the repository impossible to unlock with GPG.
git-crypt now adds a .gitattributes file to the .git-crypt directory
to prevent its contents from being encrypted, regardless of patterns in
the top-level .gitattributes.
If you are using git-crypt in GPG mode to encrypt an entire repository,
and you do not already have a .gitattributes pattern to exclude the
.git-crypt directory, you are strongly advised to upgrade. After
upgrading, you should do the following in each of your repositories to
ensure that the information inside .git-crypt is properly stored:
1. Remove existing key files: `rm .git-crypt/keys/*/0/*`
2. Re-add GPG user(s): `git-crypt add-gpg-user GPG_USER_ID ...`

84
RELEASE_NOTES-0.4.md Normal file
View File

@@ -0,0 +1,84 @@
Changes to be aware of for git-crypt 0.4
========================================
(For a complete list of changes, see the [NEWS](NEWS.md) file.)
### New workflow
The commands for setting up a repository have changed in git-crypt 0.4.
The previous commands continue to work, but will be removed in a future
release of git-crypt. Please get in the habit of using the new syntax:
`git-crypt init` no longer takes an argument, and is now used only when
initializing a repository for the very first time. It generates a key
and stores it in the `.git` directory. There is no longer a separate
`keygen` step, and you no longer need to keep a copy of the key outside
the repository.
`git-crypt init` is no longer used to decrypt a cloned repository. Instead,
run `git-crypt unlock /path/to/keyfile`, where `keyfile` is obtained by
running `git-crypt export-key /path/to/keyfile` from an already-decrypted
repository.
### GPG mode
git-crypt now supports GPG. A repository can be shared with one or more
GPG users in lieu of sharing a secret symmetric key. Symmetric key support
isn't going away, but the workflow of GPG mode is extremely easy and all users
are encouraged to consider it for their repositories.
See the [README](README.md) for details on using GPG.
### Status command
A new command, `git-crypt status`, lists encrypted files, which is
useful for making sure your `.gitattributes` pattern is protecting the
right files.
### Multiple key support
git-crypt now lets you encrypt different sets of files with different
keys, which is useful if you want to grant different collaborators access
to different sets of files.
See [doc/multiple_keys.md](doc/multiple_keys.md) for details.
### Compatibility with old repositories
Repositories created with older versions of git-crypt continue to work
without any changes needed, and backwards compatibility with these
repositories will be maintained indefinitely.
However, you will not be able to take advantage of git-crypt's new
features, such as GPG support, unless you migrate your repository.
To migrate your repository, first ensure the working tree is clean.
Then migrate your current key file and use the migrated key to unlock
your repository as follows:
git-crypt migrate-key /path/to/old_key /path/to/migrated_key
git-crypt unlock /path/to/migrated_key
Once you've confirmed that your repository is functional, you can delete
both the old and migrated key files (though keeping a backup of your key
is always a good idea).
### Known issues
It is not yet possible to revoke access from a GPG user. This will
require substantial development work and will be a major focus of future
git-crypt development.
The output of `git-crypt status` is currently very bare-bones and will
be substantially improved in a future release. Do not rely on its output
being stable. A future release of git-crypt will provide an option for stable
machine-readable output.
On Windows, git-crypt does not create key files with restrictive
permissions. Take care when using git-crypt on a multi-user Windows system.

19
THANKS.md Normal file
View File

@@ -0,0 +1,19 @@
For their contributions to git-crypt, I thank:
* Michael Mior and @zimbatm for the Homebrew formula.
* Cyril Cleaud for help with Windows support.
* The following people for contributing patches:
* Adam Nelson
* Caleb Maclennan
* Darayus Nanavati
* Jon Sailor
* Linus G Thiel
* Michael Schout
* Simon Kotlinski
* And everyone who has tested git-crypt, provided feedback, reported
bugs, and participated in discussions about new features.
Thank you!

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 Andrew Ayer
* Copyright 2012, 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
@@ -28,15 +28,46 @@
* as that of the covered work.
*/
#ifndef _COMMANDS_H
#define _COMMANDS_H
#ifndef GIT_CRYPT_COMMANDS_HPP
#define GIT_CRYPT_COMMANDS_HPP
#include <string>
#include <iosfwd>
void clean (const char* keyfile);
void smudge (const char* keyfile);
void diff (const char* keyfile, const char* filename);
void init (const char* argv0, const char* keyfile);
void keygen (const char* keyfile);
struct Error {
std::string message;
explicit Error (std::string m) : message(m) { }
};
// Plumbing commands:
int clean (int argc, const char** argv);
int smudge (int argc, const char** argv);
int diff (int argc, const char** argv);
// Public commands:
int init (int argc, const char** argv);
int unlock (int argc, const char** argv);
int lock (int argc, const char** argv);
int add_gpg_user (int argc, const char** argv);
int rm_gpg_user (int argc, const char** argv);
int ls_gpg_users (int argc, const char** argv);
int export_key (int argc, const char** argv);
int keygen (int argc, const char** argv);
int migrate_key (int argc, const char** argv);
int refresh (int argc, const char** argv);
int status (int argc, const char** argv);
// Help messages:
void help_init (std::ostream&);
void help_unlock (std::ostream&);
void help_lock (std::ostream&);
void help_add_gpg_user (std::ostream&);
void help_rm_gpg_user (std::ostream&);
void help_ls_gpg_users (std::ostream&);
void help_export_key (std::ostream&);
void help_keygen (std::ostream&);
void help_migrate_key (std::ostream&);
void help_refresh (std::ostream&);
void help_status (std::ostream&);
#endif

186
coprocess-unix.cpp Normal file
View File

@@ -0,0 +1,186 @@
/*
* Copyright 2015 Andrew Ayer
*
* This file is part of git-crypt.
*
* git-crypt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* git-crypt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "coprocess.hpp"
#include "util.hpp"
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
static int execvp (const std::string& file, const std::vector<std::string>& args)
{
std::vector<const char*> args_c_str;
args_c_str.reserve(args.size());
for (std::vector<std::string>::const_iterator arg(args.begin()); arg != args.end(); ++arg) {
args_c_str.push_back(arg->c_str());
}
args_c_str.push_back(NULL);
return execvp(file.c_str(), const_cast<char**>(&args_c_str[0]));
}
Coprocess::Coprocess ()
{
pid = -1;
stdin_pipe_reader = -1;
stdin_pipe_writer = -1;
stdin_pipe_ostream = NULL;
stdout_pipe_reader = -1;
stdout_pipe_writer = -1;
stdout_pipe_istream = NULL;
}
Coprocess::~Coprocess ()
{
close_stdin();
close_stdout();
}
std::ostream* Coprocess::stdin_pipe ()
{
if (!stdin_pipe_ostream) {
int fds[2];
if (pipe(fds) == -1) {
throw System_error("pipe", "", errno);
}
stdin_pipe_reader = fds[0];
stdin_pipe_writer = fds[1];
stdin_pipe_ostream = new ofhstream(this, write_stdin);
}
return stdin_pipe_ostream;
}
void Coprocess::close_stdin ()
{
delete stdin_pipe_ostream;
stdin_pipe_ostream = NULL;
if (stdin_pipe_writer != -1) {
close(stdin_pipe_writer);
stdin_pipe_writer = -1;
}
if (stdin_pipe_reader != -1) {
close(stdin_pipe_reader);
stdin_pipe_reader = -1;
}
}
std::istream* Coprocess::stdout_pipe ()
{
if (!stdout_pipe_istream) {
int fds[2];
if (pipe(fds) == -1) {
throw System_error("pipe", "", errno);
}
stdout_pipe_reader = fds[0];
stdout_pipe_writer = fds[1];
stdout_pipe_istream = new ifhstream(this, read_stdout);
}
return stdout_pipe_istream;
}
void Coprocess::close_stdout ()
{
delete stdout_pipe_istream;
stdout_pipe_istream = NULL;
if (stdout_pipe_writer != -1) {
close(stdout_pipe_writer);
stdout_pipe_writer = -1;
}
if (stdout_pipe_reader != -1) {
close(stdout_pipe_reader);
stdout_pipe_reader = -1;
}
}
void Coprocess::spawn (const std::vector<std::string>& args)
{
pid = fork();
if (pid == -1) {
throw System_error("fork", "", errno);
}
if (pid == 0) {
if (stdin_pipe_writer != -1) {
close(stdin_pipe_writer);
}
if (stdout_pipe_reader != -1) {
close(stdout_pipe_reader);
}
if (stdin_pipe_reader != -1) {
dup2(stdin_pipe_reader, 0);
close(stdin_pipe_reader);
}
if (stdout_pipe_writer != -1) {
dup2(stdout_pipe_writer, 1);
close(stdout_pipe_writer);
}
execvp(args[0], args);
perror(args[0].c_str());
_exit(-1);
}
if (stdin_pipe_reader != -1) {
close(stdin_pipe_reader);
stdin_pipe_reader = -1;
}
if (stdout_pipe_writer != -1) {
close(stdout_pipe_writer);
stdout_pipe_writer = -1;
}
}
int Coprocess::wait ()
{
int status = 0;
if (waitpid(pid, &status, 0) == -1) {
throw System_error("waitpid", "", errno);
}
return status;
}
size_t Coprocess::write_stdin (void* handle, const void* buf, size_t count)
{
const int fd = static_cast<Coprocess*>(handle)->stdin_pipe_writer;
ssize_t ret;
while ((ret = write(fd, buf, count)) == -1 && errno == EINTR); // restart if interrupted
if (ret < 0) {
throw System_error("write", "", errno);
}
return ret;
}
size_t Coprocess::read_stdout (void* handle, void* buf, size_t count)
{
const int fd = static_cast<Coprocess*>(handle)->stdout_pipe_reader;
ssize_t ret;
while ((ret = read(fd, buf, count)) == -1 && errno == EINTR); // restart if interrupted
if (ret < 0) {
throw System_error("read", "", errno);
}
return ret;
}

68
coprocess-unix.hpp Normal file
View File

@@ -0,0 +1,68 @@
/*
* Copyright 2015 Andrew Ayer
*
* This file is part of git-crypt.
*
* git-crypt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* git-crypt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 GIT_CRYPT_COPROCESS_HPP
#define GIT_CRYPT_COPROCESS_HPP
#include "fhstream.hpp"
#include <unistd.h>
#include <vector>
class Coprocess {
pid_t pid;
int stdin_pipe_reader;
int stdin_pipe_writer;
ofhstream* stdin_pipe_ostream;
static size_t write_stdin (void*, const void*, size_t);
int stdout_pipe_reader;
int stdout_pipe_writer;
ifhstream* stdout_pipe_istream;
static size_t read_stdout (void*, void*, size_t);
Coprocess (const Coprocess&); // Disallow copy
Coprocess& operator= (const Coprocess&); // Disallow assignment
public:
Coprocess ();
~Coprocess ();
std::ostream* stdin_pipe ();
void close_stdin ();
std::istream* stdout_pipe ();
void close_stdout ();
void spawn (const std::vector<std::string>&);
int wait ();
};
#endif

269
coprocess-win32.cpp Normal file
View File

@@ -0,0 +1,269 @@
/*
* Copyright 2015 Andrew Ayer
*
* This file is part of git-crypt.
*
* git-crypt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* git-crypt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "coprocess-win32.hpp"
#include "util.hpp"
static void escape_cmdline_argument (std::string& cmdline, const std::string& arg)
{
// For an explanation of Win32's arcane argument quoting rules, see:
// http://msdn.microsoft.com/en-us/library/17w5ykft%28v=vs.85%29.aspx
// http://msdn.microsoft.com/en-us/library/bb776391%28v=vs.85%29.aspx
// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
// http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx
cmdline.push_back('"');
std::string::const_iterator p(arg.begin());
while (p != arg.end()) {
if (*p == '"') {
cmdline.push_back('\\');
cmdline.push_back('"');
++p;
} else if (*p == '\\') {
unsigned int num_backslashes = 0;
while (p != arg.end() && *p == '\\') {
++num_backslashes;
++p;
}
if (p == arg.end() || *p == '"') {
// Backslashes need to be escaped
num_backslashes *= 2;
}
while (num_backslashes--) {
cmdline.push_back('\\');
}
} else {
cmdline.push_back(*p++);
}
}
cmdline.push_back('"');
}
static std::string format_cmdline (const std::vector<std::string>& command)
{
std::string cmdline;
for (std::vector<std::string>::const_iterator arg(command.begin()); arg != command.end(); ++arg) {
if (arg != command.begin()) {
cmdline.push_back(' ');
}
escape_cmdline_argument(cmdline, *arg);
}
return cmdline;
}
static HANDLE spawn_command (const std::vector<std::string>& command, HANDLE stdin_handle, HANDLE stdout_handle, HANDLE stderr_handle)
{
PROCESS_INFORMATION proc_info;
ZeroMemory(&proc_info, sizeof(proc_info));
STARTUPINFO start_info;
ZeroMemory(&start_info, sizeof(start_info));
start_info.cb = sizeof(STARTUPINFO);
start_info.hStdInput = stdin_handle ? stdin_handle : GetStdHandle(STD_INPUT_HANDLE);
start_info.hStdOutput = stdout_handle ? stdout_handle : GetStdHandle(STD_OUTPUT_HANDLE);
start_info.hStdError = stderr_handle ? stderr_handle : GetStdHandle(STD_ERROR_HANDLE);
start_info.dwFlags |= STARTF_USESTDHANDLES;
std::string cmdline(format_cmdline(command));
if (!CreateProcessA(NULL, // application name (NULL to use command line)
const_cast<char*>(cmdline.c_str()),
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&start_info,
&proc_info)) {
throw System_error("CreateProcess", cmdline, GetLastError());
}
CloseHandle(proc_info.hThread);
return proc_info.hProcess;
}
Coprocess::Coprocess ()
{
proc_handle = NULL;
stdin_pipe_reader = NULL;
stdin_pipe_writer = NULL;
stdin_pipe_ostream = NULL;
stdout_pipe_reader = NULL;
stdout_pipe_writer = NULL;
stdout_pipe_istream = NULL;
}
Coprocess::~Coprocess ()
{
close_stdin();
close_stdout();
if (proc_handle) {
CloseHandle(proc_handle);
}
}
std::ostream* Coprocess::stdin_pipe ()
{
if (!stdin_pipe_ostream) {
SECURITY_ATTRIBUTES sec_attr;
// Set the bInheritHandle flag so pipe handles are inherited.
sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
sec_attr.bInheritHandle = TRUE;
sec_attr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDIN.
if (!CreatePipe(&stdin_pipe_reader, &stdin_pipe_writer, &sec_attr, 0)) {
throw System_error("CreatePipe", "", GetLastError());
}
// Ensure the write handle to the pipe for STDIN is not inherited.
if (!SetHandleInformation(stdin_pipe_writer, HANDLE_FLAG_INHERIT, 0)) {
throw System_error("SetHandleInformation", "", GetLastError());
}
stdin_pipe_ostream = new ofhstream(this, write_stdin);
}
return stdin_pipe_ostream;
}
void Coprocess::close_stdin ()
{
delete stdin_pipe_ostream;
stdin_pipe_ostream = NULL;
if (stdin_pipe_writer) {
CloseHandle(stdin_pipe_writer);
stdin_pipe_writer = NULL;
}
if (stdin_pipe_reader) {
CloseHandle(stdin_pipe_reader);
stdin_pipe_reader = NULL;
}
}
std::istream* Coprocess::stdout_pipe ()
{
if (!stdout_pipe_istream) {
SECURITY_ATTRIBUTES sec_attr;
// Set the bInheritHandle flag so pipe handles are inherited.
sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
sec_attr.bInheritHandle = TRUE;
sec_attr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
if (!CreatePipe(&stdout_pipe_reader, &stdout_pipe_writer, &sec_attr, 0)) {
throw System_error("CreatePipe", "", GetLastError());
}
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!SetHandleInformation(stdout_pipe_reader, HANDLE_FLAG_INHERIT, 0)) {
throw System_error("SetHandleInformation", "", GetLastError());
}
stdout_pipe_istream = new ifhstream(this, read_stdout);
}
return stdout_pipe_istream;
}
void Coprocess::close_stdout ()
{
delete stdout_pipe_istream;
stdout_pipe_istream = NULL;
if (stdout_pipe_writer) {
CloseHandle(stdout_pipe_writer);
stdout_pipe_writer = NULL;
}
if (stdout_pipe_reader) {
CloseHandle(stdout_pipe_reader);
stdout_pipe_reader = NULL;
}
}
void Coprocess::spawn (const std::vector<std::string>& args)
{
proc_handle = spawn_command(args, stdin_pipe_reader, stdout_pipe_writer, NULL);
if (stdin_pipe_reader) {
CloseHandle(stdin_pipe_reader);
stdin_pipe_reader = NULL;
}
if (stdout_pipe_writer) {
CloseHandle(stdout_pipe_writer);
stdout_pipe_writer = NULL;
}
}
int Coprocess::wait ()
{
if (WaitForSingleObject(proc_handle, INFINITE) == WAIT_FAILED) {
throw System_error("WaitForSingleObject", "", GetLastError());
}
DWORD exit_code;
if (!GetExitCodeProcess(proc_handle, &exit_code)) {
throw System_error("GetExitCodeProcess", "", GetLastError());
}
return exit_code;
}
size_t Coprocess::write_stdin (void* handle, const void* buf, size_t count)
{
DWORD bytes_written;
if (!WriteFile(static_cast<Coprocess*>(handle)->stdin_pipe_writer, buf, count, &bytes_written, NULL)) {
throw System_error("WriteFile", "", GetLastError());
}
return bytes_written;
}
size_t Coprocess::read_stdout (void* handle, void* buf, size_t count)
{
// Note that ReadFile on a pipe may return with bytes_read==0 if the other
// end of the pipe writes zero bytes, so retry when this happens.
// When the other end of the pipe actually closes, ReadFile
// fails with ERROR_BROKEN_PIPE.
DWORD bytes_read;
do {
if (!ReadFile(static_cast<Coprocess*>(handle)->stdout_pipe_reader, buf, count, &bytes_read, NULL)) {
const DWORD read_error = GetLastError();
if (read_error != ERROR_BROKEN_PIPE) {
throw System_error("ReadFile", "", read_error);
}
return 0;
}
} while (bytes_read == 0);
return bytes_read;
}

68
coprocess-win32.hpp Normal file
View File

@@ -0,0 +1,68 @@
/*
* Copyright 2015 Andrew Ayer
*
* This file is part of git-crypt.
*
* git-crypt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* git-crypt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 GIT_CRYPT_COPROCESS_HPP
#define GIT_CRYPT_COPROCESS_HPP
#include "fhstream.hpp"
#include <windows.h>
#include <vector>
class Coprocess {
HANDLE proc_handle;
HANDLE stdin_pipe_reader;
HANDLE stdin_pipe_writer;
ofhstream* stdin_pipe_ostream;
static size_t write_stdin (void*, const void*, size_t);
HANDLE stdout_pipe_reader;
HANDLE stdout_pipe_writer;
ifhstream* stdout_pipe_istream;
static size_t read_stdout (void*, void*, size_t);
Coprocess (const Coprocess&); // Disallow copy
Coprocess& operator= (const Coprocess&); // Disallow assignment
public:
Coprocess ();
~Coprocess ();
std::ostream* stdin_pipe ();
void close_stdin ();
std::istream* stdout_pipe ();
void close_stdout ();
void spawn (const std::vector<std::string>&);
int wait ();
};
#endif

5
coprocess.cpp Normal file
View File

@@ -0,0 +1,5 @@
#ifdef _WIN32
#include "coprocess-win32.cpp"
#else
#include "coprocess-unix.cpp"
#endif

5
coprocess.hpp Normal file
View File

@@ -0,0 +1,5 @@
#ifdef _WIN32
#include "coprocess-win32.hpp"
#else
#include "coprocess-unix.hpp"
#endif

115
crypto-openssl.cpp Normal file
View File

@@ -0,0 +1,115 @@
/*
* Copyright 2012, 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
* git-crypt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* git-crypt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "crypto.hpp"
#include "key.hpp"
#include "util.hpp"
#include <openssl/aes.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <sstream>
#include <cstring>
void init_crypto ()
{
ERR_load_crypto_strings();
}
struct Aes_ecb_encryptor::Aes_impl {
AES_KEY key;
};
Aes_ecb_encryptor::Aes_ecb_encryptor (const unsigned char* raw_key)
: impl(new Aes_impl)
{
if (AES_set_encrypt_key(raw_key, KEY_LEN * 8, &(impl->key)) != 0) {
throw Crypto_error("Aes_ctr_encryptor::Aes_ctr_encryptor", "AES_set_encrypt_key failed");
}
}
Aes_ecb_encryptor::~Aes_ecb_encryptor ()
{
// Note: Explicit destructor necessary because class contains an auto_ptr
// which contains an incomplete type when the auto_ptr is declared.
explicit_memset(&impl->key, '\0', sizeof(impl->key));
}
void Aes_ecb_encryptor::encrypt(const unsigned char* plain, unsigned char* cipher)
{
AES_encrypt(plain, cipher, &(impl->key));
}
struct Hmac_sha1_state::Hmac_impl {
HMAC_CTX ctx;
};
Hmac_sha1_state::Hmac_sha1_state (const unsigned char* key, size_t key_len)
: impl(new Hmac_impl)
{
HMAC_Init(&(impl->ctx), key, key_len, EVP_sha1());
}
Hmac_sha1_state::~Hmac_sha1_state ()
{
// Note: Explicit destructor necessary because class contains an auto_ptr
// which contains an incomplete type when the auto_ptr is declared.
HMAC_cleanup(&(impl->ctx));
}
void Hmac_sha1_state::add (const unsigned char* buffer, size_t buffer_len)
{
HMAC_Update(&(impl->ctx), buffer, buffer_len);
}
void Hmac_sha1_state::get (unsigned char* digest)
{
unsigned int len;
HMAC_Final(&(impl->ctx), digest, &len);
}
void random_bytes (unsigned char* buffer, size_t len)
{
if (RAND_bytes(buffer, len) != 1) {
std::ostringstream message;
while (unsigned long code = ERR_get_error()) {
char error_string[120];
ERR_error_string_n(code, error_string, sizeof(error_string));
message << "OpenSSL Error: " << error_string << "; ";
}
throw Crypto_error("random_bytes", message.str());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 Andrew Ayer
* Copyright 2012, 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
@@ -28,102 +28,54 @@
* as that of the covered work.
*/
#define _BSD_SOURCE
#include "crypto.hpp"
#include <openssl/aes.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <fstream>
#include <iostream>
#include "util.hpp"
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
void load_keys (const char* filepath, keys_t* keys)
Aes_ctr_encryptor::Aes_ctr_encryptor (const unsigned char* raw_key, const unsigned char* nonce)
: ecb(raw_key)
{
std::ifstream file(filepath);
if (!file) {
perror(filepath);
std::exit(1);
}
char buffer[AES_KEY_BITS/8 + HMAC_KEY_LEN];
file.read(buffer, sizeof(buffer));
if (file.gcount() != sizeof(buffer)) {
std::clog << filepath << ": Premature end of key file\n";
std::exit(1);
}
// First comes the AES encryption key
if (AES_set_encrypt_key(reinterpret_cast<uint8_t*>(buffer), AES_KEY_BITS, &keys->enc) != 0) {
std::clog << filepath << ": Failed to initialize AES encryption key\n";
std::exit(1);
}
// Then it's the HMAC key
memcpy(keys->hmac, buffer + AES_KEY_BITS/8, HMAC_KEY_LEN);
}
aes_ctr_state::aes_ctr_state (const uint8_t* arg_nonce, size_t arg_nonce_len)
{
memset(nonce, '\0', sizeof(nonce));
memcpy(nonce, arg_nonce, std::min(arg_nonce_len, sizeof(nonce)));
// Set first 12 bytes of the CTR value to the nonce.
// This stays the same for the entirety of this object's lifetime.
std::memcpy(ctr_value, nonce, NONCE_LEN);
byte_counter = 0;
memset(otp, '\0', sizeof(otp));
}
void aes_ctr_state::process (const AES_KEY* key, const uint8_t* in, uint8_t* out, size_t len)
Aes_ctr_encryptor::~Aes_ctr_encryptor ()
{
explicit_memset(pad, '\0', BLOCK_LEN);
}
void Aes_ctr_encryptor::process (const unsigned char* in, unsigned char* out, size_t len)
{
for (size_t i = 0; i < len; ++i) {
if (byte_counter % 16 == 0) {
// Generate a new OTP
// CTR value:
// first 12 bytes - nonce
// last 4 bytes - block number (sequentially increasing with each block)
uint8_t ctr[16];
uint32_t blockno = htonl(byte_counter / 16);
memcpy(ctr, nonce, 12);
memcpy(ctr + 12, &blockno, 4);
AES_encrypt(ctr, otp, key);
if (byte_counter % BLOCK_LEN == 0) {
// Set last 4 bytes of CTR to the (big-endian) block number (sequentially increasing with each block)
store_be32(ctr_value + NONCE_LEN, byte_counter / BLOCK_LEN);
// Generate a new pad
ecb.encrypt(ctr_value, pad);
}
// encrypt one byte
out[i] = in[i] ^ otp[byte_counter++ % 16];
out[i] = in[i] ^ pad[byte_counter++ % BLOCK_LEN];
if (byte_counter == 0) {
throw Crypto_error("Aes_ctr_encryptor::process", "Too much data to encrypt securely");
}
}
}
hmac_sha1_state::hmac_sha1_state (const uint8_t* key, size_t key_len)
{
HMAC_Init(&ctx, key, key_len, EVP_sha1());
}
hmac_sha1_state::~hmac_sha1_state ()
{
HMAC_cleanup(&ctx);
}
void hmac_sha1_state::add (const uint8_t* buffer, size_t buffer_len)
{
HMAC_Update(&ctx, buffer, buffer_len);
}
void hmac_sha1_state::get (uint8_t* digest)
{
unsigned int len;
HMAC_Final(&ctx, digest, &len);
}
// Encrypt/decrypt an entire input stream, writing to the given output stream
void process_stream (std::istream& in, std::ostream& out, const AES_KEY* enc_key, const uint8_t* nonce)
void Aes_ctr_encryptor::process_stream (std::istream& in, std::ostream& out, const unsigned char* key, const unsigned char* nonce)
{
aes_ctr_state state(nonce, 12);
Aes_ctr_encryptor aes(key, nonce);
uint8_t buffer[1024];
unsigned char buffer[1024];
while (in) {
in.read(reinterpret_cast<char*>(buffer), sizeof(buffer));
state.process(enc_key, buffer, buffer, in.gcount());
aes.process(buffer, buffer, in.gcount());
out.write(reinterpret_cast<char*>(buffer), in.gcount());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 Andrew Ayer
* Copyright 2012, 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
@@ -28,56 +28,90 @@
* as that of the covered work.
*/
#ifndef _CRYPTO_H
#define _CRYPTO_H
#ifndef GIT_CRYPT_CRYPTO_HPP
#define GIT_CRYPT_CRYPTO_HPP
#include <openssl/aes.h>
#include <openssl/hmac.h>
#include "key.hpp"
#include <stdint.h>
#include <stddef.h>
#include <iosfwd>
#include <string>
#include <memory>
enum {
SHA1_LEN = 20,
NONCE_LEN = 12,
HMAC_KEY_LEN = 64,
AES_KEY_BITS = 256,
MAX_CRYPT_BYTES = (1ULL<<32)*16 // Don't encrypt more than this or the CTR value will repeat itself
void init_crypto ();
struct Crypto_error {
std::string where;
std::string message;
Crypto_error (const std::string& w, const std::string& m) : where(w), message(m) { }
};
struct keys_t {
AES_KEY enc;
uint8_t hmac[HMAC_KEY_LEN];
};
void load_keys (const char* filepath, keys_t* keys);
class Aes_ecb_encryptor {
public:
enum {
KEY_LEN = AES_KEY_LEN,
BLOCK_LEN = 16
};
class aes_ctr_state {
char nonce[NONCE_LEN];// First 96 bits of counter
uint32_t byte_counter; // How many bytes processed so far?
uint8_t otp[16]; // The current OTP that's in use
private:
struct Aes_impl;
std::auto_ptr<Aes_impl> impl;
public:
aes_ctr_state (const uint8_t* arg_nonce, size_t arg_nonce_len);
void process (const AES_KEY* key, const uint8_t* in, uint8_t* out, size_t len);
Aes_ecb_encryptor (const unsigned char* key);
~Aes_ecb_encryptor ();
void encrypt (const unsigned char* plain, unsigned char* cipher);
};
class hmac_sha1_state {
HMAC_CTX ctx;
// disallow copy/assignment:
hmac_sha1_state (const hmac_sha1_state&) { }
hmac_sha1_state& operator= (const hmac_sha1_state&) { return *this; }
class Aes_ctr_encryptor {
public:
hmac_sha1_state (const uint8_t* key, size_t key_len);
~hmac_sha1_state ();
enum {
NONCE_LEN = 12,
KEY_LEN = AES_KEY_LEN,
BLOCK_LEN = 16,
MAX_CRYPT_BYTES = (1ULL<<32)*16 // Don't encrypt more than this or the CTR value will repeat itself
};
void add (const uint8_t* buffer, size_t buffer_len);
void get (uint8_t*);
private:
Aes_ecb_encryptor ecb;
unsigned char ctr_value[BLOCK_LEN]; // Current CTR value (used as input to AES to derive pad)
unsigned char pad[BLOCK_LEN]; // Current encryption pad (output of AES)
uint32_t byte_counter; // How many bytes processed so far?
public:
Aes_ctr_encryptor (const unsigned char* key, const unsigned char* nonce);
~Aes_ctr_encryptor ();
void process (const unsigned char* in, unsigned char* out, size_t len);
// Encrypt/decrypt an entire input stream, writing to the given output stream
static void process_stream (std::istream& in, std::ostream& out, const unsigned char* key, const unsigned char* nonce);
};
// Encrypt/decrypt an entire input stream, writing to the given output stream
void process_stream (std::istream& in, std::ostream& out, const AES_KEY* enc_key, const uint8_t* nonce);
typedef Aes_ctr_encryptor Aes_ctr_decryptor;
class Hmac_sha1_state {
public:
enum {
LEN = 20,
KEY_LEN = HMAC_KEY_LEN
};
private:
struct Hmac_impl;
std::auto_ptr<Hmac_impl> impl;
public:
Hmac_sha1_state (const unsigned char* key, size_t key_len);
~Hmac_sha1_state ();
void add (const unsigned char* buffer, size_t buffer_len);
void get (unsigned char*);
};
void random_bytes (unsigned char*, size_t);
#endif

12
debian/changelog vendored Normal file
View File

@@ -0,0 +1,12 @@
git-crypt (0.5.0-2) unstable; urgency=medium
* Build against OpenSSL 1.0.x. (Closes: #828312)
* Bump standards version (no changes needed).
-- Andrew Ayer <agwa@andrewayer.name> Wed, 23 Nov 2016 12:51:51 -0800
git-crypt (0.5.0-1) unstable; urgency=medium
* Initial release. (Closes: #785346)
-- Andrew Ayer <agwa@andrewayer.name> Sat, 30 May 2015 20:22:22 -0700

1
debian/compat vendored Normal file
View File

@@ -0,0 +1 @@
9

24
debian/control vendored Normal file
View File

@@ -0,0 +1,24 @@
Source: git-crypt
Maintainer: Andrew Ayer <agwa@andrewayer.name>
Section: vcs
Priority: optional
Standards-Version: 3.9.8
Build-Depends: debhelper (>= 9), libssl1.0-dev | libssl-dev (<< 1.1.0~), xsltproc, docbook-xml, docbook-xsl
Vcs-Git: https://www.agwa.name/git/git-crypt.git -b debian
Homepage: https://www.agwa.name/projects/git-crypt
Package: git-crypt
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, git (>= 1.7.2)
Recommends: gnupg
Enhances: git
Description: Transparent file encryption in git
git-crypt enables transparent encryption and decryption of files in a
git repository. Files which you choose to protect are encrypted when
committed, and decrypted when checked out. git-crypt lets you freely
share a repository containing a mix of public and private content.
git-crypt gracefully degrades, so developers without the secret key
can still clone and commit to a repository with encrypted files.
This lets you store your secret material (such as keys or passwords)
in the same repository as your code, without requiring you to lock down
your entire repository.

64
debian/copyright vendored Normal file
View File

@@ -0,0 +1,64 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: git-crypt
Source: https://www.agwa.name/projects/git-crypt
Files: *
Copyright: Copyright 2012-2015 Andrew Ayer
License: GPL-3+ with OpenSSL exception
Files: fhstream.cpp fhstream.hpp parse_options.cpp parse_options.hpp
Copyright: Copyright 2012, 2014, 2015 Andrew Ayer
License: X11
License: GPL-3+ with OpenSSL exception
git-crypt is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
git-crypt is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with git-crypt. If not, see <http://www.gnu.org/licenses/>.
.
On Debian systems, the full text of the GNU General Public
License version 3 can be found in the file
`/usr/share/common-licenses/GPL-3'.
.
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.
License: X11
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
.
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
.
Except as contained in this notice, the name(s) of the above copyright
holders shall not be used in advertising or otherwise to promote the
sale, use or other dealings in this Software without prior written
authorization.

5
debian/gbp.conf vendored Normal file
View File

@@ -0,0 +1,5 @@
[DEFAULT]
pristine-tar = True
pristine-tar-commit = True
debian-branch = debian
upstream-tag = %(version)s

8
debian/git-crypt.docs vendored Normal file
View File

@@ -0,0 +1,8 @@
CONTRIBUTING.md
NEWS
NEWS.md
README
README.md
RELEASE_NOTES-0.4.1.md
RELEASE_NOTES-0.4.md
THANKS.md

7
debian/rules vendored Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/make -f
export PREFIX=/usr
export ENABLE_MAN=yes
%:
dh $@

1
debian/source/format vendored Normal file
View File

@@ -0,0 +1 @@
3.0 (quilt)

1
debian/source/options vendored Normal file
View File

@@ -0,0 +1 @@
single-debian-patch

17
debian/source/patch-header vendored Normal file
View File

@@ -0,0 +1,17 @@
Subject: Collected Debian patches for git-crypt
Author: Andrew Ayer <agwa@andrewayer.name>
Since I am also upstream for this package, there will normally not be
any patches to apply to the upstream source. However, occasionally
I'll pull up specific upstream commits prior to making an upstream
release. When this happens, this patch will collect all of those
modifications.
I use Git to maintain both the upstream source and the Debian
packages, and generating individual patches rather than using git
cherry-pick takes extra work for no gain. Since I'm also upstream,
there's no need to separate the patches for later upstream submission.
Hence, I take this approach with a unified patch when it's necessary.
For full commit history and separated commits, see the upstream Git
repository.

135
debian/upstream/signing-key.asc vendored Normal file
View File

@@ -0,0 +1,135 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFNTDEsBEACiZ+AWNaj80CvSIV9T+mlPClETM+pxEHuB+vldasG+BWsyyb2d
AH390MSjXzs4RaiDGAXgZKnP9bhlWV/6BYcF0edz+G+Ux89L+D/c6miWFqwywQ7G
FRBh10WDriNbSF6UoD6TJX9Kc2KIgeDQ7LFL1PsiFjsO/cUBfDmSvMWd/zzIV4Ug
QdqFjjHdPSTr5w9D5YDS5FY2UOmcrxyNU66PoGkIp0Cqjgaieszxx3/CkGtktn0T
M/dwP+yafgH5uUDRfgFR5McTvu4E53CAimsiv05wodXmnoELojVDrEYciJIrfGRx
fCj+tiWsz0IcJCJ/ND1UExlI5so5tx0YaYMapx3PCb7+ZrZEUrRa3xK6m+ZU2Qfk
XQJmUSnKuIsKP4Uo8ysMPxDuWrCap4nxw2uNsGgPXXnExJgVoWfyR0qMr16+BUSu
Yrtfng19npv/Y381Y6hB8uRWRNRbaG8MEAHrgdPkb6853cbXT6A+k3KbZJVcEZsv
XCS2lFuWaZTQGA1G4mj7TbudjvHLuDV8VbPsyxotgAHITSh6pekBuJFPOVhAZN41
HyPNMZnE7eoW6DmShrdC+TjIVpzliqiLytDfGabIMNbnQDWdHxhzqq/Vn07ObOVp
0MGn7PaGeyF+rmi0h1ttab3Dioku49dljjuz+uNhBcu1CB/CegIdtRVyDwARAQAB
tCJBbmRyZXcgQXllciA8YWd3YUBhbmRyZXdheWVyLm5hbWU+iQI6BBMBCAAkAhsB
BQsJCAcDBRUKCQgLBRYDAgEAAh4BAheABQJTUwz6AhkBAAoJEBA3jvwggAgM0BwQ
AI8tAYxpeHyN+6StVAiu+wY2n2SNuunsORVmqyiICDOruxPTr4ulsP8vKM7Y4L9H
lpJqfW/g65cByuZ48dFciQmI97RfZjjZgAxejM1dq/Y0RJGpZ6I7pNNbWIxvDm7o
8mMeEQYT5S4vZKmreYZpzjLLZin+3gpBfljapGRikZNC1dzQu5JA0iCkWqIWmgto
O/kEmGKID9ShLB0H/f26K+jKfKuQ2unqlPgUPlQfd65yBN0m0SaESozs5hnCjMxS
oJSDnI0Kst0W/E/c0RZp5WJ3PECqnV0ru7xazMPLP3yNMrJVv3s6rKdiwzkQYUDj
10KxCDOChc9UfVRZfiVc4OlnK77L5EM3AWLrNOwx8ntcs67hJXrppoDVin87PyMG
SJE+DNKZk40oHLIrWCE43zS8p0k4OSGpJhiVev/o2FJImIiZXdYrm0gA3rA5OOM2
nDSyPlB0kOzMN03z7pzT319OMhf5KZgYRuoAU59IqdW+Iz2q1vfusuh0UU5GfP+U
XOuiiv9O9VqPTxpDOzmk5qnw8p3NNdwnqzop64wcBsNebyWGM52Hetd8bIwdFniI
XsBhzRxnMVmNdXwQ7zhPKwpgGA9vWkyZ15EuhDK0Wc3XQf51NWVjY9Pt/NgpwKli
CEqnd+sUszM78jXhvZjsWEkophudU6O615zYEEpTe+b/tB5BbmRyZXcgQXllciA8
YW5kcmV3QGFnd2EubmFtZT6JAjcEEwEIACEFAlNTDM8CGwEFCwkIBwMFFQoJCAsF
FgMCAQACHgECF4AACgkQEDeO/CCACAxWoA/+NYvxCoJ4V/TGZnYQJxT95qK+8uGS
sQmlAYlIbYAAr0zxe1+U0HxMVMh0pj211B8Nd6Qq46X2ZYmWSyAPA6tiiSg+Y5C9
2IGyCZ11oG98Op99nnPOEnKAPVgTtGgpMeomTPo768ucj0kvvbibGg3yMs1JBH2p
wBULHZLCswlKzDOgg4YZfa5eicXRJOjl1EVbdhPyMZrnJB5bFnkno8rSIJ1h3QkV
kWAJ0bPggHxsWiYOEJUjEkzr4zXjcCJKIyPWY9rZcgJNvo1Bwqx/E7Cs+XABuPtU
0OPIysXROANzsUMVt0NpcSJsAPJxt5L5EoCVRhTDjaK6kXglEcxqOqSzTsu61cKj
MAAyZY+qfTA2ILzPYgxiy+h4ReUxAHx0Bzd7w6L+tn1aPlc9nqDm1nzk9U8rzA5j
XDJJJOuC5yCzhQ2tpsAp/RRE+1sgiGV8D+LSXf2ZYBQbV9V6icFdhHUrqKzQsw7m
5C4n2Fk1wk54JTFO1j4rWarHiVeKuKWiBrQhhUJqFtJ/lpr6tIVsI5KuoWczHpQ0
RhCnUNFPj1JaMM09pLEkg9u/RRrVo6eSHy0vHKCqYZYUAldTGpE18kRlpTDgrN8T
jhBaiFrk8PBKPiE4Pt+5BgDZr8jP+CWA9Bc06E6EtV+fDdTexAj2Uw0JE0zZdhQc
1J1NNIMHc4WpS6+0JEFuZHJldyBBeWVyIDxhbmRyZXdAYW5kcmV3YXllci5uYW1l
PokCNwQTAQgAIQUCU1MMjQIbAQULCQgHAwUVCgkICwUWAwIBAAIeAQIXgAAKCRAQ
N478IIAIDDg1EACQ+stApB16L54LnS7YMFUHeUgaG3Hfu+WmIrvRn5UcT4hxRNsY
/c7nDumrwAmg8HiOXwe7V8MJMFUEEoQylWpYLFzOTlwDmVGUMczQScgi7RvK9zwJ
0475Wlpr/7wUrG0LyinLVf7GoKPtIkefzlydTzMu4xSr52t1sSGQqcWXDVJ2bCGf
KuvmDpv1gOORdp4g2lI0wOPWgPzm4ctqhzT58jFR5vQpzpr+H0qB35prcKa9TAIG
sbtF1bdSaOBkXev/IFhY07Fq35aqEuv27+aMJkYjhzIVSnm+/9Io/1L0RTpHZpgc
px08yfllq7Vf0buCiC8uEvdFVL4sHOlp9gVU21boLj4JJnZvlJ1m+gfIMAfhtt4R
2QNxKxC+jO2bBvj4j9dHBz0MQvAoJl121cjOAxZGMHye3eV0sHtE7UntxA5pQQv0
U2hx7A70yAlN2Np76xVDO9cae4aVUV7lCdY14L9e5Ww4mAknFMeIgbkoxWT2lMA+
1pfn541c6XUuuNfr1D9flYHbDFEYTUOFZ6ypAgOxkK+/wPYlz4d6MTBJ4fcoEhSF
qX0JVFW8Jrnqs4GsDmej9dibfYdk2a0qG69oSRF8Bujxdh41R+/C03LSnRBXhjHJ
SG/xSYoCcOL6y3aiJb2gK2lbrLxmeysCn7goGPp16VikSxHNFJkigRUqDbQhQW5k
cmV3IEF5ZXIgPGFnd2FAYW5kcmV3YXllci5jb20+iQI3BBMBCAAhBQJTUwyrAhsB
BQsJCAcDBRUKCQgLBRYDAgEAAh4BAheAAAoJEBA3jvwggAgMfWsP/2F+Ds4lIn7p
rdgEeK2kT4Sxn4ldATU8ZKjVe/RnTxWx+jqEHdsXkUAVEHNuUPYwtvETub2mMFBr
lguL1WWW09J1ihYEk4ErDT81kNP+8cGGDjLkv8FWjlLPUGrcZRMj52JP0saW7AIJ
2/OxvpC00xkpb5goy5yXUKVgzBhVUAcSx1a3vY92y+RnmcwhnQmlhAy+xpyO0Ju+
dEhNfBny+gc/YdWfha6sWed6fL0VWut2l1euEU9WIH/Mcd8NWGs10ez9iqVhRWi3
zpTWxkVBY0+PaPaHkqh6N3cCDEdFXXmVtP3nPvC+4k6vIUK+frIIffjDlOXhXaPB
bGZ2SyJcYJdsh6Lb3cnQ2ISHrlKy2CgVkExdgz8JeSF296OInQJmS/U9XFH2iN/w
Yivi93pUV9xvPaJTXOSUMA+szKXapPTH8zUNQ99tm7KiwV0aMXWKG0iNxjg7C0kO
vVuKGBKEpFdyNLL7uAE/bzs7WVZ8ztebZJTlScIU7KZUr931kR++VAzIFr7yYlaC
4c2TjiinStFsHgfRqaVoJfQIWQzxrli46LkaIVur3rzBJERNSpDcPlpF1wngXkFo
Jqt+c7MTVBeoaaAv5TAK0a/D027fUNtAyxtCPmkoHs6BWh38NDGO7VNBRNog0Vnm
ItcFmFtM0rcQV6DbUjUZdTYygPGq0MAWtCNBbmRyZXcgQXllciA8YW5kcmV3QGFu
ZHJld2F5ZXIuY29tPokCNwQTAQgAIQUCU1MMwgIbAQULCQgHAwUVCgkICwUWAwIB
AAIeAQIXgAAKCRAQN478IIAIDDN4D/0eINLgeE2mHs68kM6fXRf0jfDdyVYMvxnK
ZO0jq1sjurEYgw+tHQRHo5bsu52E1yneoam5pzucu6TXUVc2X7dGKfuqvsU55+Qo
IlqE0ai0tXlmc7orVzZFgLCTGrZgL2NL208h3EvlSeqy//6yA/rirMIRiD/vSb3A
u0EBXSav+x/o092W904jPzeLGGL9c9G6HxmAzGxRO0q5FfB7u1sI9bpgljC3mBVb
jUhD/ynIwkIZfTgQFRlBHtzy0HpcnlzxzcY7b/SB+8was1aEDetzuEeXOdTDXxd6
gVBFDZMyKtZU5jsprZDU6/nleka6/ji6ZMNIJvMgcJbgwV3i/ipKH3FfItcnTt+F
gx2q3j/pVYPyKo05tp2dBCgdyEKsTgc+QTqo0EOk5to8Gz7ebkyPGIELqIfBtPjp
hoB9oTpgmnAsgSnSGDfz4yD+8uU3FBOjrMNe5tucq+N8VVYZhCQFs8fAbrghh568
ahBcLDu4n9OvjZz5hHnpARtSOw2WmLdQ43JAnFa6F8rN98ymlP6X8CT3lTnPwkRL
+9l008KXP64nZStISozKwDb4zMfRn7iHbqs6reIlvgIOevkiMmPTyhhQ0zz6aLwy
ZNcQPK7wdXtynPydzAPfKbxfQkVpkQb/mgBgr2blZSu4Z5W491tDOTI/MF0gMTfA
eqavC9PhRrkCDQRTUw1OARAAyr3w+DujjQ+oiLrlnGu+DArnxK5lf8DzeVokFlSX
TJXSgCl4niJeQhodn3EtnDRdKimdvMxgW+iVU4MKhK/2xbf0rNSQDJv4iIub0wHH
Y8kkqsBU3vDjoTYraoFMjKWIvZEr9FJTiDgX4VruJAhwydjknrSWdK7As9PzqU0l
C7ReHRJIJLu20EeVCVoGuyVmRfendTXDbflvZhUhTitcgJbNek272u521lYbk3g1
knMWhwmdsy95ZqNbjk4iMJ6eq4l2MA/yka4V4zA7P9L0WBjuFyImVix4WyaF+TIN
/t8eX+zF6VfImoKlMer8qpMmGaFBKxOdGdAH+YpoP8sysSrFDV0iSbA/WwbNa4e7
F8eras4B/sFWIldXlSd0yLqdNe8ZB0vPDS44tNcmDYz3cWIH1mDF1AbHDBbOxwa+
FkcHDPjYrnIQbFI3Z+rzOqc8vOeiNPHggPSDRjM6duah4aOLp6RYEYc/2ouO1yMD
UlWQ+eGDvqAvIt3HH2y78fbkl3K2VVA+gd5w9oxDKxXKZxp/y5RVVYCZUAv1lllT
87dRMXignTVEIsF8VdESrHhRYUQW+2wP40hKZIPEaWS5BSH/d/qGgDSBsVPbDlas
4n3FJ+SVil6xm0hJ+29Xw/GDMc+uQfs8TkPxbM0cjn59oZb6I3Zhxy+KWSxdCN2g
8nMAEQEAAYkERAQYAQgADwIbAgUCU1MO8wUJB4TPXQIpwV0gBBkBCAAGBQJTUw1O
AAoJEJ0V9ORH5MgyJTQP/0cSeXYZ+G7EKEjM+xHfjUyNavahVZ+rfiLzZpU+os4s
SGwd4annJ4Z56UMA+1U5Qm34+rH+E72qTJAEhg512CQJnFejVKqBuVzBsxaO5aLK
nSafGk3Ixev4JraspFtDmSflVHrQFVKeu3oF+tjE2elHHhhV8I1N8eA2HTiwlTpD
9RgQ1nFw9KM4+ncBm56dK3lVQbgT7XPz6noIPhXpAKH1THthvIyjUDyTqeRUrgHL
eeFtKXWnsiUMi5z3dz1lK0QacGaykl+VtLXlUIffh7IpLp3MoiaboXV2UdocIrZ3
vg00wBrZ01w7L10LB0Jl1NEH5ccbezAVCaW3/2MT6C7BnQZqpjQiKncrnx2YoupJ
+OWJT2QOQoLOKwL4ShHB8Dae4RD0aTcmTTVBEteSvH32hhcsXChMi1Dzsf6cvNmV
pl54VkfWmZqyfTl7LjqpQsOcUtDshFPQMziKF8pSyhSYiuImvLFwypTe4KtDqA/U
d1v9XB40mniAUTC5dWO+80MhOB4NpXNNSXP22DzpDZvfhdTxSCwc70bpWJIRVpuT
gVvVjqOiCVanNXqwh14Nn+/SaRDx19BglXhniBWNEXN8f2dhI3YkZ2ovS/t6mfxn
aymghsKL1yvu8zdTFZsmAhYdpTBIB9zMjoNs3TNb8I+X26oPUjSKZ1/+oAacGTtJ
CRAQN478IIAIDBXvD/sG09+tMC30Tcq6DuycebKiuHfIioBdWTNLTz3Pahi0C/4e
WDcd0H8hcxUz9cdBnUs5uvUwdo07bqVb+zJ0PidDdCpCSDwNH9Zn98Duy7QVPtAU
psPApSe2Y23zh4tr37c31dlUIsRCiGfjIjjoCFg3NZMxmw4y3UhUMPNemDt+rB+t
XmstUc03xdaUrrssAxZf/qhNy9tPucHGl1Uuq1c8ANhdgmosMacfxb9i/kcCErTl
Kz3DwZPVdE4Q/KcaiTOBxBthlvpZM27h//BeT/Yo2Hy/X7nzhhyscPqaDj/HOCbt
O/wyH/kv4z++bzy7FSERMJTQvAaYEbhWE71l+sSBJT1ZJnafDXX+b9ZlSE7Im5C8
mjGc1XX9Mw1eXzLh7wiC+BEEr4S9qOkyKtSKcQ/m+UjOARXrqA1qhDp1mmhSVNsW
UnhWabsO9TwxcRdSX98ql+rNYBjN7tsqzq3G3R3oFeaGevAqPqJFbAFhGhB33UP1
yolOPxGP6u2NdNFmGQ2vVlzJNzY5QAk2cekIZWJLd8YEcXeijPo1BX4eTg4V6d61
EWIKoSHBeKVwhuCg9l6ZFrNzUoHGP4yKDXJood71KBulDSSUkzIM05ngfcajz1RS
R9szqJlNAuLHpQHOZEI1k38sQrnPoqwzyshgU7S/vLAT6hr666bJ2i9l82X077kC
DQRTUw4RARAA30ouhOBeAX4ORzDmeyP9MjuDZWArkJv22PI5ijISIyRjUdYogvqY
XAm2z0RTzD4waW9lSabumDqVGd5MzTm0GTaIw/E+7rx08vnBVRlHLpAOZMUogNEG
p+3uXhgHmar0uhVhYar1v6SUqscray+wtnkRnXQkitkD0zzTWGx17LnWPOguNDsU
uYjWoAYgEiS9g3UYLfexQRVxae7n6YTDedNfjuMKAmmc+ixWDHQ9Dcjc4oJacV5z
iz78NYdXoPA3101lKPzUGqRye/Rvm0/GXzht5nhsIMB/X/FIk6qlbCAP/VpVRpYD
KE7N3juJY7jwoCze9d3TZx6jU8IR/rD1uuDW/gjyPVAHAcE1fRwjErtR1TsFcXdY
wtTJx+Rzq6gaS1mfeV90/V2RY0i+nqGhmY+o+lLt1uozX3iTO2X9cF/REp9i6Rly
TOUMAPW7mOryN9Yv/WAkG4S/WgtnZRCA0M88jFSgX8go+Y2Z6yAUynEXDREH9VMI
rw0kNhvOzfWSAyBOuNfNrehShR6RMtAKs/JC+VEwhvDD+avFNHywpn7fGMa2WqFw
x337Yz6wARJ5g9vBCMJBWMhWT62cMQK62TLVkW5lbULSVwUAsbNyZMaQks7kfas3
9w3gr66Bc/2v4PaQMUQ3I79yO/f6zsxu9vZ1yxPtKOGwGHQvnCdRMlMAEQEAAYkC
JQQYAQgADwIbDAUCU1MPDQUJB4TO+QAKCRAQN478IIAIDFQcEACFi6e4/++dhkPT
e1dnaifmSINXthSFkOAp8OR0mK2/0Ged6UmbyxPd7gKQJUXFV5Mz5Sj8G6N4YCLl
+55k67gwZwzZ62EYB41FnjkXKILBgP+mt2c0SCuwwwz9g8Jx351l2Cfatz6SoviE
XjQURDHPxsqK1mfKMmoMWo4Ii68hzKrBg0mcdxeJeYj/t8l0ug2JQApnJsaE+5QC
3U9txBYm1Rpsaz/IFvlGJS9CkZyQA7ci/eMmoKn+/R8Gk48avkM0vFmUz9T8saIr
g0F5T7ThjC8575PNtInpDZRp9V3PdeKtiA9Fky11fNi0VS7luurKQ3I0UJfdyxe1
Sb/kvwSu++zEDEG5/li4GfMBqiZTV6oZJ9fzZnrGjEyiRSLN7GA4T1KC795zm/63
MrTaTf/3G6iRLNcvEdcWTiSHlFSkpx+qQHoijs0KU6uzdvHwtKKX/UBrk837SOFL
AEqn8ebsOKYiO4gA0ismpAt9NQwqsKq0rhyZAIDl9hSjwGBqX24NA7qnkoZNZySC
YBn0ZZRr3VGyeNWgtZg/KIiyKX7xmEUgBswt/U42x5wG3SdhAiSn6If/y8dTpfzT
kXF2+jp/tbGpm1qHi6qrkdMqCHCf67beh9SmlKa8nYyb1fpEcNBBElA8AAFgaYKh
zZq3C24mS3JwlqDwjT/Ut1/tChr3wQ==
=ZMwd
-----END PGP PUBLIC KEY BLOCK-----

2
debian/watch vendored Normal file
View File

@@ -0,0 +1,2 @@
version=3
opts=pgpsigurlmangle=s/$/.asc/ https://www.agwa.name/projects/git-crypt/ .*/git-crypt-(\d.*)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))

24
doc/multiple_keys.md Normal file
View File

@@ -0,0 +1,24 @@
In addition to the implicit default key, git-crypt supports alternative
keys which can be used to encrypt specific files and can be shared with
specific GPG users. This is useful if you want to grant different
collaborators access to different sets of files.
To generate an alternative key named KEYNAME, pass the `-k KEYNAME`
option to `git-crypt init` as follows:
git-crypt init -k KEYNAME
To encrypt a file with an alternative key, use the `git-crypt-KEYNAME`
filter in `.gitattributes` as follows:
secretfile filter=git-crypt-KEYNAME diff=git-crypt-KEYNAME
To export an alternative key or share it with a GPG user, pass the `-k
KEYNAME` option to `git-crypt export-key` or `git-crypt add-gpg-user`
as follows:
git-crypt export-key -k KEYNAME /path/to/keyfile
git-crypt add-gpg-user -k KEYNAME GPG_USER_ID
To unlock a repository with an alternative key, use `git-crypt unlock`
normally. git-crypt will automatically determine which key is being used.

227
fhstream.cpp Normal file
View File

@@ -0,0 +1,227 @@
/*
* Copyright (C) 2012, 2015 Andrew Ayer
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name(s) of the above copyright
* holders shall not be used in advertising or otherwise to promote the
* sale, use or other dealings in this Software without prior written
* authorization.
*/
#include <cstring>
#include <algorithm> // for std::min
#include "fhstream.hpp"
/*
* ofhstream
*/
ofhbuf::ofhbuf (void* arg_handle, size_t (*arg_write_fun)(void*, const void*, size_t))
: handle(arg_handle),
write_fun(arg_write_fun),
buffer(new char[default_buffer_size]),
buffer_size(default_buffer_size)
{
reset_buffer();
}
ofhbuf::~ofhbuf ()
{
if (handle) {
try {
sync();
} catch (...) {
// Ignore exception since we're in the destructor.
// To catch write errors, call sync() explicitly.
}
}
delete[] buffer;
}
ofhbuf::int_type ofhbuf::overflow (ofhbuf::int_type c)
{
const char* p = pbase();
std::streamsize bytes_to_write = pptr() - p;
if (!is_eof(c)) {
*pptr() = c;
++bytes_to_write;
}
while (bytes_to_write > 0) {
const size_t bytes_written = write_fun(handle, p, bytes_to_write);
bytes_to_write -= bytes_written;
p += bytes_written;
}
reset_buffer();
return traits_type::to_int_type(0);
}
int ofhbuf::sync ()
{
return !is_eof(overflow(traits_type::eof())) ? 0 : -1;
}
std::streamsize ofhbuf::xsputn (const char* s, std::streamsize n)
{
// Use heuristic to decide whether to write directly or just use buffer
// Write directly only if n >= MIN(4096, available buffer capacity)
// (this is similar to what basic_filebuf does)
if (n < std::min<std::streamsize>(4096, epptr() - pptr())) {
// Not worth it to do a direct write
return std::streambuf::xsputn(s, n);
}
// Before we can do a direct write of this string, we need to flush
// out the current contents of the buffer.
if (pbase() != pptr()) {
overflow(traits_type::eof()); // throws an exception or it succeeds
}
// Now we can go ahead and write out the string.
size_t bytes_to_write = n;
while (bytes_to_write > 0) {
const size_t bytes_written = write_fun(handle, s, bytes_to_write);
bytes_to_write -= bytes_written;
s += bytes_written;
}
return n; // Return the total bytes written
}
std::streambuf* ofhbuf::setbuf (char* s, std::streamsize n)
{
if (s == 0 && n == 0) {
// Switch to unbuffered
// This won't take effect until the next overflow or sync
// (We defer it taking effect so that write errors can be properly reported)
// To cause it to take effect as soon as possible, we artificially reduce the
// size of the buffer so it has no space left. This will trigger an overflow
// on the next put.
std::streambuf::setp(pbase(), pptr());
std::streambuf::pbump(pptr() - pbase());
buffer_size = 1;
}
return this;
}
/*
* ifhstream
*/
ifhbuf::ifhbuf (void* arg_handle, size_t (*arg_read_fun)(void*, void*, size_t))
: handle(arg_handle),
read_fun(arg_read_fun),
buffer(new char[default_buffer_size + putback_size]),
buffer_size(default_buffer_size)
{
reset_buffer(0, 0);
}
ifhbuf::~ifhbuf ()
{
delete[] buffer;
}
ifhbuf::int_type ifhbuf::underflow ()
{
if (gptr() >= egptr()) { // A true underflow (no bytes in buffer left to read)
// Move the putback_size most-recently-read characters into the putback area
size_t nputback = std::min<size_t>(gptr() - eback(), putback_size);
std::memmove(buffer + (putback_size - nputback), gptr() - nputback, nputback);
// Now read new characters from the file descriptor
const size_t nread = read_fun(handle, buffer + putback_size, buffer_size);
if (nread == 0) {
// EOF
return traits_type::eof();
}
// Reset the buffer
reset_buffer(nputback, nread);
}
// Return the next character
return traits_type::to_int_type(*gptr());
}
std::streamsize ifhbuf::xsgetn (char* s, std::streamsize n)
{
// Use heuristic to decide whether to read directly
// Read directly only if n >= bytes_available + 4096
std::streamsize bytes_available = egptr() - gptr();
if (n < bytes_available + 4096) {
// Not worth it to do a direct read
return std::streambuf::xsgetn(s, n);
}
std::streamsize total_bytes_read = 0;
// First, copy out the bytes currently in the buffer
std::memcpy(s, gptr(), bytes_available);
s += bytes_available;
n -= bytes_available;
total_bytes_read += bytes_available;
// Now do the direct read
while (n > 0) {
const size_t bytes_read = read_fun(handle, s, n);
if (bytes_read == 0) {
// EOF
break;
}
s += bytes_read;
n -= bytes_read;
total_bytes_read += bytes_read;
}
// Fill up the putback area with the most recently read characters
size_t nputback = std::min<size_t>(total_bytes_read, putback_size);
std::memcpy(buffer + (putback_size - nputback), s - nputback, nputback);
// Reset the buffer with no bytes available for reading, but with some putback characters
reset_buffer(nputback, 0);
// Return the total number of bytes read
return total_bytes_read;
}
std::streambuf* ifhbuf::setbuf (char* s, std::streamsize n)
{
if (s == 0 && n == 0) {
// Switch to unbuffered
// This won't take effect until the next underflow (we don't want to
// lose what's currently in the buffer!)
buffer_size = 1;
}
return this;
}

134
fhstream.hpp Normal file
View File

@@ -0,0 +1,134 @@
/*
* Copyright (C) 2012, 2015 Andrew Ayer
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name(s) of the above copyright
* holders shall not be used in advertising or otherwise to promote the
* sale, use or other dealings in this Software without prior written
* authorization.
*/
#ifndef GIT_CRYPT_FHSTREAM_HPP
#define GIT_CRYPT_FHSTREAM_HPP
#include <ostream>
#include <istream>
#include <streambuf>
/*
* ofhstream
*/
class ofhbuf : public std::streambuf {
enum { default_buffer_size = 8192 };
void* handle;
size_t (*write_fun)(void*, const void*, size_t);
char* buffer;
size_t buffer_size;
inline void reset_buffer ()
{
std::streambuf::setp(buffer, buffer + buffer_size - 1);
}
static inline bool is_eof (int_type ch) { return traits_type::eq_int_type(ch, traits_type::eof()); }
// Disallow copy
#if __cplusplus >= 201103L /* C++11 */
ofhbuf (const ofhbuf&) = delete;
ofhbuf& operator= (const ofhbuf&) = delete;
#else
ofhbuf (const ofhbuf&);
ofhbuf& operator= (const ofhbuf&);
#endif
protected:
virtual int_type overflow (int_type ch =traits_type::eof());
virtual int sync ();
virtual std::streamsize xsputn (const char*, std::streamsize);
virtual std::streambuf* setbuf (char*, std::streamsize);
public:
ofhbuf (void*, size_t (*)(void*, const void*, size_t));
~ofhbuf (); // WARNING: calls sync() and ignores exceptions
};
class ofhstream : public std::ostream {
mutable ofhbuf buf;
public:
ofhstream (void* handle, size_t (*write_fun)(void*, const void*, size_t))
: std::ostream(0), buf(handle, write_fun)
{
std::ostream::rdbuf(&buf);
}
ofhbuf* rdbuf () const { return &buf; }
};
/*
* ifhstream
*/
class ifhbuf : public std::streambuf {
enum {
default_buffer_size = 8192,
putback_size = 4
};
void* handle;
size_t (*read_fun)(void*, void*, size_t);
char* buffer;
size_t buffer_size;
inline void reset_buffer (size_t nputback, size_t nread)
{
std::streambuf::setg(buffer + (putback_size - nputback), buffer + putback_size, buffer + putback_size + nread);
}
// Disallow copy
#if __cplusplus >= 201103L /* C++11 */
ifhbuf (const ifhbuf&) = delete;
ifhbuf& operator= (const ifhbuf&) = delete;
#else
ifhbuf (const ifhbuf&);
ifhbuf& operator= (const ifhbuf&);
#endif
protected:
virtual int_type underflow ();
virtual std::streamsize xsgetn (char*, std::streamsize);
virtual std::streambuf* setbuf (char*, std::streamsize);
public:
ifhbuf (void*, size_t (*)(void*, void*, size_t));
~ifhbuf (); // Can't fail
};
class ifhstream : public std::istream {
mutable ifhbuf buf;
public:
explicit ifhstream (void* handle, size_t (*read_fun)(void*, void*, size_t))
: std::istream(0), buf(handle, read_fun)
{
std::istream::rdbuf(&buf);
}
ifhbuf* rdbuf () const { return &buf; }
};
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 Andrew Ayer
* Copyright 2012, 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
@@ -28,59 +28,239 @@
* as that of the covered work.
*/
#include "git-crypt.hpp"
#include "commands.hpp"
#include "util.hpp"
#include "crypto.hpp"
#include "key.hpp"
#include "gpg.hpp"
#include "parse_options.hpp"
#include <cstring>
#include <unistd.h>
#include <iostream>
#include <string.h>
static void print_usage (const char* argv0)
const char* argv0;
static void print_usage (std::ostream& out)
{
std::clog << "Usage: " << argv0 << " COMMAND [ARGS ...]\n";
std::clog << "\n";
std::clog << "Valid commands:\n";
std::clog << " init KEYFILE - prepare the current git repo to use git-crypt with this key\n";
std::clog << " keygen KEYFILE - generate a git-crypt key in the given file\n";
std::clog << "\n";
std::clog << "Plumbing commands (not to be used directly):\n";
std::clog << " clean KEYFILE\n";
std::clog << " smudge KEYFILE\n";
std::clog << " diff KEYFILE FILE\n";
out << "Usage: " << argv0 << " COMMAND [ARGS ...]" << std::endl;
out << std::endl;
// |--------------------------------------------------------------------------------| 80 characters
out << "Common commands:" << std::endl;
out << " init generate a key and prepare repo to use git-crypt" << std::endl;
out << " status display which files are encrypted" << std::endl;
//out << " refresh ensure all files in the repo are properly decrypted" << std::endl;
out << " lock de-configure git-crypt and re-encrypt files in work tree" << std::endl;
out << std::endl;
out << "GPG commands:" << std::endl;
out << " add-gpg-user USERID add the user with the given GPG user ID as a collaborator" << std::endl;
//out << " rm-gpg-user USERID revoke collaborator status from the given GPG user ID" << std::endl;
//out << " ls-gpg-users list the GPG key IDs of collaborators" << std::endl;
out << " unlock decrypt this repo using the in-repo GPG-encrypted key" << std::endl;
out << std::endl;
out << "Symmetric key commands:" << std::endl;
out << " export-key FILE export this repo's symmetric key to the given file" << std::endl;
out << " unlock KEYFILE decrypt this repo using the given symmetric key" << std::endl;
out << std::endl;
out << "Legacy commands:" << std::endl;
out << " init KEYFILE alias for 'unlock KEYFILE'" << std::endl;
out << " keygen KEYFILE generate a git-crypt key in the given file" << std::endl;
out << " migrate-key OLD NEW migrate the legacy key file OLD to the new format in NEW" << std::endl;
/*
out << std::endl;
out << "Plumbing commands (not to be used directly):" << std::endl;
out << " clean [LEGACY-KEYFILE]" << std::endl;
out << " smudge [LEGACY-KEYFILE]" << std::endl;
out << " diff [LEGACY-KEYFILE] FILE" << std::endl;
*/
out << std::endl;
out << "See 'git-crypt help COMMAND' for more information on a specific command." << std::endl;
}
static void print_version (std::ostream& out)
{
out << "git-crypt " << VERSION << std::endl;
}
static bool help_for_command (const char* command, std::ostream& out)
{
if (std::strcmp(command, "init") == 0) {
help_init(out);
} else if (std::strcmp(command, "unlock") == 0) {
help_unlock(out);
} else if (std::strcmp(command, "lock") == 0) {
help_lock(out);
} else if (std::strcmp(command, "add-gpg-user") == 0) {
help_add_gpg_user(out);
} else if (std::strcmp(command, "rm-gpg-user") == 0) {
help_rm_gpg_user(out);
} else if (std::strcmp(command, "ls-gpg-users") == 0) {
help_ls_gpg_users(out);
} else if (std::strcmp(command, "export-key") == 0) {
help_export_key(out);
} else if (std::strcmp(command, "keygen") == 0) {
help_keygen(out);
} else if (std::strcmp(command, "migrate-key") == 0) {
help_migrate_key(out);
} else if (std::strcmp(command, "refresh") == 0) {
help_refresh(out);
} else if (std::strcmp(command, "status") == 0) {
help_status(out);
} else {
return false;
}
return true;
}
static int help (int argc, const char** argv)
{
if (argc == 0) {
print_usage(std::cout);
} else {
if (!help_for_command(argv[0], std::cout)) {
std::clog << "Error: '" << argv[0] << "' is not a git-crypt command. See 'git-crypt help'." << std::endl;
return 1;
}
}
return 0;
}
static int version (int argc, const char** argv)
{
print_version(std::cout);
return 0;
}
int main (int argc, const char** argv)
try {
// The following two lines are essential for achieving good performance:
std::ios_base::sync_with_stdio(false);
std::cin.tie(0);
argv0 = argv[0];
std::cin.exceptions(std::ios_base::badbit);
std::cout.exceptions(std::ios_base::badbit);
/*
* General initialization
*/
if (argc < 3) {
print_usage(argv[0]);
init_std_streams();
init_crypto();
/*
* Parse command line arguments
*/
int arg_index = 1;
while (arg_index < argc && argv[arg_index][0] == '-') {
if (std::strcmp(argv[arg_index], "--help") == 0) {
print_usage(std::clog);
return 0;
} else if (std::strcmp(argv[arg_index], "--version") == 0) {
print_version(std::clog);
return 0;
} else if (std::strcmp(argv[arg_index], "--") == 0) {
++arg_index;
break;
} else {
std::clog << argv0 << ": " << argv[arg_index] << ": Unknown option" << std::endl;
print_usage(std::clog);
return 2;
}
}
argc -= arg_index;
argv += arg_index;
if (argc == 0) {
print_usage(std::clog);
return 2;
}
/*
* Pass off to command handler
*/
const char* command = argv[0];
--argc;
++argv;
if (strcmp(argv[1], "init") == 0 && argc == 3) {
init(argv[0], argv[2]);
} else if (strcmp(argv[1], "keygen") == 0 && argc == 3) {
keygen(argv[2]);
} else if (strcmp(argv[1], "clean") == 0 && argc == 3) {
clean(argv[2]);
} else if (strcmp(argv[1], "smudge") == 0 && argc == 3) {
smudge(argv[2]);
} else if (strcmp(argv[1], "diff") == 0 && argc == 4) {
diff(argv[2], argv[3]);
} else {
print_usage(argv[0]);
try {
// Public commands:
if (std::strcmp(command, "help") == 0) {
return help(argc, argv);
}
if (std::strcmp(command, "version") == 0) {
return version(argc, argv);
}
if (std::strcmp(command, "init") == 0) {
return init(argc, argv);
}
if (std::strcmp(command, "unlock") == 0) {
return unlock(argc, argv);
}
if (std::strcmp(command, "lock") == 0) {
return lock(argc, argv);
}
if (std::strcmp(command, "add-gpg-user") == 0) {
return add_gpg_user(argc, argv);
}
if (std::strcmp(command, "rm-gpg-user") == 0) {
return rm_gpg_user(argc, argv);
}
if (std::strcmp(command, "ls-gpg-users") == 0) {
return ls_gpg_users(argc, argv);
}
if (std::strcmp(command, "export-key") == 0) {
return export_key(argc, argv);
}
if (std::strcmp(command, "keygen") == 0) {
return keygen(argc, argv);
}
if (std::strcmp(command, "migrate-key") == 0) {
return migrate_key(argc, argv);
}
if (std::strcmp(command, "refresh") == 0) {
return refresh(argc, argv);
}
if (std::strcmp(command, "status") == 0) {
return status(argc, argv);
}
// Plumbing commands (executed by git, not by user):
if (std::strcmp(command, "clean") == 0) {
return clean(argc, argv);
}
if (std::strcmp(command, "smudge") == 0) {
return smudge(argc, argv);
}
if (std::strcmp(command, "diff") == 0) {
return diff(argc, argv);
}
} catch (const Option_error& e) {
std::clog << "git-crypt: Error: " << e.option_name << ": " << e.message << std::endl;
help_for_command(command, std::clog);
return 2;
}
return 0;
std::clog << "Error: '" << command << "' is not a git-crypt command. See 'git-crypt help'." << std::endl;
return 2;
} catch (const Error& e) {
std::cerr << "git-crypt: Error: " << e.message << std::endl;
return 1;
} catch (const Gpg_error& e) {
std::cerr << "git-crypt: GPG error: " << e.message << std::endl;
return 1;
} catch (const System_error& e) {
std::cerr << "git-crypt: System error: " << e.message() << std::endl;
return 1;
} catch (const Crypto_error& e) {
std::cerr << "git-crypt: Crypto error: " << e.where << ": " << e.message << std::endl;
return 1;
} catch (Key_file::Incompatible) {
std::cerr << "git-crypt: This repository contains a incompatible key file. Please upgrade git-crypt." << std::endl;
return 1;
} catch (Key_file::Malformed) {
std::cerr << "git-crypt: This repository contains a malformed key file. It may be corrupted." << std::endl;
return 1;
} catch (const std::ios_base::failure& e) {
std::cerr << "git-crypt: I/O error: " << e.what() << std::endl;
return 1;
}

38
git-crypt.hpp Normal file
View File

@@ -0,0 +1,38 @@
/*
* Copyright 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
* git-crypt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* git-crypt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 GIT_CRYPT_GIT_CRYPT_HPP
#define GIT_CRYPT_GIT_CRYPT_HPP
#define VERSION "0.5.0"
extern const char* argv0; // initialized in main() to argv[0]
#endif

185
gpg.cpp Normal file
View File

@@ -0,0 +1,185 @@
/*
* Copyright 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
* git-crypt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* git-crypt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "gpg.hpp"
#include "util.hpp"
#include <sstream>
static std::string gpg_nth_column (const std::string& line, unsigned int col)
{
std::string::size_type pos = 0;
for (unsigned int i = 0; i < col; ++i) {
pos = line.find_first_of(':', pos);
if (pos == std::string::npos) {
throw Gpg_error("Malformed output from gpg");
}
pos = pos + 1;
}
const std::string::size_type end_pos = line.find_first_of(':', pos);
return end_pos != std::string::npos ?
line.substr(pos, end_pos - pos) :
line.substr(pos);
}
// given a key fingerprint, return the last 8 nibbles
std::string gpg_shorten_fingerprint (const std::string& fingerprint)
{
return fingerprint.size() == 40 ? fingerprint.substr(32) : fingerprint;
}
// given a key fingerprint, return the key's UID (e.g. "John Smith <jsmith@example.com>")
std::string gpg_get_uid (const std::string& fingerprint)
{
// gpg --batch --with-colons --fixed-list-mode --list-keys 0x7A399B2DB06D039020CD1CE1D0F3702D61489532
std::vector<std::string> command;
command.push_back("gpg");
command.push_back("--batch");
command.push_back("--with-colons");
command.push_back("--fixed-list-mode");
command.push_back("--list-keys");
command.push_back("0x" + fingerprint);
std::stringstream command_output;
if (!successful_exit(exec_command(command, command_output))) {
// This could happen if the keyring does not contain a public key with this fingerprint
return "";
}
while (command_output.peek() != -1) {
std::string line;
std::getline(command_output, line);
if (line.substr(0, 4) == "uid:") {
// uid:u::::1395975462::AB97D6E3E5D8789988CA55E5F77D9E7397D05229::John Smith <jsmith@example.com>:
// want the 9th column (counting from 0)
return gpg_nth_column(line, 9);
}
}
return "";
}
// return a list of fingerprints of public keys matching the given search query (such as jsmith@example.com)
std::vector<std::string> gpg_lookup_key (const std::string& query)
{
std::vector<std::string> fingerprints;
// gpg --batch --with-colons --fingerprint --list-keys jsmith@example.com
std::vector<std::string> command;
command.push_back("gpg");
command.push_back("--batch");
command.push_back("--with-colons");
command.push_back("--fingerprint");
command.push_back("--list-keys");
command.push_back(query);
std::stringstream command_output;
if (successful_exit(exec_command(command, command_output))) {
bool is_pubkey = false;
while (command_output.peek() != -1) {
std::string line;
std::getline(command_output, line);
if (line.substr(0, 4) == "pub:") {
is_pubkey = true;
} else if (line.substr(0, 4) == "sub:") {
is_pubkey = false;
} else if (is_pubkey && line.substr(0, 4) == "fpr:") {
// fpr:::::::::7A399B2DB06D039020CD1CE1D0F3702D61489532:
// want the 9th column (counting from 0)
fingerprints.push_back(gpg_nth_column(line, 9));
}
}
}
return fingerprints;
}
std::vector<std::string> gpg_list_secret_keys ()
{
// gpg --batch --with-colons --list-secret-keys --fingerprint
std::vector<std::string> command;
command.push_back("gpg");
command.push_back("--batch");
command.push_back("--with-colons");
command.push_back("--list-secret-keys");
command.push_back("--fingerprint");
std::stringstream command_output;
if (!successful_exit(exec_command(command, command_output))) {
throw Gpg_error("gpg --list-secret-keys failed");
}
std::vector<std::string> secret_keys;
while (command_output.peek() != -1) {
std::string line;
std::getline(command_output, line);
if (line.substr(0, 4) == "fpr:") {
// fpr:::::::::7A399B2DB06D039020CD1CE1D0F3702D61489532:
// want the 9th column (counting from 0)
secret_keys.push_back(gpg_nth_column(line, 9));
}
}
return secret_keys;
}
void gpg_encrypt_to_file (const std::string& filename, const std::string& recipient_fingerprint, bool key_is_trusted, const char* p, size_t len)
{
// gpg --batch -o FILENAME -r RECIPIENT -e
std::vector<std::string> command;
command.push_back("gpg");
command.push_back("--batch");
if (key_is_trusted) {
command.push_back("--trust-model");
command.push_back("always");
}
command.push_back("-o");
command.push_back(filename);
command.push_back("-r");
command.push_back("0x" + recipient_fingerprint);
command.push_back("-e");
if (!successful_exit(exec_command_with_input(command, p, len))) {
throw Gpg_error("Failed to encrypt");
}
}
void gpg_decrypt_from_file (const std::string& filename, std::ostream& output)
{
// gpg -q -d FILENAME
std::vector<std::string> command;
command.push_back("gpg");
command.push_back("-q");
command.push_back("-d");
command.push_back(filename);
if (!successful_exit(exec_command(command, output))) {
throw Gpg_error("Failed to decrypt");
}
}

51
gpg.hpp Normal file
View File

@@ -0,0 +1,51 @@
/*
* Copyright 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
* git-crypt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* git-crypt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 GIT_CRYPT_GPG_HPP
#define GIT_CRYPT_GPG_HPP
#include <string>
#include <vector>
#include <cstddef>
struct Gpg_error {
std::string message;
explicit Gpg_error (std::string m) : message(m) { }
};
std::string gpg_shorten_fingerprint (const std::string& fingerprint);
std::string gpg_get_uid (const std::string& fingerprint);
std::vector<std::string> gpg_lookup_key (const std::string& query);
std::vector<std::string> gpg_list_secret_keys ();
void gpg_encrypt_to_file (const std::string& filename, const std::string& recipient_fingerprint, bool key_is_trusted, const char* p, size_t len);
void gpg_decrypt_from_file (const std::string& filename, std::ostream&);
#endif

336
key.cpp Normal file
View File

@@ -0,0 +1,336 @@
/*
* Copyright 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
* git-crypt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* git-crypt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "key.hpp"
#include "util.hpp"
#include "crypto.hpp"
#include <sys/types.h>
#include <sys/stat.h>
#include <stdint.h>
#include <fstream>
#include <istream>
#include <ostream>
#include <sstream>
#include <cstring>
#include <stdexcept>
#include <vector>
Key_file::Entry::Entry ()
{
version = 0;
explicit_memset(aes_key, 0, AES_KEY_LEN);
explicit_memset(hmac_key, 0, HMAC_KEY_LEN);
}
void Key_file::Entry::load (std::istream& in)
{
while (true) {
uint32_t field_id;
if (!read_be32(in, field_id)) {
throw Malformed();
}
if (field_id == KEY_FIELD_END) {
break;
}
uint32_t field_len;
if (!read_be32(in, field_len)) {
throw Malformed();
}
if (field_id == KEY_FIELD_VERSION) {
if (field_len != 4) {
throw Malformed();
}
if (!read_be32(in, version)) {
throw Malformed();
}
} else if (field_id == KEY_FIELD_AES_KEY) {
if (field_len != AES_KEY_LEN) {
throw Malformed();
}
in.read(reinterpret_cast<char*>(aes_key), AES_KEY_LEN);
if (in.gcount() != AES_KEY_LEN) {
throw Malformed();
}
} else if (field_id == KEY_FIELD_HMAC_KEY) {
if (field_len != HMAC_KEY_LEN) {
throw Malformed();
}
in.read(reinterpret_cast<char*>(hmac_key), HMAC_KEY_LEN);
if (in.gcount() != HMAC_KEY_LEN) {
throw Malformed();
}
} else if (field_id & 1) { // unknown critical field
throw Incompatible();
} else {
// unknown non-critical field - safe to ignore
if (field_len > MAX_FIELD_LEN) {
throw Malformed();
}
in.ignore(field_len);
if (in.gcount() != static_cast<std::streamsize>(field_len)) {
throw Malformed();
}
}
}
}
void Key_file::Entry::load_legacy (uint32_t arg_version, std::istream& in)
{
version = arg_version;
// First comes the AES key
in.read(reinterpret_cast<char*>(aes_key), AES_KEY_LEN);
if (in.gcount() != AES_KEY_LEN) {
throw Malformed();
}
// Then the HMAC key
in.read(reinterpret_cast<char*>(hmac_key), HMAC_KEY_LEN);
if (in.gcount() != HMAC_KEY_LEN) {
throw Malformed();
}
if (in.peek() != -1) {
// Trailing data is a good indication that we are not actually reading a
// legacy key file. (This is important to check since legacy key files
// did not have any sort of file header.)
throw Malformed();
}
}
void Key_file::Entry::store (std::ostream& out) const
{
// Version
write_be32(out, KEY_FIELD_VERSION);
write_be32(out, 4);
write_be32(out, version);
// AES key
write_be32(out, KEY_FIELD_AES_KEY);
write_be32(out, AES_KEY_LEN);
out.write(reinterpret_cast<const char*>(aes_key), AES_KEY_LEN);
// HMAC key
write_be32(out, KEY_FIELD_HMAC_KEY);
write_be32(out, HMAC_KEY_LEN);
out.write(reinterpret_cast<const char*>(hmac_key), HMAC_KEY_LEN);
// End
write_be32(out, KEY_FIELD_END);
}
void Key_file::Entry::generate (uint32_t arg_version)
{
version = arg_version;
random_bytes(aes_key, AES_KEY_LEN);
random_bytes(hmac_key, HMAC_KEY_LEN);
}
const Key_file::Entry* Key_file::get_latest () const
{
return is_filled() ? get(latest()) : 0;
}
const Key_file::Entry* Key_file::get (uint32_t version) const
{
Map::const_iterator it(entries.find(version));
return it != entries.end() ? &it->second : 0;
}
void Key_file::add (const Entry& entry)
{
entries[entry.version] = entry;
}
void Key_file::load_legacy (std::istream& in)
{
entries[0].load_legacy(0, in);
}
void Key_file::load (std::istream& in)
{
unsigned char preamble[16];
in.read(reinterpret_cast<char*>(preamble), 16);
if (in.gcount() != 16) {
throw Malformed();
}
if (std::memcmp(preamble, "\0GITCRYPTKEY", 12) != 0) {
throw Malformed();
}
if (load_be32(preamble + 12) != FORMAT_VERSION) {
throw Incompatible();
}
load_header(in);
while (in.peek() != -1) {
Entry entry;
entry.load(in);
add(entry);
}
}
void Key_file::load_header (std::istream& in)
{
while (true) {
uint32_t field_id;
if (!read_be32(in, field_id)) {
throw Malformed();
}
if (field_id == HEADER_FIELD_END) {
break;
}
uint32_t field_len;
if (!read_be32(in, field_len)) {
throw Malformed();
}
if (field_id == HEADER_FIELD_KEY_NAME) {
if (field_len > KEY_NAME_MAX_LEN) {
throw Malformed();
}
if (field_len == 0) {
// special case field_len==0 to avoid possible undefined behavior
// edge cases with an empty std::vector (particularly, &bytes[0]).
key_name.clear();
} else {
std::vector<char> bytes(field_len);
in.read(&bytes[0], field_len);
if (in.gcount() != static_cast<std::streamsize>(field_len)) {
throw Malformed();
}
key_name.assign(&bytes[0], field_len);
}
if (!validate_key_name(key_name.c_str())) {
key_name.clear();
throw Malformed();
}
} else if (field_id & 1) { // unknown critical field
throw Incompatible();
} else {
// unknown non-critical field - safe to ignore
if (field_len > MAX_FIELD_LEN) {
throw Malformed();
}
in.ignore(field_len);
if (in.gcount() != static_cast<std::streamsize>(field_len)) {
throw Malformed();
}
}
}
}
void Key_file::store (std::ostream& out) const
{
out.write("\0GITCRYPTKEY", 12);
write_be32(out, FORMAT_VERSION);
if (!key_name.empty()) {
write_be32(out, HEADER_FIELD_KEY_NAME);
write_be32(out, key_name.size());
out.write(key_name.data(), key_name.size());
}
write_be32(out, HEADER_FIELD_END);
for (Map::const_iterator it(entries.begin()); it != entries.end(); ++it) {
it->second.store(out);
}
}
bool Key_file::load_from_file (const char* key_file_name)
{
std::ifstream key_file_in(key_file_name, std::fstream::binary);
if (!key_file_in) {
return false;
}
load(key_file_in);
return true;
}
bool Key_file::store_to_file (const char* key_file_name) const
{
create_protected_file(key_file_name);
std::ofstream key_file_out(key_file_name, std::fstream::binary);
if (!key_file_out) {
return false;
}
store(key_file_out);
key_file_out.close();
if (!key_file_out) {
return false;
}
return true;
}
std::string Key_file::store_to_string () const
{
std::ostringstream ss;
store(ss);
return ss.str();
}
void Key_file::generate ()
{
uint32_t version(is_empty() ? 0 : latest() + 1);
entries[version].generate(version);
}
uint32_t Key_file::latest () const
{
if (is_empty()) {
throw std::invalid_argument("Key_file::latest");
}
return entries.begin()->first;
}
bool validate_key_name (const char* key_name, std::string* reason)
{
if (!*key_name) {
if (reason) { *reason = "Key name may not be empty"; }
return false;
}
if (std::strcmp(key_name, "default") == 0) {
if (reason) { *reason = "`default' is not a legal key name"; }
return false;
}
// Need to be restrictive with key names because they're used as part of a Git filter name
size_t len = 0;
while (char c = *key_name++) {
if (!std::isalnum(c) && c != '-' && c != '_') {
if (reason) { *reason = "Key names may contain only A-Z, a-z, 0-9, '-', and '_'"; }
return false;
}
if (++len > KEY_NAME_MAX_LEN) {
if (reason) { *reason = "Key name is too long"; }
return false;
}
}
return true;
}

116
key.hpp Normal file
View File

@@ -0,0 +1,116 @@
/*
* Copyright 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
* git-crypt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* git-crypt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 GIT_CRYPT_KEY_HPP
#define GIT_CRYPT_KEY_HPP
#include <map>
#include <functional>
#include <stdint.h>
#include <iosfwd>
#include <string>
enum {
HMAC_KEY_LEN = 64,
AES_KEY_LEN = 32
};
struct Key_file {
public:
struct Entry {
uint32_t version;
unsigned char aes_key[AES_KEY_LEN];
unsigned char hmac_key[HMAC_KEY_LEN];
Entry ();
void load (std::istream&);
void load_legacy (uint32_t version, std::istream&);
void store (std::ostream&) const;
void generate (uint32_t version);
};
struct Malformed { }; // exception class
struct Incompatible { }; // exception class
const Entry* get_latest () const;
const Entry* get (uint32_t version) const;
void add (const Entry&);
void load_legacy (std::istream&);
void load (std::istream&);
void store (std::ostream&) const;
bool load_from_file (const char* filename);
bool store_to_file (const char* filename) const;
std::string store_to_string () const;
void generate ();
bool is_empty () const { return entries.empty(); }
bool is_filled () const { return !is_empty(); }
uint32_t latest () const;
void set_key_name (const char* k) { key_name = k ? k : ""; }
const char* get_key_name () const { return key_name.empty() ? 0 : key_name.c_str(); }
private:
typedef std::map<uint32_t, Entry, std::greater<uint32_t> > Map;
enum { FORMAT_VERSION = 2 };
Map entries;
std::string key_name;
void load_header (std::istream&);
enum {
HEADER_FIELD_END = 0,
HEADER_FIELD_KEY_NAME = 1
};
enum {
KEY_FIELD_END = 0,
KEY_FIELD_VERSION = 1,
KEY_FIELD_AES_KEY = 3,
KEY_FIELD_HMAC_KEY = 5
};
enum {
MAX_FIELD_LEN = 1<<20
};
};
enum {
KEY_NAME_MAX_LEN = 128
};
bool validate_key_name (const char* key_name, std::string* reason =0);
#endif

493
man/git-crypt.xml Normal file
View File

@@ -0,0 +1,493 @@
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<refentry>
<!--
Copyright (c) 2015 Andrew Ayer
See COPYING file for license information.
-->
<refentryinfo>
<title>git-crypt</title>
<date>2015-05-30</date>
<productname>git-crypt 0.5.0</productname>
<author>
<othername>Andrew Ayer</othername>
<contrib></contrib>
<email>agwa@andrewayer.name</email>
<uri>https://www.agwa.name</uri>
</author>
</refentryinfo>
<refmeta>
<refentrytitle>git-crypt</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>git-crypt</refname>
<refpurpose>transparent file encryption in Git</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>git-crypt <arg choice="opt"><replaceable>OPTIONS</replaceable></arg> <arg choice="plain"><replaceable>COMMAND</replaceable></arg> <arg choice="opt" rep="repeat"><replaceable>ARGS</replaceable></arg></command>
</cmdsynopsis>
</refsynopsisdiv>
<refsynopsisdiv>
<title>Common commands</title>
<cmdsynopsis>
<command>git-crypt init</command>
</cmdsynopsis>
<cmdsynopsis>
<command>git-crypt status</command>
</cmdsynopsis>
<cmdsynopsis>
<command>git-crypt lock</command>
</cmdsynopsis>
</refsynopsisdiv>
<refsynopsisdiv>
<title>GPG commands</title>
<cmdsynopsis>
<command>git-crypt add-gpg-user <arg choice="plain"><replaceable>GPG_USER_ID</replaceable></arg></command>
</cmdsynopsis>
<cmdsynopsis>
<command>git-crypt unlock</command>
</cmdsynopsis>
</refsynopsisdiv>
<refsynopsisdiv>
<title>Symmetric key commands</title>
<cmdsynopsis>
<command>git-crypt export-key <arg choice="plain"><replaceable>OUTPUT_KEY_FILE</replaceable></arg></command>
</cmdsynopsis>
<cmdsynopsis>
<command>git-crypt unlock <arg choice="plain"><replaceable>KEY_FILE</replaceable></arg></command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<command>git-crypt</command> enables transparent encryption and decryption
of files in a git repository. Files which you choose to protect are encrypted when committed,
and decrypted when checked out. git-crypt lets you freely share a repository containing a mix of
public and private content. git-crypt gracefully degrades, so developers without the secret key
can still clone and commit to a repository with encrypted files. This lets you store your secret
material (such as keys or passwords) in the same repository as your code, without requiring you
to lock down your entire repository.
</para>
</refsect1>
<refsect1>
<title>Commands</title>
<para>
<command>git-crypt</command> is logically divided into several sub-commands which
perform distinct tasks. Each sub-command, and its arguments,
are documented below. Note that arguments and options to sub-commands must be
specified on the command line <emphasis>after</emphasis> the name of the sub-command.
</para>
<variablelist>
<varlistentry>
<term><option>init <arg choice="opt"><replaceable>OPTIONS</replaceable></arg></option></term>
<listitem>
<para>
Generate a key and prepare the current Git repository to use git-crypt.
</para>
<para>
The following options are understood:
</para>
<variablelist>
<varlistentry>
<term><option>-k</option> <replaceable>KEY_NAME</replaceable></term>
<term><option>--key-name</option> <replaceable>KEY_NAME</replaceable></term>
<listitem>
<para>
Initialize the given key instead of the default key. git-crypt
supports multiple keys per repository, allowing you to share
different files with different sets of collaborators.
</para>
</listitem>
</varlistentry>
</variablelist>
</listitem>
</varlistentry>
<varlistentry>
<term><option>status <arg choice="opt"><replaceable>OPTIONS</replaceable></arg></option></term>
<listitem>
<para>
Display a list of files in the repository, with their status (encrypted or unencrypted).
</para>
<para>
The following options are understood:
</para>
<variablelist>
<varlistentry>
<term><option>-e</option></term>
<listitem>
<para>
Show only encrypted files.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-u</option></term>
<listitem>
<para>
Show only unencrypted files.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-f</option></term>
<term><option>--fix</option></term>
<listitem>
<para>
Encrypt files that should be encrypted but were
committed to the repository or added to the index
without encryption. (This can happen if a file
is added before git-crypt is initialized or before
the file is added to the gitattributes file.)
</para>
</listitem>
</varlistentry>
</variablelist>
</listitem>
</varlistentry>
<varlistentry>
<term><option>add-gpg-user <arg choice="opt"><replaceable>OPTIONS</replaceable></arg> <arg choice="plain" rep="repeat"><replaceable>GPG_USER_ID</replaceable></arg></option></term>
<listitem>
<para>
Add the users with the given GPG user IDs as collaborators. Specifically,
git-crypt uses <citerefentry><refentrytitle>gpg</refentrytitle><manvolnum>1</manvolnum></citerefentry>
to encrypt the shared symmetric key
to the public keys of each GPG user ID, and stores the GPG-encrypted
keys in the <filename>.git-crypt</filename> directory at the root of the repository.
</para>
<para>
<replaceable>GPG_USER_ID</replaceable> can be a key ID, a full fingerprint, an email address, or anything
else that uniquely identifies a public key to GPG (see "HOW TO SPECIFY
A USER ID" in the <citerefentry><refentrytitle>gpg</refentrytitle><manvolnum>1</manvolnum></citerefentry>
man page).
</para>
<para>
The following options are understood:
</para>
<variablelist>
<varlistentry>
<term><option>-k</option> <replaceable>KEY_NAME</replaceable></term>
<term><option>--key-name</option> <replaceable>KEY_NAME</replaceable></term>
<listitem>
<para>
Grant access to the given key, rather than the default key.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-n</option></term>
<term><option>--no-commit</option></term>
<listitem>
<para>
Don't automatically commit the changes to the <filename>.git-crypt</filename>
directory.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--trusted</option></term>
<listitem>
<para>
Assume that the GPG keys specified on the command line are trusted;
i.e. they actually belong to the users that they claim to belong to.
</para>
<para>
Without this option, git-crypt uses the same trust model as GPG,
which is based on the Web of Trust by default. Under this
model, git-crypt will reject GPG keys that do not have
trusted signatures.
</para>
<para>
If you don't want to use the Web of Trust, you can either change
GPG's trust model by setting the <option>trust-model</option>
option in <filename>~/.gnupg/gpg.conf</filename> (see
<citerefentry><refentrytitle>gpg</refentrytitle><manvolnum>1</manvolnum></citerefentry>),
or use the <option>--trusted</option> option to <command>add-gpg-user</command>
on a case-by-case basis.
</para>
</listitem>
</varlistentry>
</variablelist>
</listitem>
</varlistentry>
<varlistentry>
<term><option>unlock <!--<arg choice="opt"><replaceable>OPTIONS</replaceable></arg> --><arg choice="opt" rep="repeat"><replaceable>KEY_FILE</replaceable></arg></option></term>
<listitem>
<para>
Decrypt the repository. If one or more key files are specified on the command line,
git-crypt attempts to decrypt using those shared symmetric keys. If no key files
are specified, git-crypt attempts to decrypt using a GPG-encrypted key stored in
the repository's .git-crypt directory.
</para>
<para>
This command takes no options.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>export-key <arg choice="opt"><replaceable>OPTIONS</replaceable></arg> <arg choice="plain"><replaceable>FILENAME</replaceable></arg></option></term>
<listitem>
<para>
Export the repository's shared symmetric key to the given file.
</para>
<para>
The following options are understood:
</para>
<variablelist>
<varlistentry>
<term><option>-k</option> <replaceable>KEY_NAME</replaceable></term>
<term><option>--key-name</option> <replaceable>KEY_NAME</replaceable></term>
<listitem>
<para>
Export the given key, rather than the default key.
</para>
</listitem>
</varlistentry>
</variablelist>
</listitem>
</varlistentry>
<varlistentry>
<term><option>help <arg choice="opt"><replaceable>COMMAND</replaceable></arg></option></term>
<listitem>
<para>
Display help for the given <arg choice="plain"><replaceable>COMMAND</replaceable></arg>,
or an overview of all commands if no command is specified.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>version</option></term>
<listitem>
<para>
Print the currently-installed version of <command>git-crypt</command>.
The format of the output is always "git-crypt", followed by a space,
followed by the dotted version number.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Using git-crypt</title>
<para>
First, you prepare a repository to use git-crypt by running <command>git-crypt init</command>.
</para>
<para>
Then, you specify the files to encrypt by creating a
<citerefentry><refentrytitle>gitattributes</refentrytitle><manvolnum>5</manvolnum></citerefentry> file.
Each file which you want to encrypt should be assigned the "<literal>filter=git-crypt diff=git-crypt</literal>"
attributes. For example:
</para>
<screen>secretfile filter=git-crypt diff=git-crypt&#10;*.key filter=git-crypt diff=git-crypt</screen>
<para>
Like a <filename>.gitignore</filename> file, <filename>.gitattributes</filename> files can match wildcards and
should be checked into the repository. Make sure you don't accidentally encrypt the
<filename>.gitattributes</filename> file itself (or other git files like <filename>.gitignore</filename>
or <filename>.gitmodules</filename>). Make sure your <filename>.gitattributes</filename> rules
are in place <emphasis>before</emphasis> you add sensitive files, or those files won't be encrypted!
</para>
<para>
To share the repository with others (or with yourself) using GPG, run:
</para>
<screen>git-crypt add-gpg-user <replaceable>GPG_USER_ID</replaceable></screen>
<para>
<replaceable>GPG_USER_ID</replaceable> can be a key ID, a full fingerprint, an email address, or anything
else that uniquely identifies a public key to GPG. Note: <command>git-crypt add-gpg-user</command> will
add and commit a GPG-encrypted key file in the <filename>.git-crypt</filename> directory of
the root of your repository.
</para>
<para>
Alternatively, you can export a symmetric secret key, which you must
securely convey to collaborators (GPG is not required, and no files
are added to your repository):
</para>
<screen>git-crypt export-key <replaceable>/path/to/key</replaceable></screen>
<para>
After cloning a repository with encrypted files, unlock with with GPG:
</para>
<screen>git-crypt unlock</screen>
<para>
Or with a symmetric key:
</para>
<screen>git-crypt unlock /path/to/key</screen>
<para>
That's all you need to do - after git-crypt is set up (either with
<command>git-crypt init</command> or <command>git-crypt unlock</command>),
you can use git normally - encryption and decryption happen transparently.
</para>
</refsect1>
<refsect1>
<title>The .gitattributes file</title>
<para>
The <filename>.gitattributes</filename> file is documented in
<citerefentry><refentrytitle>gitattributes</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
The file pattern format is the same as the one used by <filename>.gitignore</filename>,
as documented in <citerefentry><refentrytitle>gitignore</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
with the exception that specifying merely a directory (e.g. "<literal>/dir/</literal>")
is <emphasis>not</emphasis> sufficient to encrypt all files beneath it.
</para>
<para>
Also note that the pattern "<literal>dir/*</literal>" does not match files under
sub-directories of dir/. To encrypt an entire sub-tree dir/, place the
following in <filename>dir/.gitattributes</filename>:
</para>
<screen>* filter=git-crypt diff=git-crypt&#10;.gitattributes !filter !diff</screen>
<para>
The second pattern is essential for ensuring that <filename>.gitattributes</filename> itself
is not encrypted.
</para>
</refsect1>
<refsect1>
<title>Multiple Key Support</title>
<para>
In addition to the implicit default key, git-crypt supports alternative
keys which can be used to encrypt specific files and can be shared with
specific GPG users. This is useful if you want to grant different
collaborators access to different sets of files.
</para>
<para>
To generate an alternative key named <replaceable>KEYNAME</replaceable>,
pass the <command>-k <replaceable>KEYNAME</replaceable></command>
option to <command>git-crypt init</command> as follows:
</para>
<screen>git-crypt init -k <replaceable>KEYNAME</replaceable></screen>
<para>
To encrypt a file with an alternative key, use the <literal>git-crypt-<replaceable>KEYNAME</replaceable></literal>
filter in <filename>.gitattributes</filename> as follows:
</para>
<screen><replaceable>secretfile</replaceable> filter=git-crypt-<replaceable>KEYNAME</replaceable> diff=git-crypt-<replaceable>KEYNAME</replaceable></screen>
<para>
To export an alternative key or share it with a GPG user, pass the
<command>-k <replaceable>KEYNAME</replaceable></command> option to
<command>git-crypt export-key</command> or <command>git-crypt add-gpg-user</command>
as follows:
</para>
<screen>git-crypt export-key -k <replaceable>KEYNAME</replaceable> <filename><replaceable>/path/to/keyfile</replaceable></filename>&#10;git-crypt add-gpg-user -k <replaceable>KEYNAME</replaceable> <replaceable>GPG_USER_ID</replaceable></screen>
<para>
To unlock a repository with an alternative key, use <command>git-crypt unlock</command>
normally. git-crypt will automatically determine which key is being used.
</para>
</refsect1>
<!--
<refsect1>
<title>Global options</title>
<para>
The following options are understood by <command>git-crypt</command> and can
be used with any sub-command. Since they apply globally
to <command>git-crypt</command>, they must be specified on the command line
<emphasis>before</emphasis> the sub-command name.
</para>
<variablelist>
</variablelist>
</refsect1>
-->
<!--
<refsect1>
<title>Files</title>
<variablelist>
<varlistentry>
<term><filename>/path/to/file</filename></term>
<listitem><para>Description.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
-->
<!--
<refsect1>
<title>Environment Variables</title>
<variablelist class='environment-variables'>
<varlistentry>
<term><varname>NAME</varname></term>
<listitem>
<para>Description.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
-->
<!-- TODO: examples section
<refsect1>
<title>Examples</title>
<para>Hello world?</para>
</refsect1>
-->
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>git</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>gitattributes</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<ulink url="https://www.agwa.name/projects/git-crypt">git-crypt home page</ulink>,
<ulink url="https://github.com/AGWA/git-crypt">GitHub repository</ulink>
</para>
</refsect1>
</refentry>

1
man/man1/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
git-crypt.1

115
parse_options.cpp Normal file
View File

@@ -0,0 +1,115 @@
/*
* Copyright 2014 Andrew Ayer
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name(s) of the above copyright
* holders shall not be used in advertising or otherwise to promote the
* sale, use or other dealings in this Software without prior written
* authorization.
*/
#include "parse_options.hpp"
#include <cstring>
static const Option_def* find_option (const Options_list& options, const std::string& name)
{
for (Options_list::const_iterator opt(options.begin()); opt != options.end(); ++opt) {
if (opt->name == name) {
return &*opt;
}
}
return 0;
}
int parse_options (const Options_list& options, int argc, const char** argv)
{
int argi = 0;
while (argi < argc && argv[argi][0] == '-') {
if (std::strcmp(argv[argi], "--") == 0) {
++argi;
break;
} else if (std::strncmp(argv[argi], "--", 2) == 0) {
std::string option_name;
const char* option_value = 0;
if (const char* eq = std::strchr(argv[argi], '=')) {
option_name.assign(argv[argi], eq);
option_value = eq + 1;
} else {
option_name = argv[argi];
}
++argi;
const Option_def* opt(find_option(options, option_name));
if (!opt) {
throw Option_error(option_name, "Invalid option");
}
if (opt->is_set) {
*opt->is_set = true;
}
if (opt->value) {
if (option_value) {
*opt->value = option_value;
} else {
if (argi >= argc) {
throw Option_error(option_name, "Option requires a value");
}
*opt->value = argv[argi];
++argi;
}
} else {
if (option_value) {
throw Option_error(option_name, "Option takes no value");
}
}
} else {
const char* arg = argv[argi] + 1;
++argi;
while (*arg) {
std::string option_name("-");
option_name.push_back(*arg);
++arg;
const Option_def* opt(find_option(options, option_name));
if (!opt) {
throw Option_error(option_name, "Invalid option");
}
if (opt->is_set) {
*opt->is_set = true;
}
if (opt->value) {
if (*arg) {
*opt->value = arg;
} else {
if (argi >= argc) {
throw Option_error(option_name, "Option requires a value");
}
*opt->value = argv[argi];
++argi;
}
break;
}
}
}
}
return argi;
}

57
parse_options.hpp Normal file
View File

@@ -0,0 +1,57 @@
/*
* Copyright 2014 Andrew Ayer
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name(s) of the above copyright
* holders shall not be used in advertising or otherwise to promote the
* sale, use or other dealings in this Software without prior written
* authorization.
*/
#ifndef PARSE_OPTIONS_HPP
#define PARSE_OPTIONS_HPP
#include <string>
#include <vector>
struct Option_def {
std::string name;
bool* is_set;
const char** value;
Option_def () : is_set(0), value(0) { }
Option_def (const std::string& arg_name, bool* arg_is_set)
: name(arg_name), is_set(arg_is_set), value(0) { }
Option_def (const std::string& arg_name, const char** arg_value)
: name(arg_name), is_set(0), value(arg_value) { }
};
typedef std::vector<Option_def> Options_list;
int parse_options (const Options_list& options, int argc, const char** argv);
struct Option_error {
std::string option_name;
std::string message;
Option_error (const std::string& n, const std::string& m) : option_name(n), message(m) { }
};
#endif

245
util-unix.cpp Normal file
View File

@@ -0,0 +1,245 @@
/*
* Copyright 2012, 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
* git-crypt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* git-crypt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <errno.h>
#include <utime.h>
#include <unistd.h>
#include <stdio.h>
#include <limits.h>
#include <fcntl.h>
#include <stdlib.h>
#include <dirent.h>
#include <vector>
#include <string>
#include <cstring>
#include <cstddef>
#include <algorithm>
std::string System_error::message () const
{
std::string mesg(action);
if (!target.empty()) {
mesg += ": ";
mesg += target;
}
if (error) {
mesg += ": ";
mesg += strerror(error);
}
return mesg;
}
void temp_fstream::open (std::ios_base::openmode mode)
{
close();
const char* tmpdir = getenv("TMPDIR");
size_t tmpdir_len = tmpdir ? std::strlen(tmpdir) : 0;
if (tmpdir_len == 0 || tmpdir_len > 4096) {
// no $TMPDIR or it's excessively long => fall back to /tmp
tmpdir = "/tmp";
tmpdir_len = 4;
}
std::vector<char> path_buffer(tmpdir_len + 18);
char* path = &path_buffer[0];
std::strcpy(path, tmpdir);
std::strcpy(path + tmpdir_len, "/git-crypt.XXXXXX");
mode_t old_umask = umask(0077);
int fd = mkstemp(path);
if (fd == -1) {
int mkstemp_errno = errno;
umask(old_umask);
throw System_error("mkstemp", "", mkstemp_errno);
}
umask(old_umask);
std::fstream::open(path, mode);
if (!std::fstream::is_open()) {
unlink(path);
::close(fd);
throw System_error("std::fstream::open", path, 0);
}
unlink(path);
::close(fd);
}
void temp_fstream::close ()
{
if (std::fstream::is_open()) {
std::fstream::close();
}
}
void mkdir_parent (const std::string& path)
{
std::string::size_type slash(path.find('/', 1));
while (slash != std::string::npos) {
std::string prefix(path.substr(0, slash));
struct stat status;
if (stat(prefix.c_str(), &status) == 0) {
// already exists - make sure it's a directory
if (!S_ISDIR(status.st_mode)) {
throw System_error("mkdir_parent", prefix, ENOTDIR);
}
} else {
if (errno != ENOENT) {
throw System_error("mkdir_parent", prefix, errno);
}
// doesn't exist - mkdir it
if (mkdir(prefix.c_str(), 0777) == -1) {
throw System_error("mkdir", prefix, errno);
}
}
slash = path.find('/', slash + 1);
}
}
static std::string readlink (const char* pathname)
{
std::vector<char> buffer(64);
ssize_t len;
while ((len = ::readlink(pathname, &buffer[0], buffer.size())) == static_cast<ssize_t>(buffer.size())) {
// buffer may have been truncated - grow and try again
buffer.resize(buffer.size() * 2);
}
if (len == -1) {
throw System_error("readlink", pathname, errno);
}
return std::string(buffer.begin(), buffer.begin() + len);
}
std::string our_exe_path ()
{
try {
return readlink("/proc/self/exe");
} catch (const System_error&) {
if (argv0[0] == '/') {
// argv[0] starts with / => it's an absolute path
return argv0;
} else if (std::strchr(argv0, '/')) {
// argv[0] contains / => it a relative path that should be resolved
char* resolved_path_p = realpath(argv0, NULL);
std::string resolved_path(resolved_path_p);
free(resolved_path_p);
return resolved_path;
} else {
// argv[0] is just a bare filename => not much we can do
return argv0;
}
}
}
int exit_status (int wait_status)
{
return wait_status != -1 && WIFEXITED(wait_status) ? WEXITSTATUS(wait_status) : -1;
}
void touch_file (const std::string& filename)
{
if (utimes(filename.c_str(), NULL) == -1 && errno != ENOENT) {
throw System_error("utimes", filename, errno);
}
}
void remove_file (const std::string& filename)
{
if (unlink(filename.c_str()) == -1 && errno != ENOENT) {
throw System_error("unlink", filename, errno);
}
}
static void init_std_streams_platform ()
{
}
void create_protected_file (const char* path)
{
int fd = open(path, O_WRONLY | O_CREAT, 0600);
if (fd == -1) {
throw System_error("open", path, errno);
}
close(fd);
}
int util_rename (const char* from, const char* to)
{
return rename(from, to);
}
static size_t sizeof_dirent_for (DIR* p)
{
long name_max = fpathconf(dirfd(p), _PC_NAME_MAX);
if (name_max == -1) {
#ifdef NAME_MAX
name_max = NAME_MAX;
#else
name_max = 255;
#endif
}
return offsetof(struct dirent, d_name) + name_max + 1; // final +1 is for d_name's null terminator
}
std::vector<std::string> get_directory_contents (const char* path)
{
std::vector<std::string> contents;
DIR* dir = opendir(path);
if (!dir) {
throw System_error("opendir", path, errno);
}
try {
std::vector<unsigned char> buffer(sizeof_dirent_for(dir));
struct dirent* dirent_buffer = reinterpret_cast<struct dirent*>(&buffer[0]);
struct dirent* ent = NULL;
int err = 0;
while ((err = readdir_r(dir, dirent_buffer, &ent)) == 0 && ent != NULL) {
if (std::strcmp(ent->d_name, ".") == 0 || std::strcmp(ent->d_name, "..") == 0) {
continue;
}
contents.push_back(ent->d_name);
}
if (err != 0) {
throw System_error("readdir_r", path, errno);
}
} catch (...) {
closedir(dir);
throw;
}
closedir(dir);
std::sort(contents.begin(), contents.end());
return contents;
}

212
util-win32.cpp Normal file
View File

@@ -0,0 +1,212 @@
/*
* Copyright 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
* git-crypt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* git-crypt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 <io.h>
#include <stdio.h>
#include <fcntl.h>
#include <windows.h>
#include <vector>
#include <cstring>
std::string System_error::message () const
{
std::string mesg(action);
if (!target.empty()) {
mesg += ": ";
mesg += target;
}
if (error) {
LPTSTR error_message;
FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPTSTR>(&error_message),
0,
NULL);
mesg += error_message;
LocalFree(error_message);
}
return mesg;
}
void temp_fstream::open (std::ios_base::openmode mode)
{
close();
char tmpdir[MAX_PATH + 1];
DWORD ret = GetTempPath(sizeof(tmpdir), tmpdir);
if (ret == 0) {
throw System_error("GetTempPath", "", GetLastError());
} else if (ret > sizeof(tmpdir) - 1) {
throw System_error("GetTempPath", "", ERROR_BUFFER_OVERFLOW);
}
char tmpfilename[MAX_PATH + 1];
if (GetTempFileName(tmpdir, TEXT("git-crypt"), 0, tmpfilename) == 0) {
throw System_error("GetTempFileName", "", GetLastError());
}
filename = tmpfilename;
std::fstream::open(filename.c_str(), mode);
if (!std::fstream::is_open()) {
DeleteFile(filename.c_str());
throw System_error("std::fstream::open", filename, 0);
}
}
void temp_fstream::close ()
{
if (std::fstream::is_open()) {
std::fstream::close();
DeleteFile(filename.c_str());
}
}
void mkdir_parent (const std::string& path)
{
std::string::size_type slash(path.find('/', 1));
while (slash != std::string::npos) {
std::string prefix(path.substr(0, slash));
if (GetFileAttributes(prefix.c_str()) == INVALID_FILE_ATTRIBUTES) {
// prefix does not exist, so try to create it
if (!CreateDirectory(prefix.c_str(), NULL)) {
throw System_error("CreateDirectory", prefix, GetLastError());
}
}
slash = path.find('/', slash + 1);
}
}
std::string our_exe_path ()
{
std::vector<char> buffer(128);
size_t len;
while ((len = GetModuleFileNameA(NULL, &buffer[0], buffer.size())) == buffer.size()) {
// buffer may have been truncated - grow and try again
buffer.resize(buffer.size() * 2);
}
if (len == 0) {
throw System_error("GetModuleFileNameA", "", GetLastError());
}
return std::string(buffer.begin(), buffer.begin() + len);
}
int exit_status (int status)
{
return status;
}
void touch_file (const std::string& filename)
{
HANDLE fh = CreateFileA(filename.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (fh == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND) {
return;
} else {
throw System_error("CreateFileA", filename, error);
}
}
SYSTEMTIME system_time;
GetSystemTime(&system_time);
FILETIME file_time;
SystemTimeToFileTime(&system_time, &file_time);
if (!SetFileTime(fh, NULL, NULL, &file_time)) {
DWORD error = GetLastError();
CloseHandle(fh);
throw System_error("SetFileTime", filename, error);
}
CloseHandle(fh);
}
void remove_file (const std::string& filename)
{
if (!DeleteFileA(filename.c_str())) {
DWORD error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND) {
return;
} else {
throw System_error("DeleteFileA", filename, error);
}
}
}
static void init_std_streams_platform ()
{
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
}
void create_protected_file (const char* path) // TODO
{
}
int util_rename (const char* from, const char* to)
{
// On Windows OS, it is necessary to ensure target file doesn't exist
unlink(to);
return rename(from, to);
}
std::vector<std::string> get_directory_contents (const char* path)
{
std::vector<std::string> filenames;
std::string patt(path);
if (!patt.empty() && patt[patt.size() - 1] != '/' && patt[patt.size() - 1] != '\\') {
patt.push_back('\\');
}
patt.push_back('*');
WIN32_FIND_DATAA ffd;
HANDLE h = FindFirstFileA(patt.c_str(), &ffd);
if (h == INVALID_HANDLE_VALUE) {
throw System_error("FindFirstFileA", patt, GetLastError());
}
do {
if (std::strcmp(ffd.cFileName, ".") != 0 && std::strcmp(ffd.cFileName, "..") != 0) {
filenames.push_back(ffd.cFileName);
}
} while (FindNextFileA(h, &ffd) != 0);
DWORD err = GetLastError();
if (err != ERROR_NO_MORE_FILES) {
throw System_error("FileNextFileA", patt, err);
}
FindClose(h);
return filenames;
}

175
util.cpp
View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 Andrew Ayer
* Copyright 2012, 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
@@ -28,88 +28,36 @@
* as that of the covered work.
*/
#include "git-crypt.hpp"
#include "util.hpp"
#include "coprocess.hpp"
#include <string>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fstream>
#include <iostream>
int exec_command (const char* command, std::ostream& output)
int exec_command (const std::vector<std::string>& args)
{
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
std::exit(9);
}
pid_t child = fork();
if (child == -1) {
perror("fork");
std::exit(9);
}
if (child == 0) {
close(pipefd[0]);
if (pipefd[1] != 1) {
dup2(pipefd[1], 1);
close(pipefd[1]);
}
execl("/bin/sh", "sh", "-c", command, NULL);
exit(-1);
}
close(pipefd[1]);
char buffer[1024];
ssize_t bytes_read;
while ((bytes_read = read(pipefd[0], buffer, sizeof(buffer))) > 0) {
output.write(buffer, bytes_read);
}
close(pipefd[0]);
int status = 0;
waitpid(child, &status, 0);
return status;
Coprocess proc;
proc.spawn(args);
return proc.wait();
}
std::string resolve_path (const char* path)
int exec_command (const std::vector<std::string>& args, std::ostream& output)
{
char* resolved_path_p = realpath(path, NULL);
std::string resolved_path(resolved_path_p);
free(resolved_path_p);
return resolved_path;
Coprocess proc;
std::istream* proc_stdout = proc.stdout_pipe();
proc.spawn(args);
output << proc_stdout->rdbuf();
return proc.wait();
}
void open_tempfile (std::fstream& file, std::ios_base::openmode mode)
int exec_command_with_input (const std::vector<std::string>& args, const char* p, size_t len)
{
const char* tmpdir = getenv("TMPDIR");
size_t tmpdir_len;
if (tmpdir) {
tmpdir_len = strlen(tmpdir);
} else {
tmpdir = "/tmp";
tmpdir_len = 4;
}
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");
unlink(path);
std::exit(9);
}
unlink(path);
close(fd);
delete[] path;
Coprocess proc;
std::ostream* proc_stdin = proc.stdin_pipe();
proc.spawn(args);
proc_stdin->write(p, len);
proc.close_stdin();
return proc.wait();
}
std::string escape_shell_arg (const std::string& str)
@@ -126,3 +74,84 @@ std::string escape_shell_arg (const std::string& str)
return new_str;
}
uint32_t load_be32 (const unsigned char* p)
{
return (static_cast<uint32_t>(p[3]) << 0) |
(static_cast<uint32_t>(p[2]) << 8) |
(static_cast<uint32_t>(p[1]) << 16) |
(static_cast<uint32_t>(p[0]) << 24);
}
void store_be32 (unsigned char* p, uint32_t i)
{
p[3] = i; i >>= 8;
p[2] = i; i >>= 8;
p[1] = i; i >>= 8;
p[0] = i;
}
bool read_be32 (std::istream& in, uint32_t& i)
{
unsigned char buffer[4];
in.read(reinterpret_cast<char*>(buffer), 4);
if (in.gcount() != 4) {
return false;
}
i = load_be32(buffer);
return true;
}
void write_be32 (std::ostream& out, uint32_t i)
{
unsigned char buffer[4];
store_be32(buffer, i);
out.write(reinterpret_cast<const char*>(buffer), 4);
}
void* explicit_memset (void* s, int c, std::size_t n)
{
volatile unsigned char* p = reinterpret_cast<unsigned char*>(s);
while (n--) {
*p++ = c;
}
return s;
}
static bool leakless_equals_char (const unsigned char* a, const unsigned char* b, std::size_t len)
{
volatile int diff = 0;
while (len > 0) {
diff |= *a++ ^ *b++;
--len;
}
return diff == 0;
}
bool leakless_equals (const void* a, const void* b, std::size_t len)
{
return leakless_equals_char(reinterpret_cast<const unsigned char*>(a), reinterpret_cast<const unsigned char*>(b), len);
}
static void init_std_streams_platform (); // platform-specific initialization
void init_std_streams ()
{
// The following two lines are essential for achieving good performance:
std::ios_base::sync_with_stdio(false);
std::cin.tie(0);
std::cin.exceptions(std::ios_base::badbit);
std::cout.exceptions(std::ios_base::badbit);
init_std_streams_platform();
}
#ifdef _WIN32
#include "util-win32.cpp"
#else
#include "util-unix.cpp"
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 Andrew Ayer
* Copyright 2012, 2014 Andrew Ayer
*
* This file is part of git-crypt.
*
@@ -28,17 +28,56 @@
* as that of the covered work.
*/
#ifndef _UTIL_H
#define _UTIL_H
#ifndef GIT_CRYPT_UTIL_HPP
#define GIT_CRYPT_UTIL_HPP
#include <string>
#include <ios>
#include <iosfwd>
#include <stdint.h>
#include <sys/types.h>
#include <fstream>
#include <vector>
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);
struct System_error {
std::string action;
std::string target;
int error;
System_error (const std::string& a, const std::string& t, int e) : action(a), target(t), error(e) { }
std::string message () const;
};
class temp_fstream : public std::fstream {
std::string filename;
public:
~temp_fstream () { close(); }
void open (std::ios_base::openmode);
void close ();
};
void mkdir_parent (const std::string& path); // Create parent directories of path, __but not path itself__
std::string our_exe_path ();
int exec_command (const std::vector<std::string>&);
int exec_command (const std::vector<std::string>&, std::ostream& output);
int exec_command_with_input (const std::vector<std::string>&, const char* p, size_t len);
int exit_status (int wait_status); // returns -1 if process did not exit (but was signaled, etc.)
inline bool successful_exit (int wait_status) { return exit_status(wait_status) == 0; }
void touch_file (const std::string&); // ignores non-existent files
void remove_file (const std::string&); // ignores non-existent files
std::string escape_shell_arg (const std::string&);
uint32_t load_be32 (const unsigned char*);
void store_be32 (unsigned char*, uint32_t);
bool read_be32 (std::istream& in, uint32_t&);
void write_be32 (std::ostream& out, uint32_t);
void* explicit_memset (void* s, int c, size_t n); // memset that won't be optimized away
bool leakless_equals (const void* a, const void* b, size_t len); // compare bytes w/o leaking timing
void init_std_streams ();
void create_protected_file (const char* path); // create empty file accessible only by current user
int util_rename (const char*, const char*);
std::vector<std::string> get_directory_contents (const char* path);
#endif