[PATCH 08/10] Adding time-stamping helper tool

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This commit adds a helper tool called `git-timestamp-util`, which does the
actual RFC3161 time-stamping work. It depends on libssl and libcrypto.

In particular, it is used for creating time-stamp signatures and for verifying
them.

To create a time-stamp signature, a Time Stamping Query (TSQ) is created and
passed to the helper tool `git-http-timestamp`, which passes it to a Time
Stamping Authority and outputs a trusted Time Stamping Response (TSR). The TSR
is then split into the time-stamp signature itself and the Time Stamping
Autority's certificate. This certificate is stored in a repository-global TSA
store file called .git_tsa_store, whereas the raw time-stamp signature is passed
to the caller to be stored in a git object. Splitting the TSR into the TSA's
certificate and the raw time-stamp signature is done to avoid redundancy as the
TSA's certificate will likely not change over years.

To verify a time-stamp signature, a SHA-1 hash of the git object to be checked
is passed along with its corresponding time-stamp signature. Identifying
certificate information like issuer and serial number is extracted from the
time-stamp signature. The tuple of issuer and serial number is then used to find
the actual certificate of the Time Stamping Autority in .git_tsa_store file.
The TSA's Certificate and the raw time-stamp signature are merged together and
verified.

Signed-off-by: Anton Würfel <anton.wuerfel@xxxxxx>
Signed-off-by: Phillip Raffeck <phillip.raffeck@xxxxxx>
---
 .gitignore       |   1 +
 Makefile         |   7 +
 command-list.txt |   1 +
 timestamp-util.c | 615 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 624 insertions(+)
 create mode 100644 timestamp-util.c

diff --git a/.gitignore b/.gitignore
index a3b270d..08005ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -160,6 +160,7 @@
 /git-svn
 /git-symbolic-ref
 /git-tag
+/git-timestamp-util
 /git-unpack-file
 /git-unpack-objects
 /git-update-index
diff --git a/Makefile b/Makefile
index c717af7..a0ab96a 100644
--- a/Makefile
+++ b/Makefile
@@ -1142,6 +1142,9 @@ ifndef NO_OPENSSL
 	ifdef NO_HMAC_CTX_CLEANUP
 		BASIC_CFLAGS += -DNO_HMAC_CTX_CLEANUP
 	endif
+
+	PROGRAM_OBJS += timestamp-util.o
+	PROGRAMS += git-timestamp-util$X
 else
 	BASIC_CFLAGS += -DNO_OPENSSL
 	BLK_SHA1 = 1
@@ -2025,6 +2028,10 @@ git-http-timestamp$X: http.o http-timestamp.o GIT-LDFLAGS $(GITLIBS)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 		$(CURL_LIBCURL) $(LIBS)
 
+git-timestamp-util$X: timestamp-util.o GIT-LDFLAGS $(GITLIBS)
+	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+		$(LIBS)
+
 $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)
 	$(QUIET_LNCP)$(RM) $@ && \
 	ln $< $@ 2>/dev/null || \
diff --git a/command-list.txt b/command-list.txt
index 3e279c1..07a4cab 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -136,6 +136,7 @@ git-submodule                           mainporcelain
 git-svn                                 foreignscminterface
 git-symbolic-ref                        plumbingmanipulators
 git-tag                                 mainporcelain           history
+git-timestamp-util                      purehelpers
 git-unpack-file                         plumbinginterrogators
 git-unpack-objects                      plumbingmanipulators
 git-update-index                        plumbingmanipulators
