# [PATCH 01/10] Add Testcases for time-stamping functionality

11 messages from 2016-03-17 to 2016-03-17. Participants: Anton Wuerfel.
Thread: https://gitlist.dev/t/41730

## Anton Wuerfel, 2016-03-17 18:46

Subject: [PATCH 00/10] Adding RFC3161 time-stamps to tags
Message-ID: <1458240421-3593-1-git-send-email-anton.wuerfel@fau.de>
URL: https://gitlist.dev/e/1458240421-3593-1-git-send-email-anton.wuerfel%40fau.de

```
Hi all,

this patch series adds RFC3161 time-stamping functionality to git tags.
This enables users to create and verify cryptographic time stamp signatures to
prove that a commit existed at a certain point in time.

The new RFC3161 time-stamping functionality is independent from GPG signatures.
However, GPG signatures include time-stamp signatures and therefore make them
robust against removal or replacement.

The time stamp signature is stored in the git tag object header under the
`timesig` key, which is equivalent to the format for GPG signatures in signed
commits. This facilitates a possible implementation of time-stamped commits in
the future.

We added two new helper commands, because of their dependencies on libcurl and
libcrypto respectively. If one of the libraries is missing, a warning message is
given when one of the new functionalities is used.

We tried to add as much documentation as possible. Especially timestamp-util.c,
which depends on libcrypto, is a bit tough to read.

Some configuration variables have been added, where `ts.tsaurl` is the most
important, which specifies the Time Stamping Autority to use.

For testing the implemented functionality, the Time Stamping Authority
SafeCreative can be used, which is free for non-commercial use:
https://tsa.safecreative.org.
Simply set the config variable ts.tsaurl to this TSA.

Your feedback is welcome.
Kind regards,
Anton Wuerfel
Phillip Raffeck

Anton Wuerfel (10):
Phillip Raffeck (10):
  Add Testcases for time-stamping functionality
  Documentation for time-stamping functionality
  Documentation: Whitespace fix
  Extending internal CURL wrapper for POST upload
  Make PGP macros global
  Add basic RFC3161 functionality
  Add git-http-timestamp helper tool
  Adding time-stamping helper tool
  Add time-stamping functionality to git verify-tag
  Add time-stamping functionality to git tag

 .gitignore                           |   2 +
 Documentation/config.txt             |  20 ++
 Documentation/git-http-timestamp.txt |  32 ++
 Documentation/git-tag.txt            |  32 +-
 Documentation/git-timestamp-util.txt |  52 +++
 Documentation/git-tsa-store.txt      |  68 ++++
 Documentation/git-verify-tag.txt     |  28 +-
 Makefile                             |  15 +
 builtin/tag.c                        |  55 +++-
 builtin/verify-tag.c                 |  61 +++-
 command-list.txt                     |   2 +
 gpg-interface.c                      |   3 -
 gpg-interface.h                      |   3 +
 http-timestamp.c                     |  76 +++++
 http.c                               |  30 +-
 http.h                               |  17 +-
 remote-curl.c                        |   2 +-
 rfc3161.c                            | 219 +++++++++++++
 rfc3161.h                            |  12 +
 t/t7031-verify-tag.sh                |  69 ++++
 timestamp-util.c                     | 615 +++++++++++++++++++++++++++++++++++
 21 files changed, 1380 insertions(+), 33 deletions(-)
 create mode 100644 Documentation/git-http-timestamp.txt
 create mode 100644 Documentation/git-timestamp-util.txt
 create mode 100644 Documentation/git-tsa-store.txt
 create mode 100644 http-timestamp.c
 create mode 100644 rfc3161.c
 create mode 100644 rfc3161.h
 create mode 100755 t/t7031-verify-tag.sh
 create mode 100644 timestamp-util.c

-- 
2.8.0.rc0.62.gfc8aefa.dirty

```

## Anton Wuerfel, 2016-03-17 18:46

Subject: [PATCH 01/10] Add Testcases for time-stamping functionality
Message-ID: <1458240421-3593-2-git-send-email-anton.wuerfel@fau.de>
URL: https://gitlist.dev/e/1458240421-3593-2-git-send-email-anton.wuerfel%40fau.de
In-Reply-To: <1458240421-3593-1-git-send-email-anton.wuerfel@fau.de>

```
This commit adds testcases for the new time-stamping functionality. To trigger
these testcases, the environment variables $GIT_TEST_TSA_URL and
$GIT_TEST_TSA_CAPATH have to be set. They do not default to any value to avoid
spamming a pre-defined Time Stamping Authority (TSA).
As testing the RFC3161 functionality without a trusted time-stamp signature is
not possible, all tests are disabled by default.

Signed-off-by: Anton Würfel <anton.wuerfel@fau.de>
Signed-off-by: Phillip Raffeck <phillip.raffeck@fau.de>
---
 t/t7031-verify-tag.sh | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)
 create mode 100755 t/t7031-verify-tag.sh

diff --git a/t/t7031-verify-tag.sh b/t/t7031-verify-tag.sh
new file mode 100755
index 0000000..bc7953b
--- /dev/null
+++ b/t/t7031-verify-tag.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='signed time-stamped tag tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+# Tests for RFC3161 implementation
+
+if test -n "$NO_OPENSSL"
+then
+	skip_all='skipping test, git built without openssl support'
+	test_done
+fi
+
+# Disable remote tests by default to avoid spamming of TSAs and to be able to
+# test without internet connection
+
+if test -z "$GIT_TEST_TSA_URL"
+then
+	skip_all='Remote TSA testing disabled (set GIT_TEST_TSA_URL to enable)'
+	test_done
+fi
+
+if test -z "$GIT_TEST_TSA_CAPATH"
+then
+	skip_all='Remote TSA testing disabled (set GIT_TEST_TSA_CAPATH to enable)'
+	test_done
+fi
+
+test_expect_success 'create a tag with a timestamp signature' '
+	git config ts.tsaurl $GIT_TEST_TSA_URL &&
+	git config ts.capath $GIT_TEST_TSA_CAPATH &&
+	git config ts.failonverify 1 &&
+	test_tick &&
+	echo foo > foo &&
+	git add foo &&
+	git commit -m foo &&
+	git tag -t -m onlytime tsa_onlytime
+'
+
+test_expect_success GPG 'create a tag with gpg and timestamp signatures' '
+	echo foobar > bar &&
+	git add bar &&
+	git commit -m test123 &&
+	git tag -s -t -m tsa_both tsa_both
+'
+
+test_expect_success 'verify tag with gpg and timestamp signature' '
+	git verify-tag tsa_both
+'
+
+test_expect_success 'verify tag with timestamp signature' '
+	test_must_fail git verify-tag tsa_onlytime
+'
+
+test_expect_success 'verify tag with timestamp signature with -t' '
+	git verify-tag -t tsa_onlytime
+'
+
+test_expect_success 'verify fudged timestamp' '
+	git cat-file tag tsa_onlytime >raw &&
+	sed -e "s/onlytime/morethantime/" raw >forged1 &&
+	git hash-object -w -t tag forged1 >forged1.tag &&
+	test_must_fail git verify-tag $(cat forged1.tag) >actual1 2>&1 &&
+	grep "BAD time-stamp signature" actual1 &&
+	! grep "Verified timestamp" actual1
+'
+
+test_done
-- 
2.8.0.rc0.62.gfc8aefa.dirty

```

