7 Commits

Author SHA1 Message Date
Andrew Ayer
160cf642e1 Prepare 0.8.0 release 2025-09-23 20:41:07 -04:00
Andrew Ayer
4dd5c20243 Drop support for OpenSSL 1.0; fix compilation with OpenSSL 3 2025-09-23 20:37:48 -04:00
Andrew Ayer
968c924798 GitHub actions: upgrade download/upload artifacts
Closes: #313
2024-09-03 19:30:58 -04:00
Andrew Ayer
08dbdcfed4 When adding GPG collaborator, include full fingerprint in commit message
Short key IDs are bad (https://evil32.com/)

Closes: #253
2022-06-07 12:34:52 -04:00
Andrew Ayer
a1e6311f56 Prepare 0.7.0 release 2022-04-21 13:08:16 -04:00
Andrew Ayer
12c422228a Add GitHub Actions to build & upload release binaries
Closes: #227
2022-04-21 13:02:07 -04:00
Andrew Ayer
1c905faeb5 Remove references to the mailing lists
Since the git-crypt mailing lists have barely been used, and mailing
lists seem to be falling out of fashion for open source projects, I've
decided to shut down the git-crypt mailing lists in favor of functionality
provided by GitHub.

For announcements of new releases, you can watch the git-crypt
repository (https://github.com/AGWA/git-crypt) for new releases.

For bug reports, you can file an issue:
https://github.com/AGWA/git-crypt/issues

For discussions, you can use GitHub's new discussions feature:
https://github.com/AGWA/git-crypt/discussions
2021-02-28 10:15:20 -05:00
17 changed files with 133 additions and 208 deletions

46
.github/workflows/release-linux.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
on:
release:
types: [published]
name: Build Release Binary (Linux)
jobs:
build:
name: Build Release Binary
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt install libssl-dev
- name: Build binary
run: make
- name: Upload release artifact
uses: actions/upload-artifact@v4
with:
name: git-crypt-artifacts
path: git-crypt
upload:
name: Upload Release Binary
runs-on: ubuntu-latest
needs: build
permissions:
contents: write
steps:
- name: Download release artifact
uses: actions/download-artifact@v4
with:
name: git-crypt-artifacts
- name: Upload release asset
uses: actions/github-script@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require("fs").promises;
const { repo: { owner, repo }, sha } = context;
await github.repos.uploadReleaseAsset({
owner, repo,
release_id: ${{ github.event.release.id }},
name: 'git-crypt-${{ github.event.release.name }}-linux-x86_64',
data: await fs.readFile('git-crypt'),
});

56
.github/workflows/release-windows.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
on:
release:
types: [published]
name: Build Release Binary (Windows)
jobs:
build:
name: Build Release Binary
runs-on: windows-2022
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup msys2
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
install: >-
base-devel
msys2-devel
mingw-w64-x86_64-toolchain
mingw-w64-x86_64-openssl
openssl-devel
- name: Build binary
shell: msys2 {0}
run: make LDFLAGS="-static-libstdc++ -static -lcrypto -lws2_32"
- name: Upload release artifact
uses: actions/upload-artifact@v4
with:
name: git-crypt-artifacts
path: git-crypt.exe
upload:
name: Upload Release Binary
runs-on: ubuntu-latest
needs: build
permissions:
contents: write
steps:
- name: Download release artifact
uses: actions/download-artifact@v4
with:
name: git-crypt-artifacts
- name: Upload release asset
uses: actions/github-script@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require("fs").promises;
const { repo: { owner, repo }, sha } = context;
await github.repos.uploadReleaseAsset({
owner, repo,
release_id: ${{ github.event.release.id }},
name: 'git-crypt-${{ github.event.release.name }}-x86_64.exe',
data: await fs.readFile('git-crypt.exe'),
});

View File

@@ -4,8 +4,7 @@ documentation, bug reports, or anything else that improves git-crypt.
When contributing code, please consider the following guidelines: When contributing code, please consider the following guidelines:
* You are encouraged to open an issue on GitHub or send mail to * You are encouraged to open an issue on GitHub to discuss any non-trivial
git-crypt-discuss@lists.cloudmutt.com to discuss any non-trivial
changes before you start coding. changes before you start coding.
* Please mimic the existing code style as much as possible. In * Please mimic the existing code style as much as possible. In
@@ -15,8 +14,7 @@ When contributing code, please consider the following guidelines:
* To minimize merge commits, please rebase your changes before opening * To minimize merge commits, please rebase your changes before opening
a pull request. a pull request.
* To submit your patch, open a pull request on GitHub or send a * To submit your patch, open a pull request on GitHub.
properly-formatted patch to git-crypt-discuss@lists.cloudmutt.com.
Finally, be aware that since git-crypt is security-sensitive software, Finally, be aware that since git-crypt is security-sensitive software,
the bar for contributions is higher than average. Please don't be the bar for contributions is higher than average. Please don't be

View File

@@ -24,7 +24,7 @@ OBJFILES = \
coprocess.o \ coprocess.o \
fhstream.o fhstream.o
OBJFILES += crypto-openssl-10.o crypto-openssl-11.o OBJFILES += crypto-openssl-11.o
LDFLAGS += -lcrypto LDFLAGS += -lcrypto
XSLTPROC ?= xsltproc XSLTPROC ?= xsltproc

9
NEWS
View File

@@ -1,3 +1,12 @@
v0.8.0 (2025-09-23)
* Remove OpenSSL 1.0 support, fix compilation with OpenSSL 3.
* Avoid use of problematic short GPG key IDs.
v0.7.0 (2022-04-21)
* Avoid "argument list too long" errors on macOS.
* Fix handling of "-" arguments.
* Minor documentation improvements.
v0.6.0 (2017-11-26) v0.6.0 (2017-11-26)
* Add support for OpenSSL 1.1 (still works with OpenSSL 1.0). * Add support for OpenSSL 1.1 (still works with OpenSSL 1.0).
* Switch to C++11 (gcc 4.9 or higher now required to build). * Switch to C++11 (gcc 4.9 or higher now required to build).

View File

@@ -1,6 +1,15 @@
News News
==== ====
######v0.8.0 (2025-09-23)
* Remove OpenSSL 1.0 support, fix compilation with OpenSSL 3.
* Avoid use of problematic short GPG key IDs.
######v0.7.0 (2022-04-21)
* Avoid "argument list too long" errors on macOS.
* Fix handling of "-" arguments.
* Minor documentation improvements.
######v0.6.0 (2017-11-26) ######v0.6.0 (2017-11-26)
* Add support for OpenSSL 1.1 (still works with OpenSSL 1.0). * Add support for OpenSSL 1.1 (still works with OpenSSL 1.0).
* Switch to C++11 (gcc 4.9 or higher now required to build). * Switch to C++11 (gcc 4.9 or higher now required to build).

11
README
View File

@@ -70,7 +70,7 @@ encryption and decryption happen transparently.
CURRENT STATUS CURRENT STATUS
The latest version of git-crypt is 0.6.0, released on 2017-11-26. The latest version of git-crypt is 0.8.0, released on 2025-09-23.
git-crypt aims to be bug-free and reliable, meaning it shouldn't git-crypt aims to be bug-free and reliable, meaning it shouldn't
crash, malfunction, or expose your confidential data. However, crash, malfunction, or expose your confidential data. However,
it has not yet reached maturity, meaning it is not as documented, it has not yet reached maturity, meaning it is not as documented,
@@ -158,12 +158,3 @@ match it accidentally. If necessary, you can exclude .gitattributes from
encryption like this: encryption like this:
.gitattributes !filter !diff .gitattributes !filter !diff
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

View File

@@ -71,8 +71,8 @@ encryption and decryption happen transparently.
Current Status Current Status
-------------- --------------
The latest version of git-crypt is [0.6.0](NEWS.md), released on The latest version of git-crypt is [0.8.0](NEWS.md), released on
2017-11-26. git-crypt aims to be bug-free and reliable, meaning it 2025-09-23. git-crypt aims to be bug-free and reliable, meaning it
shouldn't crash, malfunction, or expose your confidential data. shouldn't crash, malfunction, or expose your confidential data.
However, it has not yet reached maturity, meaning it is not as However, it has not yet reached maturity, meaning it is not as
documented, featureful, or easy-to-use as it should be. Additionally, documented, featureful, or easy-to-use as it should be. Additionally,
@@ -160,12 +160,3 @@ match it accidentally. If necessary, you can exclude .gitattributes from
encryption like this: encryption like this:
.gitattributes !filter !diff .gitattributes !filter !diff
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)

View File

@@ -461,25 +461,6 @@ static std::pair<std::string, std::string> get_file_attributes (const std::strin
return std::make_pair(filter_attr, diff_attr); return std::make_pair(filter_attr, diff_attr);
} }
static bool check_if_blob_is_empty (const std::string& object_id)
{
// git cat-file blob object_id
std::vector<std::string> command;
command.push_back("git");
command.push_back("cat-file");
command.push_back("blob");
command.push_back(object_id);
// TODO: do this more efficiently - don't read entire command output into buffer, only read what we need
std::stringstream output;
if (!successful_exit(exec_command(command, output))) {
throw Error("'git cat-file' failed - is this a Git repository?");
}
return output.get() == std::stringstream::traits_type::eof();
}
static bool check_if_blob_is_encrypted (const std::string& object_id) static bool check_if_blob_is_encrypted (const std::string& object_id)
{ {
// git cat-file blob object_id // git cat-file blob object_id
@@ -789,10 +770,6 @@ int clean (int argc, const char** argv)
return 1; return 1;
} }
if (file_size == 0 && key_file.get_skip_empty()) {
return 0;
}
// We use an HMAC of the file as the encryption nonce (IV) for CTR mode. // We use an HMAC of the file as the encryption nonce (IV) for CTR mode.
// By using a hash of the file we ensure that the encryption is // By using a hash of the file we ensure that the encryption is
// deterministic so git doesn't think the file has changed when it really // deterministic so git doesn't think the file has changed when it really
@@ -910,11 +887,6 @@ int smudge (int argc, const char** argv)
// Read the header to get the nonce and make sure it's actually encrypted // Read the header to get the nonce and make sure it's actually encrypted
unsigned char header[10 + Aes_ctr_decryptor::NONCE_LEN]; unsigned char header[10 + Aes_ctr_decryptor::NONCE_LEN];
std::cin.read(reinterpret_cast<char*>(header), sizeof(header)); std::cin.read(reinterpret_cast<char*>(header), sizeof(header));
if (std::cin.gcount() == 0 && key_file.get_skip_empty()) {
return 0;
}
if (std::cin.gcount() != sizeof(header) || std::memcmp(header, "\0GITCRYPT\0", 10) != 0) { if (std::cin.gcount() != sizeof(header) || std::memcmp(header, "\0GITCRYPT\0", 10) != 0) {
// File not encrypted - just copy it out to stdout // File not encrypted - just copy it out to stdout
std::clog << "git-crypt: Warning: file not encrypted" << std::endl; std::clog << "git-crypt: Warning: file not encrypted" << std::endl;
@@ -1019,7 +991,6 @@ int init (int argc, const char** argv)
std::clog << "Generating key..." << std::endl; std::clog << "Generating key..." << std::endl;
Key_file key_file; Key_file key_file;
key_file.set_key_name(key_name); key_file.set_key_name(key_name);
key_file.set_skip_empty(true);
key_file.generate(); key_file.generate();
mkdir_parent(internal_key_path); mkdir_parent(internal_key_path);
@@ -1326,7 +1297,8 @@ int add_gpg_user (int argc, const char** argv)
std::ostringstream commit_message_builder; std::ostringstream commit_message_builder;
commit_message_builder << "Add " << collab_keys.size() << " git-crypt collaborator" << (collab_keys.size() != 1 ? "s" : "") << "\n\nNew collaborators:\n\n"; commit_message_builder << "Add " << collab_keys.size() << " git-crypt collaborator" << (collab_keys.size() != 1 ? "s" : "") << "\n\nNew collaborators:\n\n";
for (std::vector<std::pair<std::string, bool> >::const_iterator collab(collab_keys.begin()); collab != collab_keys.end(); ++collab) { for (std::vector<std::pair<std::string, bool> >::const_iterator collab(collab_keys.begin()); collab != collab_keys.end(); ++collab) {
commit_message_builder << '\t' << gpg_shorten_fingerprint(collab->first) << ' ' << gpg_get_uid(collab->first) << '\n'; commit_message_builder << " " << collab->first << '\n';
commit_message_builder << " " << gpg_get_uid(collab->first) << '\n';
} }
// git commit -m MESSAGE NEW_FILE ... // git commit -m MESSAGE NEW_FILE ...
@@ -1454,7 +1426,6 @@ int keygen (int argc, const char** argv)
std::clog << "Generating key..." << std::endl; std::clog << "Generating key..." << std::endl;
Key_file key_file; Key_file key_file;
key_file.set_skip_empty(true);
key_file.generate(); key_file.generate();
if (std::strcmp(key_file_name, "-") == 0) { if (std::strcmp(key_file_name, "-") == 0) {
@@ -1659,8 +1630,7 @@ int status (int argc, const char** argv)
if (file_attrs.first == "git-crypt" || std::strncmp(file_attrs.first.c_str(), "git-crypt-", 10) == 0) { if (file_attrs.first == "git-crypt" || std::strncmp(file_attrs.first.c_str(), "git-crypt-", 10) == 0) {
// File is encrypted // File is encrypted
// If the file is empty, don't consider it unencrypted, because in newly-initialized repos (specifically those with keys with skip_empty set) we don't encrypt empty files. Unfortunately, we can't easily determine here if the key has skip_empty set, so just act like it is. This means we won't notice if an old repo has an empty unencrypted file that should be encrypted. Fortunately, this isn't really a big deal because empty files obviously don't contain anything sensitive in them. const bool blob_is_unencrypted = !object_id.empty() && !check_if_blob_is_encrypted(object_id);
const bool blob_is_unencrypted = !object_id.empty() && !check_if_blob_is_encrypted(object_id) && !check_if_blob_is_empty(object_id);
if (fix_problems && blob_is_unencrypted) { if (fix_problems && blob_is_unencrypted) {
if (access(filename.c_str(), F_OK) != 0) { if (access(filename.c_str(), F_OK) != 0) {

View File

@@ -1,120 +0,0 @@
/*
* 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 <openssl/opensslconf.h>
#if !defined(OPENSSL_API_COMPAT)
#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 unique_ptr
// which contains an incomplete type when the unique_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 unique_ptr
// which contains an incomplete type when the unique_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());
}
}
#endif

View File

@@ -30,8 +30,6 @@
#include <openssl/opensslconf.h> #include <openssl/opensslconf.h>
#if defined(OPENSSL_API_COMPAT)
#include "crypto.hpp" #include "crypto.hpp"
#include "key.hpp" #include "key.hpp"
#include "util.hpp" #include "util.hpp"
@@ -115,5 +113,3 @@ void random_bytes (unsigned char* buffer, size_t len)
throw Crypto_error("random_bytes", message.str()); throw Crypto_error("random_bytes", message.str());
} }
} }
#endif

View File

@@ -31,7 +31,7 @@
#ifndef GIT_CRYPT_GIT_CRYPT_HPP #ifndef GIT_CRYPT_GIT_CRYPT_HPP
#define GIT_CRYPT_GIT_CRYPT_HPP #define GIT_CRYPT_GIT_CRYPT_HPP
#define VERSION "0.6.0" #define VERSION "0.8.0"
extern const char* argv0; // initialized in main() to argv[0] extern const char* argv0; // initialized in main() to argv[0]

View File

@@ -61,12 +61,6 @@ static std::string gpg_nth_column (const std::string& line, unsigned int col)
line.substr(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>") // 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) std::string gpg_get_uid (const std::string& fingerprint)
{ {

View File

@@ -41,7 +41,6 @@ struct Gpg_error {
explicit Gpg_error (std::string m) : message(m) { } 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::string gpg_get_uid (const std::string& fingerprint);
std::vector<std::string> gpg_lookup_key (const std::string& query); std::vector<std::string> gpg_lookup_key (const std::string& query);
std::vector<std::string> gpg_list_secret_keys (); std::vector<std::string> gpg_list_secret_keys ();

View File

@@ -232,11 +232,6 @@ void Key_file::load_header (std::istream& in)
key_name.clear(); key_name.clear();
throw Malformed(); throw Malformed();
} }
} else if (field_id == HEADER_FIELD_SKIP_EMPTY) {
if (field_len != 0) {
throw Malformed();
}
skip_empty = true;
} else if (field_id & 1) { // unknown critical field } else if (field_id & 1) { // unknown critical field
throw Incompatible(); throw Incompatible();
} else { } else {
@@ -261,10 +256,6 @@ void Key_file::store (std::ostream& out) const
write_be32(out, key_name.size()); write_be32(out, key_name.size());
out.write(key_name.data(), key_name.size()); out.write(key_name.data(), key_name.size());
} }
if (skip_empty) {
write_be32(out, HEADER_FIELD_SKIP_EMPTY);
write_be32(out, 0);
}
write_be32(out, HEADER_FIELD_END); write_be32(out, HEADER_FIELD_END);
for (Map::const_iterator it(entries.begin()); it != entries.end(); ++it) { for (Map::const_iterator it(entries.begin()); it != entries.end(); ++it) {
it->second.store(out); it->second.store(out);

View File

@@ -83,23 +83,18 @@ public:
void set_key_name (const char* k) { key_name = k ? k : ""; } 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(); } const char* get_key_name () const { return key_name.empty() ? 0 : key_name.c_str(); }
void set_skip_empty (bool v) { skip_empty = v; }
bool get_skip_empty () const { return skip_empty; }
private: private:
typedef std::map<uint32_t, Entry, std::greater<uint32_t> > Map; typedef std::map<uint32_t, Entry, std::greater<uint32_t> > Map;
enum { FORMAT_VERSION = 2 }; enum { FORMAT_VERSION = 2 };
Map entries; Map entries;
std::string key_name; std::string key_name;
bool skip_empty = false;
void load_header (std::istream&); void load_header (std::istream&);
enum { enum {
HEADER_FIELD_END = 0, HEADER_FIELD_END = 0,
HEADER_FIELD_KEY_NAME = 1, HEADER_FIELD_KEY_NAME = 1
HEADER_FIELD_SKIP_EMPTY = 3 // If this field is present, empty files are left unencrypted (see issue #53)
}; };
enum { enum {
KEY_FIELD_END = 0, KEY_FIELD_END = 0,

View File

@@ -7,8 +7,8 @@
--> -->
<refentryinfo> <refentryinfo>
<title>git-crypt</title> <title>git-crypt</title>
<date>2017-11-26</date> <date>2022-04-21</date>
<productname>git-crypt 0.6.0</productname> <productname>git-crypt 0.8.0</productname>
<author> <author>
<othername>Andrew Ayer</othername> <othername>Andrew Ayer</othername>