diff --git a/timestamp-util.c b/timestamp-util.c
new file mode 100644
index 0000000..fee4fc2
--- /dev/null
+++ b/timestamp-util.c
@@ -0,0 +1,615 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/rand.h>
+#include <openssl/ts.h>
+#include <openssl/bn.h>
+
+#include "cache.h"
+#include "run-command.h"
+#include "strbuf.h"
+
+/*
+ * This code is based on the RFC3161 implementation in OpenSSL
+ * by Zoltan Glozik.
+ */
+
+#define NONCE_LENGTH (64)
+#define ISSUER_LEN (1024)
+
+struct tsr_info {
+	char issuer[ISSUER_LEN];
+	unsigned long serial;
+	X509 *cert;
+};
+
+
+static const char *timesig_cmd = "http-timestamp";
+
+static const char *config_ca_key = "ts.capath";
+static const char *certstore_file = ".git_tsa_store";
+static const char *certstore_header_begin = "-----BEGIN ENTRY-----";
+static const char *certstore_header_end = "-----END ENTRY-----";
+
+static TS_REQ *create_query(const char *digest);
+static ASN1_INTEGER *create_nonce(int bits);
+static TS_MSG_IMPRINT *create_msg_imprint(const char *digest);
+
+static int output_as_base64(TS_RESP *response);
+static TS_RESP *strip_tsr(TS_RESP *response);
+
+static int do_verify(TS_RESP *response, const char *digest, const char *CApath,
+		     X509 *untrusted);
+static X509_STORE *create_cert_store(const char *CApath);
+static TS_VERIFY_CTX *create_verify_ctx(const char *digest, const char *CApath,
+					X509 *cert);
+
+static int append_certificate_to_store(struct tsr_info *info);
+static X509 *find_certificate_in_store(struct tsr_info *info);
+
+static int extract_info_from_response(struct tsr_info *info, TS_RESP *response);
+
+static void usage_and_die(const char *name);
+
+static int create_tsr(const char *sha1);
+static int verify_tsr(const char *sha1);
+
+/*
+ * Request a TSR from a Time Stamping Authority. The TSR includes the
+ * authority's public key. To avoid saving this public key for every
+ * time-stamped git object, the public key is stripped from the TSR and saved
+ * separately. By default, it is stored in .git_tsa_store file.
+ */
+static int create_tsr(const char *sha1)
+{
+	TS_RESP *response = NULL;
+	TS_REQ *query = NULL;
+	X509 *cert = NULL;
+	BIO *out_bio = NULL;
+	BIO *in_bio = NULL;
+	struct child_process timesig = CHILD_PROCESS_INIT;
+	struct tsr_info info;
+	int retval = 1;
+	const char *args[] = {
+		timesig_cmd,
+		NULL
+	};
+
+	/*
+	 * Invoke git-http-timestamp to receive a TSR from
+	 * the Time Stamping Authority.
+	 */
+	timesig.argv = args;
+	timesig.in = -1;
+	timesig.out = -1;
+	timesig.git_cmd = 1;
+
+	query = create_query(sha1);
+	if (!query) {
+		ERR_print_errors_fp(stderr);
+		goto end;
+	}
+
+	if (start_command(&timesig))
+		return error(_("could not run git-%s"), timesig_cmd);
+
+	out_bio = BIO_new_fd(timesig.in, BIO_NOCLOSE | BIO_FP_TEXT);
+	if (!out_bio)
+		goto end;
+
+	if (!i2d_TS_REQ_bio(out_bio, query))
+		goto end;
+
+	close(timesig.in);
+
+	in_bio = BIO_new_fd(timesig.out, BIO_NOCLOSE);
+	if (!in_bio)
+		goto end;
+
+	response = d2i_TS_RESP_bio(in_bio, NULL);
+	if (!response)
+		goto end;
+
+	close(timesig.out);
+
+	if (finish_command(&timesig))
+		goto end;
+
+	extract_info_from_response(&info, response);
+
+	/* Add certificate to TSA store if it does not exist yet */
+	cert = find_certificate_in_store(&info);
+	if (!cert) {
+		retval = append_certificate_to_store(&info);
+		if (retval)
+			goto end;
+	}
+
+	/* Strip certificate from TSR */
+	strip_tsr(response);
+	if (!response)
+		goto end;
+
+	/* Send stripped TSR to stdout */
+	if (output_as_base64(response))
+		goto end;
+
+	retval = 0;
+
+end:
+	BIO_free_all(out_bio);
+	BIO_free_all(in_bio);
+	TS_RESP_free(response);
+	TS_REQ_free(query);
+	X509_free(cert);
+	return retval;
+}
+
+/*
+ * Verify a TSR passed via stdin. The base64 encoded TSR is combined with the
+ * corresponding public key of the Time Stamping Authority (TSA) and then passed
+ * to the `openssl ts -verify` command.
+ */
+static int verify_tsr(const char *sha1)
+{
+	BIO *b64 = NULL;
+	BIO *in = NULL;
+	BIO *out = NULL;
+	TS_RESP *response = NULL;
+	TS_TST_INFO *tst_info = NULL;
+	X509 *cert = NULL;
+	int retval = 1;
+	char *config_ca_path;
+	struct tsr_info info;
+
+	/* get config options */
+	if (git_config_get_pathname(config_ca_key,
+				    (const char **)&config_ca_path))
+		die(_("git config option '%s' must be set"), config_ca_key);
+
+	/* prepare BIO-stdout */
+	out = BIO_new_fp(stdout, BIO_NOCLOSE);
+	if (!out)
+		goto end;
+
+	/* read in base64 encoded stripped tsr from stdin */
+	b64 = BIO_new(BIO_f_base64());
+	in = BIO_new_fp(stdin, BIO_NOCLOSE);
+	if (!in)
+		goto end;
+
+	in = BIO_push(b64, in);
+
+	response = d2i_TS_RESP_bio(in, NULL);
+	if (!response)
+		goto end;
+
+	extract_info_from_response(&info, response);
+	cert = find_certificate_in_store(&info);
+	if (!cert) {
+		error(_("certificate not found in %s"), certstore_file);
+		goto end;
+	}
+
+	if (do_verify(response, sha1, config_ca_path, cert)) {
+		error("BAD time-stamp signature");
+		ERR_print_errors(out);
+		goto end;
+	}
+
+	/* prepare data for output */
+	tst_info = TS_RESP_get_tst_info(response);
+
+	BIO_puts(out, "Verified time-stamp: ");
+	ASN1_GENERALIZEDTIME_print(out, tst_info->time);
+	BIO_printf(out, "\nTime Stamping Authority: %s\n",
+		   info.issuer);
+
+	retval = 0;
+
+end:
+	TS_RESP_free(response);
+	BIO_free_all(in);
+	BIO_free_all(out);
+	free(config_ca_path);
+
+	return retval;
+}
+
+static int do_verify(TS_RESP *response, const char *digest, const char *CApath,
+		     X509 *cert)
+{
+	TS_VERIFY_CTX *verify_ctx = NULL;
+	int ret = 0;
+
+	verify_ctx = create_verify_ctx(digest, CApath, cert);
+	if (!verify_ctx)
+		goto end;
+
+	ret = TS_RESP_verify_response(verify_ctx, response);
+
+end:
+	/*
+	 * TS_VERIFY_CTX_free also cleans up the created X509_STORE, so no
+	 * further action is needed.
+	 */
+	TS_VERIFY_CTX_free(verify_ctx);
+
+	/*
+	 * Invert ret to follow git return semantics. 0 indicates success,
+	 * anything else indicates errors.
+	 */
+	return !ret;
+}
+
+static int append_certificate_to_store(struct tsr_info *info)
+{
+	FILE *store = fopen(certstore_file, "a");
+
+	if (!store) {
+		return error(_("Failed to open the certificate store at "
+			       "%s: %s"), certstore_file, strerror(errno));
+	}
+
+	fprintf(store, "%s\nVersion: 1\nSerial: %lu\nIssuer: %s\n\n",
+		certstore_header_begin,
+		info->serial,
+		info->issuer
+	       );
+
+	if (!PEM_write_X509(store, info->cert)) {
+		fclose(store);
+		return 1;
+	}
+
+	fprintf(store, "%s\n", certstore_header_end);
+	fclose(store);
+
+	return 0;
+}
+
+static X509 *find_certificate_in_store(struct tsr_info *info)
+{
+	char buf[1024];
+	char issuer[ISSUER_LEN];
+	X509 *cert = NULL;
+	unsigned long serial;
+	FILE *store;
+
+	store = fopen(certstore_file, "r");
+	if (!store) {
+		if (errno == ENOENT)
+			return NULL;
+		die(_("Failed to open the certificate store at "
+			       "%s: %s"), certstore_file, strerror(errno));
+	}
+
+	while (fgets(buf, 1024, store)) {
+		if (!starts_with(buf, certstore_header_begin))
+			continue;
+
+		serial = 0;
+		*issuer = 0;
+		/* We are reading in an entry. Read in meta-data.*/
+		while (fgets(buf, 1024, store)) {
+			if (starts_with(buf, "Serial:"))
+				sscanf(buf, "Serial: %lu\n", &serial);
+
+			if (starts_with(buf, "Issuer:"))
+				sscanf(buf, "Issuer: %[^\n]\n", issuer);
+
+			/* A empty line separates meta-data from certificate */
+			if (!strcmp(buf, "\n"))
+			       break;
+			if (starts_with(buf, certstore_header_end)) {
+				serial = 0;
+				*issuer = 0;
+				break;
+			}
+		}
+
+		/* Check if certificate matches */
+		if (serial != info->serial ||
+		    strcmp(issuer, info->issuer))
+			continue;
+
+		/* Matching certificate found */
+		cert = PEM_read_X509(store, NULL, NULL, NULL);
+
+		if (cert)
+			break;
+
+		/* Certificate could not be read. Output a warning. */
+		fprintf(stderr, "Warning: Failed to read certificate with serial: "
+				"%lu and issuer: %s. "
+				"It may be corrupted.\n", serial, issuer);
+	}
+
+	fclose(store);
+	return cert;
+}
+
+/* Extract issuer, serial number and TSA's public key from a TS_RESP struct */
+static int extract_info_from_response(struct tsr_info *info, TS_RESP *response)
+{
+	PKCS7 *token;
+	PKCS7_SIGNER_INFO *pksi = NULL;
+	PKCS7_ISSUER_AND_SERIAL *pkis = NULL;
+
+	token = response->token;
+	pksi = sk_PKCS7_SIGNER_INFO_value(token->d.sign->signer_info, 0);
+	pkis = pksi->issuer_and_serial;
+
+	X509_NAME_get_text_by_NID(pkis->issuer, NID_commonName, info->issuer,
+				  ISSUER_LEN);
+	info->serial = ASN1_INTEGER_get(pkis->serial);
+	info->cert = sk_X509_value(token->d.sign->cert, 0);
+
+	return 0;
+}
+
+/* Use libssl functions to convert data to base64 and output it via stdout */
+static int output_as_base64(TS_RESP *response)
+{
+	BIO *b64 = NULL;
+	BIO *out = NULL;
+
+	b64 = BIO_new(BIO_f_base64());
+	out = BIO_new_fp(stdout, BIO_NOCLOSE);
+	out = BIO_push(b64, out);
+
+	if (!i2d_TS_RESP_bio(out, response))
+		return 1;
+
+	BIO_flush(out);
+	BIO_free_all(out);
+
+	return 0;
+}
+
+/*
+ * Strip TSR by removing all X509 certificates as they are stored in a dedicated
+ * TSA certificate store.
+ */
+static TS_RESP *strip_tsr(TS_RESP *response)
+{
+	while (sk_X509_num(response->token->d.sign->cert) > 0)
+		sk_X509_pop(response->token->d.sign->cert);
+
+	return response;
+}
+
+static TS_REQ *create_query(const char *digest)
+{
+	int fail = 0;
+	TS_REQ *ts_req = NULL;
+	TS_MSG_IMPRINT *msg_imprint = NULL;
+	ASN1_INTEGER *nonce = NULL;
+
+	/* Creating request object. */
+	ts_req = TS_REQ_new();
+	if (!ts_req)
+		goto err;
+
+	/* Setting version. */
+	if (!TS_REQ_set_version(ts_req, 1))
+		goto err;
+
+	/* Setting MSG_IMPRINT */
+	msg_imprint = create_msg_imprint(digest);
+	if (!msg_imprint)
+		goto err;
+	if (!TS_REQ_set_msg_imprint(ts_req, msg_imprint))
+		goto err;
+
+	/* Setting nonce. */
+	nonce = create_nonce(NONCE_LENGTH);
+	if (!nonce)
+		goto err;
+	if (!TS_REQ_set_nonce(ts_req, nonce))
+		goto err;
+
+	/* Set certificate request flag. */
+	if (!TS_REQ_set_cert_req(ts_req, 1))
+		goto err;
+
+	fail = 1;
+err:
+	if (!fail) {
+		TS_REQ_free(ts_req);
+		ts_req = NULL;
+	}
+
+	TS_MSG_IMPRINT_free(msg_imprint);
+	ASN1_INTEGER_free(nonce);
+	return ts_req;
+}
+
+static TS_MSG_IMPRINT *create_msg_imprint(const char *digest)
+{
+	long len;
+	const EVP_MD *md = NULL;
+	TS_MSG_IMPRINT *msg_imprint = NULL;
+	X509_ALGOR *algo = NULL;
+	unsigned char *data = NULL;
+	int fail = 1;
+
+	/* Creating MSG_IMPRINT object. */
+	msg_imprint = TS_MSG_IMPRINT_new();
+	if (!msg_imprint)
+		goto err;
+
+	data = string_to_hex(digest, &len);
+	if (!TS_MSG_IMPRINT_set_msg(msg_imprint, data, len))
+		goto err;
+
+	/* Adding sha1 algorithm. */
+	md = EVP_sha1();
+	algo = X509_ALGOR_new();
+	if (!algo)
+		goto err;
+	algo->algorithm = OBJ_nid2obj(EVP_MD_type(md));
+	if (!algo->algorithm)
+		goto err;
+	algo->parameter = ASN1_TYPE_new();
+	if (!algo->parameter)
+		goto err;
+	algo->parameter->type = V_ASN1_NULL;
+	if (!TS_MSG_IMPRINT_set_algo(msg_imprint, algo))
+		goto err;
+
+	fail = 0;
+
+err:
+	if (fail) {
+		TS_MSG_IMPRINT_free(msg_imprint);
+		msg_imprint = NULL;
+	}
+
+	OPENSSL_free(data);
+	X509_ALGOR_free(algo);
+	return msg_imprint;
+}
+
+static ASN1_INTEGER *create_nonce(int bits)
+{
+	unsigned char buf[20];
+	ASN1_INTEGER *nonce = NULL;
+	int len = (bits - 1) / 8 + 1;
+	int i;
+
+	/* Generating random byte sequence. */
+	if (len > (int) sizeof(buf))
+		goto err;
+	if (RAND_bytes(buf, len) <= 0)
+		goto err;
+
+	/* Find the first non-zero byte and creating ASN1_INTEGER object. */
+	for (i = 0; i < len && !buf[i]; ++i)
+		;
+	nonce = ASN1_INTEGER_new();
+	if (!nonce)
+		goto err;
+	OPENSSL_free(nonce->data);
+
+	/* Allocate at least one byte. */
+	nonce->length = len - i;
+	nonce->data = OPENSSL_malloc(nonce->length + 1);
+	if (!nonce->data)
+		goto err;
+	memcpy(nonce->data, buf + i, nonce->length);
+
+	return nonce;
+ err:
+	ASN1_INTEGER_free(nonce);
+	return NULL;
+}
+
+static TS_VERIFY_CTX *create_verify_ctx(const char *digest, const char *CApath,
+					X509 *cert)
+{
+	TS_VERIFY_CTX *ctx = NULL;
+	unsigned char *sha1 = NULL;
+	long imprint_len;
+	int f = 0;
+
+	ctx = TS_VERIFY_CTX_new();
+	if (!ctx)
+		goto err;
+
+	f = TS_VFY_VERSION | TS_VFY_SIGNER | TS_VFY_IMPRINT | TS_VFY_SIGNATURE;
+	sha1 = string_to_hex(digest, &imprint_len);
+	if (!sha1)
+		goto err;
+
+	/* Add digest to verification context. */
+	ctx->imprint = sha1;
+	ctx->imprint_len = imprint_len;
+
+	/* Add the signature verification flag and arguments. */
+	ctx->flags = f;
+
+	/* Initialising the X509_STORE object. */
+	ctx->store = create_cert_store(CApath);
+	if (!ctx->store)
+		goto err;
+
+	/* Add signing certificate of TSA */
+	ctx->certs = sk_X509_new_null();
+	if (!ctx->certs)
+		goto err;
+
+	sk_X509_push(ctx->certs, cert);
+
+
+	return ctx;
+
+err:
+	/*
+	 * TS_VERIFY_CTX also cleans up *sha1 and the created X509 store, so no
+	 * further action is needed.
+	 */
+	TS_VERIFY_CTX_free(ctx);
+	return NULL;
+}
+
+static X509_STORE *create_cert_store(const char *CApath)
+{
+	X509_STORE *cert_ctx = NULL;
+	X509_LOOKUP *lookup = NULL;
+	int ret;
+
+	cert_ctx = X509_STORE_new();
+	lookup = X509_STORE_add_lookup(cert_ctx, X509_LOOKUP_hash_dir());
+	if (!lookup)
+		goto err;
+
+	ret = X509_LOOKUP_add_dir(lookup, CApath, X509_FILETYPE_PEM);
+	if (!ret) {
+		error(_("Error loading directory %s"), CApath);
+		goto err;
+	}
+
+	return cert_ctx;
+
+err:
+	X509_STORE_free(cert_ctx);
+	return NULL;
+}
+
+
+static void usage_and_die(const char *name)
+{
+	fprintf(stderr, "Usage: %s <-c | -v> [<sha1>]\n\n", name);
+	fputs("-c: Create a time-stamp signature for the given SHA1-hash\n",
+	      stderr);
+	fputs("-v: Verify a time-stamp signature from data passed via stdin\n",
+	      stderr);
+
+	exit(EXIT_FAILURE);
+}
+
+int main(int argc, char *argv[])
+{
+	CRYPTO_malloc_init();
+	ERR_load_crypto_strings();
+	OpenSSL_add_all_algorithms();
+
+	if (argc != 3)
+		usage_and_die(argv[0]);
+
+	if (!strcmp(argv[1], "-c"))
+		return create_tsr(argv[2]);
+
+	else if (!strcmp(argv[1], "-v"))
+		return verify_tsr(argv[2]);
+
+	else
+		usage_and_die(argv[0]);
+
+	return 0;
+}
-- 
2.8.0.rc0.62.gfc8aefa.dirty

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]