## Anton Wuerfel, 2016-03-17 18:46

Subject: [PATCH 02/10] Documentation for time-stamping functionality
Message-ID: <1458240421-3593-3-git-send-email-anton.wuerfel@fau.de>
URL: https://gitlist.dev/e/1458240421-3593-3-git-send-email-anton.wuerfel%40fau.de
In-Reply-To: <1458240421-3593-1-git-send-email-anton.wuerfel@fau.de>

```
This commit adds documentation for newly introduced helper commands,
configuration variables and the certificate storage file `.git_tsa_store`. The
documentation pages for `git tag` and git `verify-tag` are altered to describe
the new command line options for time-stamping.

Signed-off-by: Anton Würfel <anton.wuerfel@fau.de>
Signed-off-by: Phillip Raffeck <phillip.raffeck@fau.de>
---
 Documentation/config.txt             | 20 +++++++++++
 Documentation/git-http-timestamp.txt | 32 +++++++++++++++++
 Documentation/git-tag.txt            | 30 +++++++++++-----
 Documentation/git-timestamp-util.txt | 52 +++++++++++++++++++++++++++
 Documentation/git-tsa-store.txt      | 68 ++++++++++++++++++++++++++++++++++++
 Documentation/git-verify-tag.txt     | 28 ++++++++++++---
 6 files changed, 218 insertions(+), 12 deletions(-)
 create mode 100644 Documentation/git-http-timestamp.txt
 create mode 100644 Documentation/git-timestamp-util.txt
 create mode 100644 Documentation/git-tsa-store.txt

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 2cd6bdd..8be5fe6 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -2774,6 +2774,26 @@ transfer.unpackLimit::
 	not set, the value of this variable is used instead.
 	The default value is 100.
 
+ts.capath::
+	This variable contains a path to a directory with trusted certificates.
+	This is necessary to verify the authenticity of RFC3161 time-stamps in
+	time-stamped tags with `git tag -v` or `git verify-tag`. There is no
+	default value.
+
+ts.failonverify::
+	This variable controls the return value semantics of `git verify-tag`.
+	If set, both the return values of the gpg signature and the time-stamp
+	verification are reported. If the least significant bit is set, it
+	indicates a failure during the gpg signature verification, while the
+	second lowest bit reports the return value of the time-stamp verification.
+	Per default, only the return value of the gpg signature verification is
+	reported to remain consistent with legacy code.
+
+ts.tsaurl::
+	This variable contains the URL of the Time Stamping Authority from which
+	RFC3161 time-stamps should be obtained when creating time-stamped tags
+	with `git tag`. There is no default value.
+
 uploadarchive.allowUnreachable::
 	If true, allow clients to use `git archive --remote` to request
 	any tree, whether reachable from the ref tips or not. See the
diff --git a/Documentation/git-http-timestamp.txt b/Documentation/git-http-timestamp.txt
new file mode 100644
index 0000000..1c7781e
--- /dev/null
+++ b/Documentation/git-http-timestamp.txt
@@ -0,0 +1,32 @@
+git-http-timestamp(1)
+=====================
+
+NAME
+----
+git-http-timestamp - Helper program to communicate with a RFC3161 Time
+Stamping Authority.
+
+
+SYNOPSIS
+--------
+[verse]
+'git http-timestamp'
+
+DESCRIPTION
+-----------
+Offers an interface for the communication with a RFC3161 Time Stamping Authority
+to obtain a trusted time-stamp signature for a Time Stamping Query. The Time
+Stamping Query is read from stdin in DER format.
+The response received from the Time Stamping Authority is written to stdout.
+The URL of the Time Stamping Authority must be given using the configuration
+variable `ts.tsaurl`.
+
+NOTES
+-----
+The main purpose of this command is to be called from the internal mechanism of
+`git tag` or `git verify-tag`. Some commands are implemented in this separate
+binary to allow the usage of libcurl without building all of git against it.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index abab481..940676e 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -9,7 +9,7 @@ git-tag - Create, list, delete or verify a tag object signed with GPG
 SYNOPSIS
 --------
 [verse]
-'git tag' [-a | -s | -u <keyid>] [-f] [-m <msg> | -F <file>]
+'git tag' [-a | -s | -t | -u <keyid>] [-f] [-m <msg> | -F <file>]
 	<tagname> [<commit> | <object>]
 'git tag' -d <tagname>...
 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
@@ -25,12 +25,12 @@ to delete, list or verify tags.
 
 Unless `-f` is given, the named tag must not yet exist.
 
-If one of `-a`, `-s`, or `-u <keyid>` is passed, the command
+If one of `-a`, `-s`, `-t`, or `-u <keyid>` is passed, the command
 creates a 'tag' object, and requires a tag message.  Unless
 `-m <msg>` or `-F <file>` is given, an editor is started for the user to type
 in the tag message.
 
-If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <keyid>`
+If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, `-t`, and `-u <keyid>`
 are absent, `-a` is implied.
 
 Otherwise just a tag reference for the SHA-1 object name of the commit object is
@@ -42,10 +42,14 @@ committer identity for the current user is used to find the
 GnuPG key for signing. 	The configuration variable `gpg.program`
 is used to specify custom GnuPG binary.
 
-Tag objects (created with `-a`, `-s`, or `-u`) are called "annotated"
+A tag containing a time-stamp according to RFC3161 will be created when `-t` is
+used. The configuration variable `ts.tsaurl` is used to specify the URL of the
+Time Stamping Authority from which the time-stamp should be obtained.
+
+Tag objects (created with `-a`, `-s`, `-t`, or `-u`) are called "annotated"
 tags; they contain a creation date, the tagger name and e-mail, a
-tagging message, and an optional GnuPG signature. Whereas a
-"lightweight" tag is simply a name for an object (usually a commit
+tagging message, an optional GnuPG signature, and an optional RFC3161 time-stamp.
+Whereas a "lightweight" tag is simply a name for an object (usually a commit
 object).
 
 Annotated tags are meant for release while lightweight tags are meant
@@ -64,6 +68,10 @@ OPTIONS
 --sign::
 	Make a GPG-signed tag, using the default e-mail address's key.
 
+-t::
+--time-stamp::
+	Add a RFC3161 time-stamp to the tag.
+
 -u <keyid>::
 --local-user=<keyid>::
 	Make a GPG-signed tag, using the given key.
@@ -78,7 +86,13 @@ OPTIONS
 
 -v::
 --verify::
-	Verify the gpg signature of the given tag names.
+	Verify, if present, the gpg signature and the RFC3161 time-stamp of the
+	given tag names. The configuration variable `ts.failonverify` specifies
+	if the return value of the time-stamp verification should be reported.
+	If set, the return values of both the gpg signature and the time-stamp
+	verfification are considered. Per default, only the return value of the
+	gpg signature verfification is reported to remain consistent with legacy
+	code.
 
 -n<num>::
 	<num> specifies how many lines from the annotation, if any,
@@ -128,7 +142,7 @@ This option is only applicable when listing tags without annotation lines.
 	Use the given tag message (instead of prompting).
 	If multiple `-m` options are given, their values are
 	concatenated as separate paragraphs.
-	Implies `-a` if none of `-a`, `-s`, or `-u <keyid>`
+	Implies `-a` if none of `-a`, `-s`, `-t`, or `-u <keyid>`
 	is given.
 
 -F <file>::
diff --git a/Documentation/git-timestamp-util.txt b/Documentation/git-timestamp-util.txt
new file mode 100644
index 0000000..4724e6f
--- /dev/null
+++ b/Documentation/git-timestamp-util.txt
@@ -0,0 +1,52 @@
+git-timestamp-util(1)
+=====================
+
+NAME
+----
+git-timestamp-util - Helper tool to manage RFC3161 time-stamps.
+
+
+SYNOPSIS
+--------
+[verse]
+'git timestamp-util' [-c <sha1>] [-v <sha1>]
+
+DESCRIPTION
+-----------
+Offers an interface to obtain a time-stamp signature for a given SHA1 hash or to
+verify the time-stamp of a given SHA1 hash. The signing certificates of the
+response sent by the Time Stamp Autority are stripped and stored in a separate
+`git_tsa_store` file to avoid duplicates.
+
+OPTIONS
+-------
+-c sha1::
+	Create a RFC3161 time-stamp over the given hash. The response from the
+	Time Stamping authority is stripped from the signing certificate and
+	then written to stdout as base64.
+
+-v sha1::
+	Verify a time-stamp for the given hash. The corresponding TSR, encoded
+	in base64, is read from stdin and merged with its signing certificate
+	from a separate certificate store. To allow the verification of the
+	signing certificate, the configuration variable `ts.capath` must contain
+	a path to a directory with trusted certificates. The configuration
+	variable `ts.failonverify` controls the return value semantics. If set,
+	the return values of both the gpg signature and the time-stamp
+	verification are reported. The return value of the time-stamp
+	verification is reported using the second lowest bit, the return value
+	of the gpg signature verification using the least significant bit. Per
+	default, only the return value of the gpg signature verification is
+	reported to remain consistent with legacy code.
+
+NOTES
+-----
+The main purpose of this command is to be called from the internal
+mechanism of `git tag` or `git verify-tag`. Some commands are implemented in
+this separate binary to allow the usage of libcrypto without building all of git
+against it. While it is possible to is it on its own, the interface is highly
+tailored to the needs of the internal storage mechanisms.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-tsa-store.txt b/Documentation/git-tsa-store.txt
new file mode 100644
index 0000000..2ce015e
--- /dev/null
+++ b/Documentation/git-tsa-store.txt
@@ -0,0 +1,68 @@
+git-tsa-store(5)
+================
+
+NAME
+----
+git-tsa-store - Stores certificates of RFC3161 Time Stamping Authorities
+
+DESCRIPTION
+-----------
+A `.git_tsa_store` file stores certificates of RFC3161 Time Stamping Authorities
+from which time-stamps were obtained using `git tag` or `git timestamp-util`. To
+avoid unnecessary pollution by large git objects, each RFC3161 time-stamp is
+stripped from the signing certificate of the Time Stamping Authority before it
+is stored in the tag object. These signing certificates are then managed in the
+`.git_tsa_store` file for each repository.
+
+Each certificate is only stored once in a `.git_tsa_store` file. The comparison
+criterion for equality is the pair of the serial number of the certificate and
+the common name of the issuer. Therefore it is possible to manage multiple
+certificates for one Time Stamping Authority if for some reason a certificate
+should change.
+
+The certificates stored in a `.git_tsa_store` file are only used to correctly
+identify the issuer of a time-stamp. They are not considered trustworthy. For
+verification purposes it is therefore necesssary to specify trusted certificates
+using the configuration variable `ts.capath`.
+
+All certificates in a `.git_tsa_store` file are stored using the pattern
+described below.
+
+PATTERN FORMAT
+--------------
+ - The string '-----BEGIN ENTRY-----' marks the beginning of a certificate
+   entry.
+
+ - The next line is of the format 'Version: <version>'. The current version
+   number is 1.
+
+ - The next line is of the format 'Serial: <serial>', containing the serial
+   number of the certificate. Together with the common name of the issuer it
+   forms the identifying tuple.
+
+ - The next line is of the format 'Issuer: <common name>', containing the common
+   name of the issuer of the time-stamp. Together with the serial number of the
+   certificate it forms the identifying tuple.
+
+ - The next line contains only a new line to separate metadata from the
+   certificate itself.
+
+ - The certificate itself follows in PEM format surrounded by the usual
+   delimiters '-----BEGIN CERTIFICATE-----' and '-----END CERTIFICATE-----'.
+
+ - A closing '-----END ENTRY-----' marks the end of an entry.
+
+NOTES
+-----
+The main purpose of a `.git_tsa_store` file is to be managed exclusively by `git
+timestamp-util`. Nevertheless it is possible to fill in certificates manually as
+long as the above specified format is observed. Manually editing this file might
+be necessary in the unlikely case of non-resolvable merge conflicts.
+
+SEE ALSO
+--------
+linkgit:git-timestamp-util[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt
index d590edc..539e545 100644
--- a/Documentation/git-verify-tag.txt
+++ b/Documentation/git-verify-tag.txt
@@ -3,7 +3,7 @@ git-verify-tag(1)
 
 NAME
 ----
-git-verify-tag - Check the GPG signature of tags
+git-verify-tag - Check the GPG signature and RFC3161 time-stamp of tags
 
 SYNOPSIS
 --------
@@ -12,13 +12,33 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Validates the gpg signature created by 'git tag'.
+Validates the gpg signature and RFC3161 time-stamp created by 'git tag'. To
+validate the signing certificate of the Time Stamping Authority of a RFC3161
+time-stamp, a path to a directory of trusted certificates must be given using
+the configuration variable `ts.capath`. The containing files must follow the
+OpenSSL naming convention `<issuer-hash>.0`. The configuration variable
+`ts.failonverify` specifies if the return value of the time-stamp verification
+should be reported.  If set, the return values of both the gpg signature and the
+time-stamp verfification are considered. Per default, only the return value of
+the gpg signature verfification is reported to remain consistent with legacy
+code. Using the `-s` or `-t` options overrides the behaviour set by
+`ts.failonverify`.
 
 OPTIONS
 -------
 --raw::
-	Print the raw gpg status output to standard error instead of the normal
-	human-readable output.
+	Print the raw gpg status output and time-stamp verification output to
+	standard error instead of the normal human-readable output.
+
+-s::
+--signature::
+	Report the return value of the gpg signature verification. If the least
+	significant bit is set, an error occurred.
+
+-t::
+--timestamp::
+	Report the return value of the time-stamp verification. If the second
+	lowest bit is set, an error occurred.
 
 -v::
 --verbose::
-- 
2.8.0.rc0.62.gfc8aefa.dirty

```

## Anton Wuerfel, 2016-03-17 18:46

Subject: [PATCH 03/10] Documentation: Whitespace fix
Message-ID: <1458240421-3593-4-git-send-email-anton.wuerfel@fau.de>
URL: https://gitlist.dev/e/1458240421-3593-4-git-send-email-anton.wuerfel%40fau.de
In-Reply-To: <1458240421-3593-1-git-send-email-anton.wuerfel@fau.de>

```
This commit contains just a tiny whitespace fix in the git-tag documentation.

Signed-off-by: Anton Würfel <anton.wuerfel@fau.de>
Signed-off-by: Phillip Raffeck <phillip.raffeck@fau.de>
---
 Documentation/git-tag.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index 940676e..b49725b 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -39,7 +39,7 @@ created (i.e. a lightweight tag).
 A GnuPG signed tag object will be created when `-s` or `-u
 <keyid>` is used.  When `-u <keyid>` is not used, the
 committer identity for the current user is used to find the
-GnuPG key for signing. 	The configuration variable `gpg.program`
+GnuPG key for signing. The configuration variable `gpg.program`
 is used to specify custom GnuPG binary.
 
 A tag containing a time-stamp according to RFC3161 will be created when `-t` is
-- 
2.8.0.rc0.62.gfc8aefa.dirty

```

## Anton Wuerfel, 2016-03-17 18:46

Subject: [PATCH 04/10] Extending internal CURL wrapper for POST upload
Message-ID: <1458240421-3593-5-git-send-email-anton.wuerfel@fau.de>
URL: https://gitlist.dev/e/1458240421-3593-5-git-send-email-anton.wuerfel%40fau.de
In-Reply-To: <1458240421-3593-1-git-send-email-anton.wuerfel@fau.de>

```
This commit adds POST upload capabilities to the internal libcurl wrapper, which
are necessary for uploading RFC3161 Time Stamping Queries to a Time Stamping
Authority.

Therefore, the internal struct http_get_options is renamed to
http_request_options and extended by 2 strbuf fields named `postfields` and
`post_content_type`. If either one of them is non-NULL, the corresponding
curl_easy_setopt() functions are called.

Signed-off-by: Anton Würfel <anton.wuerfel@fau.de>
Signed-off-by: Phillip Raffeck <phillip.raffeck@fau.de>
---
 http.c        | 30 +++++++++++++++++++++++-------
 http.h        | 17 +++++++++++++++--
 remote-curl.c |  2 +-
 3 files changed, 39 insertions(+), 10 deletions(-)

diff --git a/http.c b/http.c
index 69da445..a108658 100644
--- a/http.c
+++ b/http.c
@@ -1373,17 +1373,19 @@ static void http_opt_request_remainder(CURL *curl, off_t pos)
 
 static int http_request(const char *url,
 			void *result, int target,
-			const struct http_get_options *options)
+			const struct http_request_options *options)
 {
 	struct active_request_slot *slot;
 	struct slot_results results;
 	struct curl_slist *headers = NULL;
 	struct strbuf buf = STRBUF_INIT;
+	struct strbuf content_type = STRBUF_INIT;
 	const char *accept_language;
 	int ret;
 
 	slot = get_active_slot();
-	curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+	if (options && !options->postfields)
+		curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
 
 	if (result == NULL) {
 		curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
@@ -1407,6 +1409,19 @@ static int http_request(const char *url,
 	if (accept_language)
 		headers = curl_slist_append(headers, accept_language);
 
+	if (options && options->postfields && options->post_content_type) {
+		strbuf_addstr(&content_type, "Content-Type: ");
+		strbuf_addbuf(&content_type, options->post_content_type);
+		headers = curl_slist_append(headers, content_type.buf);
+	}
+
+	if (options && options->postfields) {
+		curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS,
+				 options->postfields->buf);
+		curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE,
+				 options->postfields->len);
+	}
+
 	strbuf_addstr(&buf, "Pragma:");
 	if (options && options->no_cache)
 		strbuf_addstr(&buf, " no-cache");
@@ -1434,6 +1449,7 @@ static int http_request(const char *url,
 				options->effective_url);
 
 	curl_slist_free_all(headers);
+	strbuf_release(&content_type);
 	strbuf_release(&buf);
 
 	return ret;
@@ -1491,7 +1507,7 @@ static int update_url_from_redirect(struct strbuf *base,
 
 static int http_request_reauth(const char *url,
 			       void *result, int target,
-			       struct http_get_options *options)
+			       struct http_request_options *options)
 {
 	int ret = http_request(url, result, target, options);
 
@@ -1529,7 +1545,7 @@ static int http_request_reauth(const char *url,
 
 int http_get_strbuf(const char *url,
 		    struct strbuf *result,
-		    struct http_get_options *options)
+		    struct http_request_options *options)
 {
 	return http_request_reauth(url, result, HTTP_REQUEST_STRBUF, options);
 }
@@ -1541,7 +1557,7 @@ int http_get_strbuf(const char *url,
  * file is still around) the download is resumed.
  */
 static int http_get_file(const char *url, const char *filename,
-			 struct http_get_options *options)
+			 struct http_request_options *options)
 {
 	int ret;
 	struct strbuf tmpfile = STRBUF_INIT;
@@ -1567,7 +1583,7 @@ cleanup:
 
 int http_fetch_ref(const char *base, struct ref *ref)
 {
-	struct http_get_options options = {0};
+	struct http_request_options options = {0};
 	char *url;
 	struct strbuf buffer = STRBUF_INIT;
 	int ret = -1;
@@ -1659,7 +1675,7 @@ add_pack:
 
 int http_get_info_packs(const char *base_url, struct packed_git **packs_head)
 {
-	struct http_get_options options = {0};
+	struct http_request_options options = {0};
 	int ret = 0, i = 0;
 	char *url, *data;
 	struct strbuf buf = STRBUF_INIT;
diff --git a/http.h b/http.h
index 4ef4bbd..0b7cebf 100644
--- a/http.h
+++ b/http.h
@@ -136,7 +136,7 @@ extern char *get_remote_object_url(const char *url, const char *hex,
 				   int only_two_digit_prefix);
 
 /* Options for http_get_*() */
-struct http_get_options {
+struct http_request_options {
 	unsigned no_cache:1,
 		 keep_error:1;
 
@@ -163,6 +163,18 @@ struct http_get_options {
 	 * for details.
 	 */
 	struct strbuf *base_url;
+
+	/*
+	 * If non-NULL, a HTTP POST request is sent with the raw post data
+	 * contained in postfields.
+	 */
+	struct strbuf *postfields;
+
+	/*
+	 * If non-NULL, a content-type header is applied to the HTTP POST
+	 * request.
+	 */
+	struct strbuf *post_content_type;
 };
 
 /* Return values for http_get_*() */
@@ -178,7 +190,8 @@ struct http_get_options {
  *
  * If the result pointer is NULL, a HTTP HEAD request is made instead of GET.
  */
-int http_get_strbuf(const char *url, struct strbuf *result, struct http_get_options *options);
+int http_get_strbuf(const char *url, struct strbuf *result,
+		    struct http_request_options *options);
 
 extern int http_fetch_ref(const char *base, struct ref *ref);
 
diff --git a/remote-curl.c b/remote-curl.c
index 15e48e2..345a5e6 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -254,7 +254,7 @@ static struct discovery *discover_refs(const char *service, int for_push)
 	struct strbuf effective_url = STRBUF_INIT;
 	struct discovery *last = last_discovery;
 	int http_ret, maybe_smart = 0;
-	struct http_get_options options;
+	struct http_request_options options;
 
 	if (last && !strcmp(service, last->service))
 		return last;
-- 
2.8.0.rc0.62.gfc8aefa.dirty

```

## Anton Wuerfel, 2016-03-17 18:46

Subject: [PATCH 05/10] Make PGP macros global
Message-ID: <1458240421-3593-6-git-send-email-anton.wuerfel@fau.de>
URL: https://gitlist.dev/e/1458240421-3593-6-git-send-email-anton.wuerfel%40fau.de
In-Reply-To: <1458240421-3593-1-git-send-email-anton.wuerfel@fau.de>

```
This commit moves the macros PGP_SIGNATURE and PGP_MESSAGE to gpg-interface.h to
make them publicly reachable, which is necessary for RFC3161 time-stamping.

When verifying an RFC3161 time-stamp signature, a SHA-1 hash has to be generated
over the git object excluding a possible PGP signature, which makes it necessary
to detect the beginning of this PGP signature.

Signed-off-by: Anton Würfel <anton.wuerfel@fau.de>
Signed-off-by: Phillip Raffeck <phillip.raffeck@fau.de>
---
 gpg-interface.c | 3 ---
 gpg-interface.h | 3 +++
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/gpg-interface.c b/gpg-interface.c
index 3dc2fe3..4b42860 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -7,9 +7,6 @@
 static char *configured_signing_key;
 static const char *gpg_program = "gpg";
 
-#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
-#define PGP_MESSAGE "-----BEGIN PGP MESSAGE-----"
-
 void signature_check_clear(struct signature_check *sigc)
 {
 	free(sigc->payload);
diff --git a/gpg-interface.h b/gpg-interface.h
index ea68885..04b0eab 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -4,6 +4,9 @@
 #define GPG_VERIFY_VERBOSE	1
 #define GPG_VERIFY_RAW		2
 
+#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+#define PGP_MESSAGE "-----BEGIN PGP MESSAGE-----"
+
 struct signature_check {
 	char *payload;
 	char *gpg_output;
-- 
2.8.0.rc0.62.gfc8aefa.dirty

```

## Anton Wuerfel, 2016-03-17 18:46

Subject: [PATCH 06/10] Add basic RFC3161 functionality
Message-ID: <1458240421-3593-7-git-send-email-anton.wuerfel@fau.de>
URL: https://gitlist.dev/e/1458240421-3593-7-git-send-email-anton.wuerfel%40fau.de
In-Reply-To: <1458240421-3593-1-git-send-email-anton.wuerfel@fau.de>

```
This commit adds basic RFC3161 functionality, which is linked to the main git
binary. It is used for interaction with git objects by parsing data from and
writing data to it. Moreover, it is responsible for passing data to the external
helper tool `git-timestamp-util`, which does the actual work of creating and
verifying trusted time-stamps.

Signed-off-by: Anton Würfel <anton.wuerfel@fau.de>
Signed-off-by: Phillip Raffeck <phillip.raffeck@fau.de>
---
 Makefile  |   1 +
 rfc3161.c | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 rfc3161.h |  12 ++++
 3 files changed, 232 insertions(+)
 create mode 100644 rfc3161.c
 create mode 100644 rfc3161.h

diff --git a/Makefile b/Makefile
index 24bef8d..432c3de 100644
--- a/Makefile
+++ b/Makefile
@@ -792,6 +792,7 @@ LIB_OBJS += replace_object.o
 LIB_OBJS += rerere.o
 LIB_OBJS += resolve-undo.o
 LIB_OBJS += revision.o
+LIB_OBJS += rfc3161.o
 LIB_OBJS += run-command.o
 LIB_OBJS += send-pack.o
 LIB_OBJS += sequencer.o
diff --git a/rfc3161.c b/rfc3161.c
new file mode 100644
index 0000000..21a386f
--- /dev/null
+++ b/rfc3161.c
@@ -0,0 +1,219 @@
+#include "cache.h"
+#include "commit.h"
+#include "run-command.h"
+#include "strbuf.h"
+#include "gpg-interface.h"
+#include "rfc3161.h"
+
+static const char *timeutil_cmd = "timestamp-util";
+static const char *ts_signature_begin = "-----BEGIN RFC3161-----";
+static const char *ts_signature_end = "-----END RFC3161-----";
+
+static void sha1_from_strbuf(struct strbuf *buf, unsigned char sha1[20]);
+static void sha1_in_hex(struct strbuf *buf, char sha1_hex[40]);
+
+static int verify_tsr(char *sha1, struct strbuf *base64);
+
+/*
+ * To create a time-stamp signature, get the SHA1 hash of buffer and pass it to
+ * git-timestamp-util. This helper program returns a minimalized TSR which is
+ * appended with some metadata and stored to the git object.
+ */
+int create_time_signature(struct strbuf *buffer, struct strbuf *sig)
+{
+	ssize_t len;
+	char sha1_hex[40];
+	struct strbuf tsr = STRBUF_INIT;
+	struct child_process timeutil = CHILD_PROCESS_INIT;
+	const char *args[] = {
+		timeutil_cmd,
+		"-c",
+		sha1_hex,
+		NULL
+	};
+
+	timeutil.argv = args;
+	timeutil.in = 0;
+	timeutil.out = -1;
+	timeutil.git_cmd = 1;
+
+	sha1_in_hex(buffer, sha1_hex);
+
+	if (start_command(&timeutil))
+		return error(_("could not run git-%s"), timeutil_cmd);
+
+	len = strbuf_read(&tsr, timeutil.out, 1024);
+	close(timeutil.out);
+
+	if (finish_command(&timeutil) || len <= 0)
+		return 1;
+
+	strbuf_addf(sig, "%s\n", ts_signature_begin);
+	strbuf_addf(sig, "Version: 1\n\n");
+	strbuf_addbuf(sig, &tsr);
+	strbuf_addf(sig, "%s\n", ts_signature_end);
+
+	strbuf_release(&tsr);
+	return 0;
+}
+
+/*
+ * To verify a time-stamp signature, extract the time-stamp from a git object,
+ * extract the object id and pass the prepared data to git-timestamp-util, which
+ * will do the verification for us.
+ */
+int verify_time_signature(const char *buf, unsigned long size)
+{
+	char sha1_hex[40];
+	struct strbuf timesig_base64 = STRBUF_INIT;
+	struct strbuf payload = STRBUF_INIT;
+	int ret = 1;
+
+	printf("\n");
+
+	if (!parse_timestamp(buf, size, &timesig_base64, &payload)) {
+		printf("time-stamp: no signature found\n");
+		return 1;
+	}
+
+	/*
+	 * Remove possible inline GPG signature from git object. Note that git
+	 * tags do not use inline signatures for historical reasons, so checking
+	 * the return value of remove_signature would be useless.
+	 * Any tag-like signatures outside of the object header are already
+	 * removed by parse_timestamp.
+	 */
+	remove_signature(&payload);
+	sha1_in_hex(&payload, sha1_hex);
+
+	if (verify_tsr(sha1_hex, &timesig_base64))
+		goto err;
+
+	ret = 0;
+err:
+	strbuf_release(&timesig_base64);
+	strbuf_release(&payload);
+	return ret;
+}
+
+int parse_timestamp(const char *buffer, size_t size, struct strbuf *timestamp,
+		    struct strbuf *payload)
+{
+	int saw_timestamp = -1;
+	const char *line, *tail, *next;
+
+	line = buffer;
+	tail = buffer + size;
+	saw_timestamp = 0;
+
+	/* Search for beginning of time-stamp. */
+	while (line < tail) {
+		next = memchr(line, '\n', tail - line);
+
+		next = next ? next + 1 : tail;
+		if (starts_with(line, time_sig_header) &&
+		    line[time_sig_header_len] == ' ')
+			break;
+
+		strbuf_add(payload, line, next - line);
+		line = next;
+	}
+
+	if (line >= tail)
+		return saw_timestamp;
+
+	/*
+	 * Found beginning of time-stamp.  Do not add -----BEGIN RFC3161-----,
+	 * version information and the separating newline to the buffer.
+	 */
+	line = memchr(line, '\n', tail - line) + 1;
+	line = memchr(line, '\n', tail - line) + 1;
+	line = memchr(line, '\n', tail - line) + 1;
+	saw_timestamp = 1;
+
+	/* Read in time-stamp data. */
+	while (line < tail) {
+		next = memchr(line, '\n', tail - line);
+
+		next = next ? next + 1 : tail;
+		if (line[0] == ' ')
+			line = line + 1;
+		else
+			break;
+
+		/* do not add -----END RFC3161----- to buffer */
+		if (starts_with(line, ts_signature_end)) {
+			line = next;
+			break;
+		}
+
+		strbuf_add(timestamp, line, next - line);
+		line = next;
+	}
+
+	while (line < tail) {
+		next = memchr(line, '\n', tail - line);
+		next = next ? next + 1 : tail;
+
+		if (starts_with(line, PGP_SIGNATURE))
+			break;
+
+		strbuf_add(payload, line, next - line);
+		line = next;
+	}
+
+	return saw_timestamp;
+}
+
+/* Pass the prepared data to git-timestamp-util */
+static int verify_tsr(char *sha1, struct strbuf *base64)
+{
+	struct child_process timeutil = CHILD_PROCESS_INIT;
+	const char *args[] = {
+		timeutil_cmd,
+		"-v",
+		sha1,
+		NULL
+	};
+
+	timeutil.argv = args;
+	timeutil.in = -1;
+	timeutil.out = 0;
+	timeutil.git_cmd = 1;
+
+	if (start_command(&timeutil))
+		return error(_("could not run git-%s"), timeutil_cmd);
+
+	if (write_in_full(timeutil.in, base64->buf, base64->len)
+			  != base64->len) {
+		close(timeutil.in);
+		finish_command(&timeutil);
+		return 1;
+	}
+	close(timeutil.in);
+
+	if (finish_command(&timeutil))
+		return 1;
+
+	return 0;
+}
+
+static void sha1_from_strbuf(struct strbuf *buf, unsigned char sha1[20])
+{
+	git_SHA_CTX c;
+
+	git_SHA1_Init(&c);
+	git_SHA1_Update(&c, buf->buf, buf->len);
+	git_SHA1_Final(sha1, &c);
+}
+
+static void sha1_in_hex(struct strbuf *buf, char sha1_hex[40])
+{
+	char *tmp;
+	unsigned char sha1[20];
+
+	sha1_from_strbuf(buf, sha1);
+	tmp = sha1_to_hex(sha1);
+	strcpy(sha1_hex, tmp);
+}
+
diff --git a/rfc3161.h b/rfc3161.h
new file mode 100644
index 0000000..eac91ae
--- /dev/null
+++ b/rfc3161.h
@@ -0,0 +1,12 @@
+#ifndef RFC3161_H
+#define RFC3161_H
+
+#define time_sig_header "timesig"
+#define time_sig_header_len (sizeof(time_sig_header) - 1)
+
+int create_time_signature(struct strbuf *buffer, struct strbuf *sig);
+int verify_time_signature(const char *buf, unsigned long size);
+int parse_timestamp(const char *buffer, size_t size, struct strbuf *timestamp,
+		    struct strbuf *payload);
+
+#endif
-- 
2.8.0.rc0.62.gfc8aefa.dirty

```

## Anton Wuerfel, 2016-03-17 18:46

Subject: [PATCH 07/10] Add git-http-timestamp helper tool
Message-ID: <1458240421-3593-8-git-send-email-anton.wuerfel@fau.de>
URL: https://gitlist.dev/e/1458240421-3593-8-git-send-email-anton.wuerfel%40fau.de
In-Reply-To: <1458240421-3593-1-git-send-email-anton.wuerfel@fau.de>

```
This commit adds the git-http-timestamp helper tool. It does all the
communication to a Time Stamping Authority via libcurl and passes the received
data to the caller via stdout.

Libcurl by default depends on GnuTLS, the new RFC3161 time-stamping
functionality depends on libcrypto (OpenSSL) as GnuTLS does not support RFC3161
time-stamps. The git-http-timestamp helper tool is introduced to avoid linking
OpenSSL and GnuTLS together in a single binary.

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

diff --git a/.gitignore b/.gitignore
index 5087ce1..a3b270d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -70,6 +70,7 @@
 /git-http-backend
 /git-http-fetch
 /git-http-push
+/git-http-timestamp
 /git-imap-send
 /git-index-pack
 /git-init
diff --git a/Makefile b/Makefile
index 432c3de..c717af7 100644
--- a/Makefile
+++ b/Makefile
@@ -1116,6 +1116,9 @@ else
 			BASIC_CFLAGS += -DEXPAT_NEEDS_XMLPARSE_H
 		endif
 	endif
+
+	PROGRAM_OBJS += http-timestamp.o
+	PROGRAMS += git-http-timestamp$X
 endif
 IMAP_SEND_LDFLAGS += $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO)
 
@@ -2018,6 +2021,10 @@ git-remote-testsvn$X: remote-testsvn.o GIT-LDFLAGS $(GITLIBS) $(VCSSVN_LIB)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) \
 	$(VCSSVN_LIB)
 
+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)
+
 $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)
 	$(QUIET_LNCP)$(RM) $@ && \
 	ln $< $@ 2>/dev/null || \
diff --git a/command-list.txt b/command-list.txt
index 2a94137..3e279c1 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -68,6 +68,7 @@ git-help                                ancillaryinterrogators
 git-http-backend                        synchingrepositories
 git-http-fetch                          synchelpers
 git-http-push                           synchelpers
+git-http-timestamp                      purehelpers
 git-imap-send                           foreignscminterface
 git-index-pack                          plumbingmanipulators
 git-init                                mainporcelain           init
diff --git a/http-timestamp.c b/http-timestamp.c
new file mode 100644
index 0000000..86b42e0
--- /dev/null
+++ b/http-timestamp.c
@@ -0,0 +1,76 @@
+#include "cache.h"
+#include "strbuf.h"
+#include "http.h"
+
+static int obtain_tsr(struct strbuf *tsq, struct strbuf *tsr);
+
+static void usage_and_die(const char *name);
+
+static const char *config_tsa_url_key = "ts.tsaurl";
+
+int main(int argc, const char *argv[])
+{
+	struct strbuf tsr = STRBUF_INIT;
+	struct strbuf tsq = STRBUF_INIT;
+	int ret;
+
+	if (argc != 1)
+		usage_and_die(argv[0]);
+
+	if (strbuf_fread(&tsq, 1024, stdin) < 0) {
+		strbuf_release(&tsq);
+		return error(_("strbuf_fread failed: %s"), strerror(errno));
+	}
+
+	ret = obtain_tsr(&tsq, &tsr);
+
+	if (!ret)
+		write_in_full(fileno(stdout), tsr.buf, tsr.len);
+
+	strbuf_release(&tsr);
+	strbuf_release(&tsq);
+	return ret;
+}
+
+static int obtain_tsr(struct strbuf *tsq, struct strbuf *tsr)
+{
+	struct strbuf content_type = STRBUF_INIT;
+	struct http_request_options options = {0};
+
+	char *config_tsa_url;
+
+	if (git_config_get_string(config_tsa_url_key, &config_tsa_url)) {
+		die(_("git config option '%s' must be set"),
+		      config_tsa_url_key);
+	}
+
+	/* libcurl stuff */
+
+	http_init(NULL, config_tsa_url, 0);
+
+	strbuf_addstr(&content_type, "application/timestamp-query");
+	options.post_content_type = &content_type;
+	options.postfields = tsq;
+
+	if (http_get_strbuf(config_tsa_url, tsr, &options)) {
+		strbuf_release(&content_type);
+		return error(_("sending time-stamp query failed"));
+	}
+
+	strbuf_release(&content_type);
+	free(config_tsa_url);
+	http_cleanup();
+
+	return 0;
+}
+
+static void usage_and_die(const char *name)
+{
+	fprintf(stderr, "Usage: %s\n\n", name);
+	fputs("Obtain a trusted time-stamp from the Time Stamping Authority\n"
+	      "specified in configuration variable `ts.tsaurl` and write the\n"
+	      "result to stdout.\n",
+	      stderr);
+
+	exit(EXIT_FAILURE);
+}
-- 
2.8.0.rc0.62.gfc8aefa.dirty

```

## Anton Wuerfel, 2016-03-17 18:46

Subject: [PATCH 08/10] Adding time-stamping helper tool
Message-ID: <1458240421-3593-9-git-send-email-anton.wuerfel@fau.de>
URL: https://gitlist.dev/e/1458240421-3593-9-git-send-email-anton.wuerfel%40fau.de
In-Reply-To: <1458240421-3593-1-git-send-email-anton.wuerfel@fau.de>

```
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@fau.de>
Signed-off-by: Phillip Raffeck <phillip.raffeck@fau.de>
---
 .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

```

## Anton Wuerfel, 2016-03-17 18:47

Subject: [PATCH 09/10] Add time-stamping functionality to git verify-tag
Message-ID: <1458240421-3593-10-git-send-email-anton.wuerfel@fau.de>
URL: https://gitlist.dev/e/1458240421-3593-10-git-send-email-anton.wuerfel%40fau.de
In-Reply-To: <1458240421-3593-1-git-send-email-anton.wuerfel@fau.de>

```
This commit introduces command line options for git verify-tag to allow
verification of RFC3161 time-stamped tags.

To keep consistent with the current behavior of verifying gpg signatures, the
return value of `git verify-tag` still indicates only the success of gpg
signature verification by default. To influence this behavior, the configuration
variable `ts.failonverify` is introduced.

ts.failonverify set to 0: use default behavior
	Return 1 if gpg verification failed or no signature was found
	Return 0 on success.

If config variable ts.failonverify is set, the return value includes the status
of time-stamp verification in the second lowest bit. Possible return values are:
	0 - both gpg and time stamp verification succeeded
	1 - gpg failed, time stamp succeeded
	2 - gpg succeeded, time stamp failed
	3 - both gpg and time stamp verification failed

Command line parameters `-s` or `-t` override the configuration
variable mentioned above.

Signed-off-by: Anton Würfel <anton.wuerfel@fau.de>
Signed-off-by: Phillip Raffeck <phillip.raffeck@fau.de>
---
 builtin/verify-tag.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 57 insertions(+), 4 deletions(-)

diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c
index 00663f6..67fd464 100644
--- a/builtin/verify-tag.c
+++ b/builtin/verify-tag.c
@@ -12,9 +12,15 @@
 #include <signal.h>
 #include "parse-options.h"
 #include "gpg-interface.h"
+#include "rfc3161.h"
+
+#define GPG_VERIFY_RET 4
+#define TS_VERIFY_RET 8
+
+static const char *config_key_verify_ts = "ts.failonverify";
 
 static const char * const verify_tag_usage[] = {
-		N_("git verify-tag [-v | --verbose] <tag>..."),
+		N_("git verify-tag [-s | --signature] [-t | --timestamp] [-v | --verbose] [--raw] <tag>..."),
 		NULL
 };
 
@@ -31,7 +37,8 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)
 	if (size == len) {
 		if (flags & GPG_VERIFY_VERBOSE)
 			write_in_full(1, buf, len);
-		return error("no signature found");
+		printf("pgp: no signature found\n");
+		return 1;
 	}
 
 	ret = check_signature(buf, len, buf + len, size - len, &sigc);
@@ -47,7 +54,12 @@ static int verify_tag(const char *name, unsigned flags)
 	unsigned char sha1[20];
 	char *buf;
 	unsigned long size;
-	int ret;
+	int gpg_had_error = 0;
+	int ret = 0;
+	int config_failonverify = 0;
+	int ts_had_error = 0;
+
+	git_config_get_bool(config_key_verify_ts, &config_failonverify);
 
 	if (get_sha1(name, sha1))
 		return error("tag '%s' not found.", name);
@@ -61,9 +73,48 @@ static int verify_tag(const char *name, unsigned flags)
 	if (!buf)
 		return error("%s: unable to read file.", name);
 
-	ret = run_gpg_verify(buf, size, flags);
+	if (run_gpg_verify(buf, size, flags))
+		gpg_had_error = 1;
+
+#if defined(NO_CURL) || defined(NO_OPENSSL)
+	fputs("git has been compiled without RFC3161 time-stamp support. "
+	      "NO_CURL and NO_OPENSSL must not be defined", stderr);
+#else
+	if (verify_time_signature(buf, size))
+		ts_had_error = 1;
+#endif
 
 	free(buf);
+
+	/*
+	 * If the config variable ts.failonverify is not set, behave like older
+	 * versions of git verify-tag:
+	 * Return 1 if gpg verification failed or no signature was found
+	 * Return 0 on success.
+	 *
+	 * If the config variable ts.failonverify is set and neither -s nor -t
+	 * are set as command line parameters, the return value includes the
+	 * status of time-stamp verification in the second lowest bit. Possible
+	 * return values are:
+	 * 0 - both gpg and time stamp verification succeeded
+	 * 1 - gpg failed, time stamp succeeded
+	 * 2 - gpg succeeded, time stamp failed
+	 * 3 - both gpg and time stamp verification failed
+	 *
+	 * Command line parameters `-s` or `-t` override the configuration
+	 * variable mentioned above.
+	 */
+	if (flags & (GPG_VERIFY_RET | TS_VERIFY_RET)) {
+		if (flags & GPG_VERIFY_RET)
+			ret = gpg_had_error;
+		if (flags & TS_VERIFY_RET)
+			ret |= (ts_had_error << 1);
+	} else {
+		ret = gpg_had_error;
+
+		if (config_failonverify)
+			ret |= (ts_had_error << 1);
+	}
 	return ret;
 }
 
@@ -82,6 +133,8 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)
 	const struct option verify_tag_options[] = {
 		OPT__VERBOSE(&verbose, N_("print tag contents")),
 		OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW),
+		OPT_BIT('s', "signature", &flags, N_("report return value of gpg signature"), GPG_VERIFY_RET),
+		OPT_BIT('t', "timestamp", &flags, N_("report return value of time-stamp signature"), TS_VERIFY_RET),
 		OPT_END()
 	};
 
-- 
2.8.0.rc0.62.gfc8aefa.dirty

```

## Anton Wuerfel, 2016-03-17 18:47

Subject: [PATCH 10/10] Add time-stamping functionality to git tag
Message-ID: <1458240421-3593-11-git-send-email-anton.wuerfel@fau.de>
URL: https://gitlist.dev/e/1458240421-3593-11-git-send-email-anton.wuerfel%40fau.de
In-Reply-To: <1458240421-3593-1-git-send-email-anton.wuerfel@fau.de>

```
This commit introduces command line options for git tag to allow adding trusted
time-stamps from a Time Stamping Authority according to RFC3161.

The SHA-1 has used for a time-stamp signature is generated from the header data
and the tag message, if present. After obtaining the time-stamp signature, it is
inserted into the object header under the `timesig`-key in a custom PEM-like
format. If the tag is also GPG-signed, the GPG signature includes the time-stamp
signature to prevent attackers from altering the time-stamp signature or
replacing it.

However, it is still possible to create tags with only a GPG signature or only a
time-stamp, although it is recommended to additionally GPG-sign time-stamp
signatures for the reasons stated above.

In contrast to the GPG signature, the time-stamp signatures are part of
the header, emulating the way GPG signatures of signed commits are stored. This
facilitates implementing RFC3161 time-stamps for commits eventually.

Signed-off-by: Anton Würfel <anton.wuerfel@fau.de>
Signed-off-by: Phillip Raffeck <phillip.raffeck@fau.de>
---
 builtin/tag.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 52 insertions(+), 3 deletions(-)

diff --git a/builtin/tag.c b/builtin/tag.c
index 1705c94..9b3d2a1 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -18,9 +18,10 @@
 #include "sha1-array.h"
 #include "column.h"
 #include "ref-filter.h"
+#include "rfc3161.h"
 
 static const char * const git_tag_usage[] = {
-	N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
+	N_("git tag [-a | -s | -u <key-id> | -t] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
 	N_("git tag -d <tagname>..."),
 	N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
 		"\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
@@ -118,6 +119,39 @@ static int do_sign(struct strbuf *buffer)
 	return sign_buffer(buffer, buffer, get_signing_key());
 }
 
+static int do_timesig(struct strbuf *buffer)
+{
+	struct strbuf sig = STRBUF_INIT;
+	int inspos, copypos;
+
+	/* find the end of the header */
+	inspos = strstr(buffer->buf, "\n\n") - buffer->buf + 1;
+
+	if (create_time_signature(buffer, &sig)) {
+		strbuf_release(&sig);
+		return -1;
+	}
+
+	for (copypos = 0; sig.buf[copypos]; ) {
+		const char *bol = sig.buf + copypos;
+		const char *eol = strchrnul(bol, '\n');
+		int len = (eol - bol) + !!*eol;
+
+		if (!copypos) {
+			strbuf_insert(buffer, inspos, time_sig_header,
+				      time_sig_header_len);
+			inspos += time_sig_header_len;
+		}
+		strbuf_insert(buffer, inspos++, " ", 1);
+		strbuf_insert(buffer, inspos, bol, len);
+		inspos += len;
+		copypos += len;
+	}
+	strbuf_release(&sig);
+
+	return 0;
+}
+
 static const char tag_template[] =
 	N_("\nWrite a message for tag:\n  %s\n"
 	"Lines starting with '%c' will be ignored.\n");
@@ -193,8 +227,11 @@ static void write_tag_body(int fd, const unsigned char *sha1)
 	free(buf);
 }
 
-static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)
+static int build_tag_object(struct strbuf *buf, int sign, int timesig,
+			    unsigned char *result)
 {
+	if (timesig && do_timesig(buf) < 0)
+		return error(_("unable to generate time-stamp signature"));
 	if (sign && do_sign(buf) < 0)
 		return error(_("unable to sign the tag"));
 	if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0)
@@ -205,6 +242,7 @@ static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)
 struct create_tag_options {
 	unsigned int message_given:1;
 	unsigned int sign;
+	unsigned int timesig;
 	enum {
 		CLEANUP_NONE,
 		CLEANUP_SPACE,
@@ -276,7 +314,7 @@ static void create_tag(const unsigned char *object, const char *tag,
 
 	strbuf_insert(buf, 0, header_buf, header_len);
 
-	if (build_tag_object(buf, opt->sign, result) < 0) {
+	if (build_tag_object(buf, opt->sign, opt->timesig, result) < 0) {
 		if (path)
 			fprintf(stderr, _("The tag message has been left in %s\n"),
 				path);
@@ -350,6 +388,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 			     N_("tag message"), parse_msg_arg),
 		OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
 		OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
+		OPT_BOOL('t', "timestamp", &opt.timesig, N_("add trusted RFC3161 time-stamp")),
 		OPT_STRING(0, "cleanup", &cleanup_arg, N_("mode"),
 			N_("how to strip spaces and #comments from message")),
 		OPT_STRING('u', "local-user", &keyid, N_("key-id"),
@@ -387,6 +426,16 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 	}
 	if (opt.sign)
 		annotate = 1;
+
+#if defined(NO_CURL) || defined(NO_OPENSSL)
+	if (opt.timesig)
+		return error("git has been compiled without RFC3161 time-stamp support. "
+			     "NO_CURL and NO_OPENSSL must not be defined");
+#else
+	if (opt.timesig)
+		annotate = 1;
+#endif
+
 	if (argc == 0 && !cmdmode)
 		cmdmode = 'l';
 
-- 
2.8.0.rc0.62.gfc8aefa.dirty

```
