{"thread":{"id":"41730","subject":"[PATCH 01/10] Add Testcases for time-stamping functionality","startedAt":"2016-03-17T18:46:51Z","lastAt":"2016-03-17T18:47:01Z","messageCount":11,"participants":["Anton Wuerfel"],"isPatch":true,"patchVersion":1,"patchTotal":10},"messages":[{"id":"281076","messageId":"1458240421-3593-1-git-send-email-anton.wuerfel@fau.de","threadId":"41730","inReplyTo":null,"subject":"[PATCH 00/10] Adding RFC3161 time-stamps to tags","fromName":"Anton Wuerfel","fromEmail":"anton.wuerfel@fau.de","sentAt":"2016-03-17T18:46:51Z","receivedAt":"2016-03-17T18:46:51Z","isPatch":true,"sender":{"key":"anton.wuerfel@fau.de","avatar":null},"body":"Hi all,\n\nthis patch series adds RFC3161 time-stamping functionality to git tags.\nThis enables users to create and verify cryptographic time stamp signatures to\nprove that a commit existed at a certain point in time.\n\nThe new RFC3161 time-stamping functionality is independent from GPG signatures.\nHowever, GPG signatures include time-stamp signatures and therefore make them\nrobust against removal or replacement.\n\nThe time stamp signature is stored in the git tag object header under the\n`timesig` key, which is equivalent to the format for GPG signatures in signed\ncommits. This facilitates a possible implementation of time-stamped commits in\nthe future.\n\nWe added two new helper commands, because of their dependencies on libcurl and\nlibcrypto respectively. If one of the libraries is missing, a warning message is\ngiven when one of the new functionalities is used.\n\nWe tried to add as much documentation as possible. Especially timestamp-util.c,\nwhich depends on libcrypto, is a bit tough to read.\n\nSome configuration variables have been added, where `ts.tsaurl` is the most\nimportant, which specifies the Time Stamping Autority to use.\n\nFor testing the implemented functionality, the Time Stamping Authority\nSafeCreative can be used, which is free for non-commercial use:\nhttps://tsa.safecreative.org.\nSimply set the config variable ts.tsaurl to this TSA.\n\nYour feedback is welcome.\nKind regards,\nAnton Wuerfel\nPhillip Raffeck\n\nAnton Wuerfel (10):\nPhillip Raffeck (10):\n  Add Testcases for time-stamping functionality\n  Documentation for time-stamping functionality\n  Documentation: Whitespace fix\n  Extending internal CURL wrapper for POST upload\n  Make PGP macros global\n  Add basic RFC3161 functionality\n  Add git-http-timestamp helper tool\n  Adding time-stamping helper tool\n  Add time-stamping functionality to git verify-tag\n  Add time-stamping functionality to git tag\n\n .gitignore                           |   2 +\n Documentation/config.txt             |  20 ++\n Documentation/git-http-timestamp.txt |  32 ++\n Documentation/git-tag.txt            |  32 +-\n Documentation/git-timestamp-util.txt |  52 +++\n Documentation/git-tsa-store.txt      |  68 ++++\n Documentation/git-verify-tag.txt     |  28 +-\n Makefile                             |  15 +\n builtin/tag.c                        |  55 +++-\n builtin/verify-tag.c                 |  61 +++-\n command-list.txt                     |   2 +\n gpg-interface.c                      |   3 -\n gpg-interface.h                      |   3 +\n http-timestamp.c                     |  76 +++++\n http.c                               |  30 +-\n http.h                               |  17 +-\n remote-curl.c                        |   2 +-\n rfc3161.c                            | 219 +++++++++++++\n rfc3161.h                            |  12 +\n t/t7031-verify-tag.sh                |  69 ++++\n timestamp-util.c                     | 615 +++++++++++++++++++++++++++++++++++\n 21 files changed, 1380 insertions(+), 33 deletions(-)\n create mode 100644 Documentation/git-http-timestamp.txt\n create mode 100644 Documentation/git-timestamp-util.txt\n create mode 100644 Documentation/git-tsa-store.txt\n create mode 100644 http-timestamp.c\n create mode 100644 rfc3161.c\n create mode 100644 rfc3161.h\n create mode 100755 t/t7031-verify-tag.sh\n create mode 100644 timestamp-util.c\n\n-- \n2.8.0.rc0.62.gfc8aefa.dirty\n"},{"id":"281067","messageId":"1458240421-3593-2-git-send-email-anton.wuerfel@fau.de","threadId":"41730","inReplyTo":"1458240421-3593-1-git-send-email-anton.wuerfel@fau.de","subject":"[PATCH 01/10] Add Testcases for time-stamping functionality","fromName":"Anton Wuerfel","fromEmail":"anton.wuerfel@fau.de","sentAt":"2016-03-17T18:46:52Z","receivedAt":"2016-03-17T18:46:52Z","isPatch":true,"sender":{"key":"anton.wuerfel@fau.de","avatar":null},"body":"This commit adds testcases for the new time-stamping functionality. To trigger\nthese testcases, the environment variables $GIT_TEST_TSA_URL and\n$GIT_TEST_TSA_CAPATH have to be set. They do not default to any value to avoid\nspamming a pre-defined Time Stamping Authority (TSA).\nAs testing the RFC3161 functionality without a trusted time-stamp signature is\nnot possible, all tests are disabled by default.\n\nSigned-off-by: Anton Würfel <anton.wuerfel@fau.de>\nSigned-off-by: Phillip Raffeck <phillip.raffeck@fau.de>\n---\n t/t7031-verify-tag.sh | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 69 insertions(+)\n create mode 100755 t/t7031-verify-tag.sh\n\ndiff --git a/t/t7031-verify-tag.sh b/t/t7031-verify-tag.sh\nnew file mode 100755\nindex 0000000..bc7953b\n--- /dev/null\n+++ b/t/t7031-verify-tag.sh\n@@ -0,0 +1,69 @@\n+#!/bin/sh\n+\n+test_description='signed time-stamped tag tests'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-gpg.sh\"\n+\n+# Tests for RFC3161 implementation\n+\n+if test -n \"$NO_OPENSSL\"\n+then\n+\tskip_all='skipping test, git built without openssl support'\n+\ttest_done\n+fi\n+\n+# Disable remote tests by default to avoid spamming of TSAs and to be able to\n+# test without internet connection\n+\n+if test -z \"$GIT_TEST_TSA_URL\"\n+then\n+\tskip_all='Remote TSA testing disabled (set GIT_TEST_TSA_URL to enable)'\n+\ttest_done\n+fi\n+\n+if test -z \"$GIT_TEST_TSA_CAPATH\"\n+then\n+\tskip_all='Remote TSA testing disabled (set GIT_TEST_TSA_CAPATH to enable)'\n+\ttest_done\n+fi\n+\n+test_expect_success 'create a tag with a timestamp signature' '\n+\tgit config ts.tsaurl $GIT_TEST_TSA_URL &&\n+\tgit config ts.capath $GIT_TEST_TSA_CAPATH &&\n+\tgit config ts.failonverify 1 &&\n+\ttest_tick &&\n+\techo foo > foo &&\n+\tgit add foo &&\n+\tgit commit -m foo &&\n+\tgit tag -t -m onlytime tsa_onlytime\n+'\n+\n+test_expect_success GPG 'create a tag with gpg and timestamp signatures' '\n+\techo foobar > bar &&\n+\tgit add bar &&\n+\tgit commit -m test123 &&\n+\tgit tag -s -t -m tsa_both tsa_both\n+'\n+\n+test_expect_success 'verify tag with gpg and timestamp signature' '\n+\tgit verify-tag tsa_both\n+'\n+\n+test_expect_success 'verify tag with timestamp signature' '\n+\ttest_must_fail git verify-tag tsa_onlytime\n+'\n+\n+test_expect_success 'verify tag with timestamp signature with -t' '\n+\tgit verify-tag -t tsa_onlytime\n+'\n+\n+test_expect_success 'verify fudged timestamp' '\n+\tgit cat-file tag tsa_onlytime >raw &&\n+\tsed -e \"s/onlytime/morethantime/\" raw >forged1 &&\n+\tgit hash-object -w -t tag forged1 >forged1.tag &&\n+\ttest_must_fail git verify-tag $(cat forged1.tag) >actual1 2>&1 &&\n+\tgrep \"BAD time-stamp signature\" actual1 &&\n+\t! grep \"Verified timestamp\" actual1\n+'\n+\n+test_done\n-- \n2.8.0.rc0.62.gfc8aefa.dirty\n"},{"id":"281077","messageId":"1458240421-3593-3-git-send-email-anton.wuerfel@fau.de","threadId":"41730","inReplyTo":"1458240421-3593-1-git-send-email-anton.wuerfel@fau.de","subject":"[PATCH 02/10] Documentation for time-stamping functionality","fromName":"Anton Wuerfel","fromEmail":"anton.wuerfel@fau.de","sentAt":"2016-03-17T18:46:53Z","receivedAt":"2016-03-17T18:46:53Z","isPatch":true,"sender":{"key":"anton.wuerfel@fau.de","avatar":null},"body":"This commit adds documentation for newly introduced helper commands,\nconfiguration variables and the certificate storage file `.git_tsa_store`. The\ndocumentation pages for `git tag` and git `verify-tag` are altered to describe\nthe new command line options for time-stamping.\n\nSigned-off-by: Anton Würfel <anton.wuerfel@fau.de>\nSigned-off-by: Phillip Raffeck <phillip.raffeck@fau.de>\n---\n Documentation/config.txt             | 20 +++++++++++\n Documentation/git-http-timestamp.txt | 32 +++++++++++++++++\n Documentation/git-tag.txt            | 30 +++++++++++-----\n Documentation/git-timestamp-util.txt | 52 +++++++++++++++++++++++++++\n Documentation/git-tsa-store.txt      | 68 ++++++++++++++++++++++++++++++++++++\n Documentation/git-verify-tag.txt     | 28 ++++++++++++---\n 6 files changed, 218 insertions(+), 12 deletions(-)\n create mode 100644 Documentation/git-http-timestamp.txt\n create mode 100644 Documentation/git-timestamp-util.txt\n create mode 100644 Documentation/git-tsa-store.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 2cd6bdd..8be5fe6 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -2774,6 +2774,26 @@ transfer.unpackLimit::\n \tnot set, the value of this variable is used instead.\n \tThe default value is 100.\n \n+ts.capath::\n+\tThis variable contains a path to a directory with trusted certificates.\n+\tThis is necessary to verify the authenticity of RFC3161 time-stamps in\n+\ttime-stamped tags with `git tag -v` or `git verify-tag`. There is no\n+\tdefault value.\n+\n+ts.failonverify::\n+\tThis variable controls the return value semantics of `git verify-tag`.\n+\tIf set, both the return values of the gpg signature and the time-stamp\n+\tverification are reported. If the least significant bit is set, it\n+\tindicates a failure during the gpg signature verification, while the\n+\tsecond lowest bit reports the return value of the time-stamp verification.\n+\tPer default, only the return value of the gpg signature verification is\n+\treported to remain consistent with legacy code.\n+\n+ts.tsaurl::\n+\tThis variable contains the URL of the Time Stamping Authority from which\n+\tRFC3161 time-stamps should be obtained when creating time-stamped tags\n+\twith `git tag`. There is no default value.\n+\n uploadarchive.allowUnreachable::\n \tIf true, allow clients to use `git archive --remote` to request\n \tany tree, whether reachable from the ref tips or not. See the\ndiff --git a/Documentation/git-http-timestamp.txt b/Documentation/git-http-timestamp.txt\nnew file mode 100644\nindex 0000000..1c7781e\n--- /dev/null\n+++ b/Documentation/git-http-timestamp.txt\n@@ -0,0 +1,32 @@\n+git-http-timestamp(1)\n+=====================\n+\n+NAME\n+----\n+git-http-timestamp - Helper program to communicate with a RFC3161 Time\n+Stamping Authority.\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git http-timestamp'\n+\n+DESCRIPTION\n+-----------\n+Offers an interface for the communication with a RFC3161 Time Stamping Authority\n+to obtain a trusted time-stamp signature for a Time Stamping Query. The Time\n+Stamping Query is read from stdin in DER format.\n+The response received from the Time Stamping Authority is written to stdout.\n+The URL of the Time Stamping Authority must be given using the configuration\n+variable `ts.tsaurl`.\n+\n+NOTES\n+-----\n+The main purpose of this command is to be called from the internal mechanism of\n+`git tag` or `git verify-tag`. Some commands are implemented in this separate\n+binary to allow the usage of libcurl without building all of git against it.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex abab481..940676e 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -9,7 +9,7 @@ git-tag - Create, list, delete or verify a tag object signed with GPG\n SYNOPSIS\n --------\n [verse]\n-'git tag' [-a | -s | -u <keyid>] [-f] [-m <msg> | -F <file>]\n+'git tag' [-a | -s | -t | -u <keyid>] [-f] [-m <msg> | -F <file>]\n \t<tagname> [<commit> | <object>]\n 'git tag' -d <tagname>...\n 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]\n@@ -25,12 +25,12 @@ to delete, list or verify tags.\n \n Unless `-f` is given, the named tag must not yet exist.\n \n-If one of `-a`, `-s`, or `-u <keyid>` is passed, the command\n+If one of `-a`, `-s`, `-t`, or `-u <keyid>` is passed, the command\n creates a 'tag' object, and requires a tag message.  Unless\n `-m <msg>` or `-F <file>` is given, an editor is started for the user to type\n in the tag message.\n \n-If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <keyid>`\n+If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, `-t`, and `-u <keyid>`\n are absent, `-a` is implied.\n \n Otherwise just a tag reference for the SHA-1 object name of the commit object is\n@@ -42,10 +42,14 @@ committer identity for the current user is used to find the\n GnuPG key for signing. \tThe configuration variable `gpg.program`\n is used to specify custom GnuPG binary.\n \n-Tag objects (created with `-a`, `-s`, or `-u`) are called \"annotated\"\n+A tag containing a time-stamp according to RFC3161 will be created when `-t` is\n+used. The configuration variable `ts.tsaurl` is used to specify the URL of the\n+Time Stamping Authority from which the time-stamp should be obtained.\n+\n+Tag objects (created with `-a`, `-s`, `-t`, or `-u`) are called \"annotated\"\n tags; they contain a creation date, the tagger name and e-mail, a\n-tagging message, and an optional GnuPG signature. Whereas a\n-\"lightweight\" tag is simply a name for an object (usually a commit\n+tagging message, an optional GnuPG signature, and an optional RFC3161 time-stamp.\n+Whereas a \"lightweight\" tag is simply a name for an object (usually a commit\n object).\n \n Annotated tags are meant for release while lightweight tags are meant\n@@ -64,6 +68,10 @@ OPTIONS\n --sign::\n \tMake a GPG-signed tag, using the default e-mail address's key.\n \n+-t::\n+--time-stamp::\n+\tAdd a RFC3161 time-stamp to the tag.\n+\n -u <keyid>::\n --local-user=<keyid>::\n \tMake a GPG-signed tag, using the given key.\n@@ -78,7 +86,13 @@ OPTIONS\n \n -v::\n --verify::\n-\tVerify the gpg signature of the given tag names.\n+\tVerify, if present, the gpg signature and the RFC3161 time-stamp of the\n+\tgiven tag names. The configuration variable `ts.failonverify` specifies\n+\tif the return value of the time-stamp verification should be reported.\n+\tIf set, the return values of both the gpg signature and the time-stamp\n+\tverfification are considered. Per default, only the return value of the\n+\tgpg signature verfification is reported to remain consistent with legacy\n+\tcode.\n \n -n<num>::\n \t<num> specifies how many lines from the annotation, if any,\n@@ -128,7 +142,7 @@ This option is only applicable when listing tags without annotation lines.\n \tUse the given tag message (instead of prompting).\n \tIf multiple `-m` options are given, their values are\n \tconcatenated as separate paragraphs.\n-\tImplies `-a` if none of `-a`, `-s`, or `-u <keyid>`\n+\tImplies `-a` if none of `-a`, `-s`, `-t`, or `-u <keyid>`\n \tis given.\n \n -F <file>::\ndiff --git a/Documentation/git-timestamp-util.txt b/Documentation/git-timestamp-util.txt\nnew file mode 100644\nindex 0000000..4724e6f\n--- /dev/null\n+++ b/Documentation/git-timestamp-util.txt\n@@ -0,0 +1,52 @@\n+git-timestamp-util(1)\n+=====================\n+\n+NAME\n+----\n+git-timestamp-util - Helper tool to manage RFC3161 time-stamps.\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git timestamp-util' [-c <sha1>] [-v <sha1>]\n+\n+DESCRIPTION\n+-----------\n+Offers an interface to obtain a time-stamp signature for a given SHA1 hash or to\n+verify the time-stamp of a given SHA1 hash. The signing certificates of the\n+response sent by the Time Stamp Autority are stripped and stored in a separate\n+`git_tsa_store` file to avoid duplicates.\n+\n+OPTIONS\n+-------\n+-c sha1::\n+\tCreate a RFC3161 time-stamp over the given hash. The response from the\n+\tTime Stamping authority is stripped from the signing certificate and\n+\tthen written to stdout as base64.\n+\n+-v sha1::\n+\tVerify a time-stamp for the given hash. The corresponding TSR, encoded\n+\tin base64, is read from stdin and merged with its signing certificate\n+\tfrom a separate certificate store. To allow the verification of the\n+\tsigning certificate, the configuration variable `ts.capath` must contain\n+\ta path to a directory with trusted certificates. The configuration\n+\tvariable `ts.failonverify` controls the return value semantics. If set,\n+\tthe return values of both the gpg signature and the time-stamp\n+\tverification are reported. The return value of the time-stamp\n+\tverification is reported using the second lowest bit, the return value\n+\tof the gpg signature verification using the least significant bit. Per\n+\tdefault, only the return value of the gpg signature verification is\n+\treported to remain consistent with legacy code.\n+\n+NOTES\n+-----\n+The main purpose of this command is to be called from the internal\n+mechanism of `git tag` or `git verify-tag`. Some commands are implemented in\n+this separate binary to allow the usage of libcrypto without building all of git\n+against it. While it is possible to is it on its own, the interface is highly\n+tailored to the needs of the internal storage mechanisms.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Documentation/git-tsa-store.txt b/Documentation/git-tsa-store.txt\nnew file mode 100644\nindex 0000000..2ce015e\n--- /dev/null\n+++ b/Documentation/git-tsa-store.txt\n@@ -0,0 +1,68 @@\n+git-tsa-store(5)\n+================\n+\n+NAME\n+----\n+git-tsa-store - Stores certificates of RFC3161 Time Stamping Authorities\n+\n+DESCRIPTION\n+-----------\n+A `.git_tsa_store` file stores certificates of RFC3161 Time Stamping Authorities\n+from which time-stamps were obtained using `git tag` or `git timestamp-util`. To\n+avoid unnecessary pollution by large git objects, each RFC3161 time-stamp is\n+stripped from the signing certificate of the Time Stamping Authority before it\n+is stored in the tag object. These signing certificates are then managed in the\n+`.git_tsa_store` file for each repository.\n+\n+Each certificate is only stored once in a `.git_tsa_store` file. The comparison\n+criterion for equality is the pair of the serial number of the certificate and\n+the common name of the issuer. Therefore it is possible to manage multiple\n+certificates for one Time Stamping Authority if for some reason a certificate\n+should change.\n+\n+The certificates stored in a `.git_tsa_store` file are only used to correctly\n+identify the issuer of a time-stamp. They are not considered trustworthy. For\n+verification purposes it is therefore necesssary to specify trusted certificates\n+using the configuration variable `ts.capath`.\n+\n+All certificates in a `.git_tsa_store` file are stored using the pattern\n+described below.\n+\n+PATTERN FORMAT\n+--------------\n+ - The string '-----BEGIN ENTRY-----' marks the beginning of a certificate\n+   entry.\n+\n+ - The next line is of the format 'Version: <version>'. The current version\n+   number is 1.\n+\n+ - The next line is of the format 'Serial: <serial>', containing the serial\n+   number of the certificate. Together with the common name of the issuer it\n+   forms the identifying tuple.\n+\n+ - The next line is of the format 'Issuer: <common name>', containing the common\n+   name of the issuer of the time-stamp. Together with the serial number of the\n+   certificate it forms the identifying tuple.\n+\n+ - The next line contains only a new line to separate metadata from the\n+   certificate itself.\n+\n+ - The certificate itself follows in PEM format surrounded by the usual\n+   delimiters '-----BEGIN CERTIFICATE-----' and '-----END CERTIFICATE-----'.\n+\n+ - A closing '-----END ENTRY-----' marks the end of an entry.\n+\n+NOTES\n+-----\n+The main purpose of a `.git_tsa_store` file is to be managed exclusively by `git\n+timestamp-util`. Nevertheless it is possible to fill in certificates manually as\n+long as the above specified format is observed. Manually editing this file might\n+be necessary in the unlikely case of non-resolvable merge conflicts.\n+\n+SEE ALSO\n+--------\n+linkgit:git-timestamp-util[1]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt\nindex d590edc..539e545 100644\n--- a/Documentation/git-verify-tag.txt\n+++ b/Documentation/git-verify-tag.txt\n@@ -3,7 +3,7 @@ git-verify-tag(1)\n \n NAME\n ----\n-git-verify-tag - Check the GPG signature of tags\n+git-verify-tag - Check the GPG signature and RFC3161 time-stamp of tags\n \n SYNOPSIS\n --------\n@@ -12,13 +12,33 @@ SYNOPSIS\n \n DESCRIPTION\n -----------\n-Validates the gpg signature created by 'git tag'.\n+Validates the gpg signature and RFC3161 time-stamp created by 'git tag'. To\n+validate the signing certificate of the Time Stamping Authority of a RFC3161\n+time-stamp, a path to a directory of trusted certificates must be given using\n+the configuration variable `ts.capath`. The containing files must follow the\n+OpenSSL naming convention `<issuer-hash>.0`. The configuration variable\n+`ts.failonverify` specifies if the return value of the time-stamp verification\n+should be reported.  If set, the return values of both the gpg signature and the\n+time-stamp verfification are considered. Per default, only the return value of\n+the gpg signature verfification is reported to remain consistent with legacy\n+code. Using the `-s` or `-t` options overrides the behaviour set by\n+`ts.failonverify`.\n \n OPTIONS\n -------\n --raw::\n-\tPrint the raw gpg status output to standard error instead of the normal\n-\thuman-readable output.\n+\tPrint the raw gpg status output and time-stamp verification output to\n+\tstandard error instead of the normal human-readable output.\n+\n+-s::\n+--signature::\n+\tReport the return value of the gpg signature verification. If the least\n+\tsignificant bit is set, an error occurred.\n+\n+-t::\n+--timestamp::\n+\tReport the return value of the time-stamp verification. If the second\n+\tlowest bit is set, an error occurred.\n \n -v::\n --verbose::\n-- \n2.8.0.rc0.62.gfc8aefa.dirty\n"},{"id":"281070","messageId":"1458240421-3593-4-git-send-email-anton.wuerfel@fau.de","threadId":"41730","inReplyTo":"1458240421-3593-1-git-send-email-anton.wuerfel@fau.de","subject":"[PATCH 03/10] Documentation: Whitespace fix","fromName":"Anton Wuerfel","fromEmail":"anton.wuerfel@fau.de","sentAt":"2016-03-17T18:46:54Z","receivedAt":"2016-03-17T18:46:54Z","isPatch":true,"sender":{"key":"anton.wuerfel@fau.de","avatar":null},"body":"This commit contains just a tiny whitespace fix in the git-tag documentation.\n\nSigned-off-by: Anton Würfel <anton.wuerfel@fau.de>\nSigned-off-by: Phillip Raffeck <phillip.raffeck@fau.de>\n---\n Documentation/git-tag.txt | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 940676e..b49725b 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -39,7 +39,7 @@ created (i.e. a lightweight tag).\n A GnuPG signed tag object will be created when `-s` or `-u\n <keyid>` is used.  When `-u <keyid>` is not used, the\n committer identity for the current user is used to find the\n-GnuPG key for signing. \tThe configuration variable `gpg.program`\n+GnuPG key for signing. The configuration variable `gpg.program`\n is used to specify custom GnuPG binary.\n \n A tag containing a time-stamp according to RFC3161 will be created when `-t` is\n-- \n2.8.0.rc0.62.gfc8aefa.dirty\n"},{"id":"281068","messageId":"1458240421-3593-5-git-send-email-anton.wuerfel@fau.de","threadId":"41730","inReplyTo":"1458240421-3593-1-git-send-email-anton.wuerfel@fau.de","subject":"[PATCH 04/10] Extending internal CURL wrapper for POST upload","fromName":"Anton Wuerfel","fromEmail":"anton.wuerfel@fau.de","sentAt":"2016-03-17T18:46:55Z","receivedAt":"2016-03-17T18:46:55Z","isPatch":true,"sender":{"key":"anton.wuerfel@fau.de","avatar":null},"body":"This commit adds POST upload capabilities to the internal libcurl wrapper, which\nare necessary for uploading RFC3161 Time Stamping Queries to a Time Stamping\nAuthority.\n\nTherefore, the internal struct http_get_options is renamed to\nhttp_request_options and extended by 2 strbuf fields named `postfields` and\n`post_content_type`. If either one of them is non-NULL, the corresponding\ncurl_easy_setopt() functions are called.\n\nSigned-off-by: Anton Würfel <anton.wuerfel@fau.de>\nSigned-off-by: Phillip Raffeck <phillip.raffeck@fau.de>\n---\n http.c        | 30 +++++++++++++++++++++++-------\n http.h        | 17 +++++++++++++++--\n remote-curl.c |  2 +-\n 3 files changed, 39 insertions(+), 10 deletions(-)\n\ndiff --git a/http.c b/http.c\nindex 69da445..a108658 100644\n--- a/http.c\n+++ b/http.c\n@@ -1373,17 +1373,19 @@ static void http_opt_request_remainder(CURL *curl, off_t pos)\n \n static int http_request(const char *url,\n \t\t\tvoid *result, int target,\n-\t\t\tconst struct http_get_options *options)\n+\t\t\tconst struct http_request_options *options)\n {\n \tstruct active_request_slot *slot;\n \tstruct slot_results results;\n \tstruct curl_slist *headers = NULL;\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct strbuf content_type = STRBUF_INIT;\n \tconst char *accept_language;\n \tint ret;\n \n \tslot = get_active_slot();\n-\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);\n+\tif (options && !options->postfields)\n+\t\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);\n \n \tif (result == NULL) {\n \t\tcurl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);\n@@ -1407,6 +1409,19 @@ static int http_request(const char *url,\n \tif (accept_language)\n \t\theaders = curl_slist_append(headers, accept_language);\n \n+\tif (options && options->postfields && options->post_content_type) {\n+\t\tstrbuf_addstr(&content_type, \"Content-Type: \");\n+\t\tstrbuf_addbuf(&content_type, options->post_content_type);\n+\t\theaders = curl_slist_append(headers, content_type.buf);\n+\t}\n+\n+\tif (options && options->postfields) {\n+\t\tcurl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS,\n+\t\t\t\t options->postfields->buf);\n+\t\tcurl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE,\n+\t\t\t\t options->postfields->len);\n+\t}\n+\n \tstrbuf_addstr(&buf, \"Pragma:\");\n \tif (options && options->no_cache)\n \t\tstrbuf_addstr(&buf, \" no-cache\");\n@@ -1434,6 +1449,7 @@ static int http_request(const char *url,\n \t\t\t\toptions->effective_url);\n \n \tcurl_slist_free_all(headers);\n+\tstrbuf_release(&content_type);\n \tstrbuf_release(&buf);\n \n \treturn ret;\n@@ -1491,7 +1507,7 @@ static int update_url_from_redirect(struct strbuf *base,\n \n static int http_request_reauth(const char *url,\n \t\t\t       void *result, int target,\n-\t\t\t       struct http_get_options *options)\n+\t\t\t       struct http_request_options *options)\n {\n \tint ret = http_request(url, result, target, options);\n \n@@ -1529,7 +1545,7 @@ static int http_request_reauth(const char *url,\n \n int http_get_strbuf(const char *url,\n \t\t    struct strbuf *result,\n-\t\t    struct http_get_options *options)\n+\t\t    struct http_request_options *options)\n {\n \treturn http_request_reauth(url, result, HTTP_REQUEST_STRBUF, options);\n }\n@@ -1541,7 +1557,7 @@ int http_get_strbuf(const char *url,\n  * file is still around) the download is resumed.\n  */\n static int http_get_file(const char *url, const char *filename,\n-\t\t\t struct http_get_options *options)\n+\t\t\t struct http_request_options *options)\n {\n \tint ret;\n \tstruct strbuf tmpfile = STRBUF_INIT;\n@@ -1567,7 +1583,7 @@ cleanup:\n \n int http_fetch_ref(const char *base, struct ref *ref)\n {\n-\tstruct http_get_options options = {0};\n+\tstruct http_request_options options = {0};\n \tchar *url;\n \tstruct strbuf buffer = STRBUF_INIT;\n \tint ret = -1;\n@@ -1659,7 +1675,7 @@ add_pack:\n \n int http_get_info_packs(const char *base_url, struct packed_git **packs_head)\n {\n-\tstruct http_get_options options = {0};\n+\tstruct http_request_options options = {0};\n \tint ret = 0, i = 0;\n \tchar *url, *data;\n \tstruct strbuf buf = STRBUF_INIT;\ndiff --git a/http.h b/http.h\nindex 4ef4bbd..0b7cebf 100644\n--- a/http.h\n+++ b/http.h\n@@ -136,7 +136,7 @@ extern char *get_remote_object_url(const char *url, const char *hex,\n \t\t\t\t   int only_two_digit_prefix);\n \n /* Options for http_get_*() */\n-struct http_get_options {\n+struct http_request_options {\n \tunsigned no_cache:1,\n \t\t keep_error:1;\n \n@@ -163,6 +163,18 @@ struct http_get_options {\n \t * for details.\n \t */\n \tstruct strbuf *base_url;\n+\n+\t/*\n+\t * If non-NULL, a HTTP POST request is sent with the raw post data\n+\t * contained in postfields.\n+\t */\n+\tstruct strbuf *postfields;\n+\n+\t/*\n+\t * If non-NULL, a content-type header is applied to the HTTP POST\n+\t * request.\n+\t */\n+\tstruct strbuf *post_content_type;\n };\n \n /* Return values for http_get_*() */\n@@ -178,7 +190,8 @@ struct http_get_options {\n  *\n  * If the result pointer is NULL, a HTTP HEAD request is made instead of GET.\n  */\n-int http_get_strbuf(const char *url, struct strbuf *result, struct http_get_options *options);\n+int http_get_strbuf(const char *url, struct strbuf *result,\n+\t\t    struct http_request_options *options);\n \n extern int http_fetch_ref(const char *base, struct ref *ref);\n \ndiff --git a/remote-curl.c b/remote-curl.c\nindex 15e48e2..345a5e6 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -254,7 +254,7 @@ static struct discovery *discover_refs(const char *service, int for_push)\n \tstruct strbuf effective_url = STRBUF_INIT;\n \tstruct discovery *last = last_discovery;\n \tint http_ret, maybe_smart = 0;\n-\tstruct http_get_options options;\n+\tstruct http_request_options options;\n \n \tif (last && !strcmp(service, last->service))\n \t\treturn last;\n-- \n2.8.0.rc0.62.gfc8aefa.dirty\n"},{"id":"281074","messageId":"1458240421-3593-6-git-send-email-anton.wuerfel@fau.de","threadId":"41730","inReplyTo":"1458240421-3593-1-git-send-email-anton.wuerfel@fau.de","subject":"[PATCH 05/10] Make PGP macros global","fromName":"Anton Wuerfel","fromEmail":"anton.wuerfel@fau.de","sentAt":"2016-03-17T18:46:56Z","receivedAt":"2016-03-17T18:46:56Z","isPatch":true,"sender":{"key":"anton.wuerfel@fau.de","avatar":null},"body":"This commit moves the macros PGP_SIGNATURE and PGP_MESSAGE to gpg-interface.h to\nmake them publicly reachable, which is necessary for RFC3161 time-stamping.\n\nWhen verifying an RFC3161 time-stamp signature, a SHA-1 hash has to be generated\nover the git object excluding a possible PGP signature, which makes it necessary\nto detect the beginning of this PGP signature.\n\nSigned-off-by: Anton Würfel <anton.wuerfel@fau.de>\nSigned-off-by: Phillip Raffeck <phillip.raffeck@fau.de>\n---\n gpg-interface.c | 3 ---\n gpg-interface.h | 3 +++\n 2 files changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 3dc2fe3..4b42860 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -7,9 +7,6 @@\n static char *configured_signing_key;\n static const char *gpg_program = \"gpg\";\n \n-#define PGP_SIGNATURE \"-----BEGIN PGP SIGNATURE-----\"\n-#define PGP_MESSAGE \"-----BEGIN PGP MESSAGE-----\"\n-\n void signature_check_clear(struct signature_check *sigc)\n {\n \tfree(sigc->payload);\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex ea68885..04b0eab 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -4,6 +4,9 @@\n #define GPG_VERIFY_VERBOSE\t1\n #define GPG_VERIFY_RAW\t\t2\n \n+#define PGP_SIGNATURE \"-----BEGIN PGP SIGNATURE-----\"\n+#define PGP_MESSAGE \"-----BEGIN PGP MESSAGE-----\"\n+\n struct signature_check {\n \tchar *payload;\n \tchar *gpg_output;\n-- \n2.8.0.rc0.62.gfc8aefa.dirty\n"},{"id":"281071","messageId":"1458240421-3593-7-git-send-email-anton.wuerfel@fau.de","threadId":"41730","inReplyTo":"1458240421-3593-1-git-send-email-anton.wuerfel@fau.de","subject":"[PATCH 06/10] Add basic RFC3161 functionality","fromName":"Anton Wuerfel","fromEmail":"anton.wuerfel@fau.de","sentAt":"2016-03-17T18:46:57Z","receivedAt":"2016-03-17T18:46:57Z","isPatch":true,"sender":{"key":"anton.wuerfel@fau.de","avatar":null},"body":"This commit adds basic RFC3161 functionality, which is linked to the main git\nbinary. It is used for interaction with git objects by parsing data from and\nwriting data to it. Moreover, it is responsible for passing data to the external\nhelper tool `git-timestamp-util`, which does the actual work of creating and\nverifying trusted time-stamps.\n\nSigned-off-by: Anton Würfel <anton.wuerfel@fau.de>\nSigned-off-by: Phillip Raffeck <phillip.raffeck@fau.de>\n---\n Makefile  |   1 +\n rfc3161.c | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n rfc3161.h |  12 ++++\n 3 files changed, 232 insertions(+)\n create mode 100644 rfc3161.c\n create mode 100644 rfc3161.h\n\ndiff --git a/Makefile b/Makefile\nindex 24bef8d..432c3de 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -792,6 +792,7 @@ LIB_OBJS += replace_object.o\n LIB_OBJS += rerere.o\n LIB_OBJS += resolve-undo.o\n LIB_OBJS += revision.o\n+LIB_OBJS += rfc3161.o\n LIB_OBJS += run-command.o\n LIB_OBJS += send-pack.o\n LIB_OBJS += sequencer.o\ndiff --git a/rfc3161.c b/rfc3161.c\nnew file mode 100644\nindex 0000000..21a386f\n--- /dev/null\n+++ b/rfc3161.c\n@@ -0,0 +1,219 @@\n+#include \"cache.h\"\n+#include \"commit.h\"\n+#include \"run-command.h\"\n+#include \"strbuf.h\"\n+#include \"gpg-interface.h\"\n+#include \"rfc3161.h\"\n+\n+static const char *timeutil_cmd = \"timestamp-util\";\n+static const char *ts_signature_begin = \"-----BEGIN RFC3161-----\";\n+static const char *ts_signature_end = \"-----END RFC3161-----\";\n+\n+static void sha1_from_strbuf(struct strbuf *buf, unsigned char sha1[20]);\n+static void sha1_in_hex(struct strbuf *buf, char sha1_hex[40]);\n+\n+static int verify_tsr(char *sha1, struct strbuf *base64);\n+\n+/*\n+ * To create a time-stamp signature, get the SHA1 hash of buffer and pass it to\n+ * git-timestamp-util. This helper program returns a minimalized TSR which is\n+ * appended with some metadata and stored to the git object.\n+ */\n+int create_time_signature(struct strbuf *buffer, struct strbuf *sig)\n+{\n+\tssize_t len;\n+\tchar sha1_hex[40];\n+\tstruct strbuf tsr = STRBUF_INIT;\n+\tstruct child_process timeutil = CHILD_PROCESS_INIT;\n+\tconst char *args[] = {\n+\t\ttimeutil_cmd,\n+\t\t\"-c\",\n+\t\tsha1_hex,\n+\t\tNULL\n+\t};\n+\n+\ttimeutil.argv = args;\n+\ttimeutil.in = 0;\n+\ttimeutil.out = -1;\n+\ttimeutil.git_cmd = 1;\n+\n+\tsha1_in_hex(buffer, sha1_hex);\n+\n+\tif (start_command(&timeutil))\n+\t\treturn error(_(\"could not run git-%s\"), timeutil_cmd);\n+\n+\tlen = strbuf_read(&tsr, timeutil.out, 1024);\n+\tclose(timeutil.out);\n+\n+\tif (finish_command(&timeutil) || len <= 0)\n+\t\treturn 1;\n+\n+\tstrbuf_addf(sig, \"%s\\n\", ts_signature_begin);\n+\tstrbuf_addf(sig, \"Version: 1\\n\\n\");\n+\tstrbuf_addbuf(sig, &tsr);\n+\tstrbuf_addf(sig, \"%s\\n\", ts_signature_end);\n+\n+\tstrbuf_release(&tsr);\n+\treturn 0;\n+}\n+\n+/*\n+ * To verify a time-stamp signature, extract the time-stamp from a git object,\n+ * extract the object id and pass the prepared data to git-timestamp-util, which\n+ * will do the verification for us.\n+ */\n+int verify_time_signature(const char *buf, unsigned long size)\n+{\n+\tchar sha1_hex[40];\n+\tstruct strbuf timesig_base64 = STRBUF_INIT;\n+\tstruct strbuf payload = STRBUF_INIT;\n+\tint ret = 1;\n+\n+\tprintf(\"\\n\");\n+\n+\tif (!parse_timestamp(buf, size, &timesig_base64, &payload)) {\n+\t\tprintf(\"time-stamp: no signature found\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\t/*\n+\t * Remove possible inline GPG signature from git object. Note that git\n+\t * tags do not use inline signatures for historical reasons, so checking\n+\t * the return value of remove_signature would be useless.\n+\t * Any tag-like signatures outside of the object header are already\n+\t * removed by parse_timestamp.\n+\t */\n+\tremove_signature(&payload);\n+\tsha1_in_hex(&payload, sha1_hex);\n+\n+\tif (verify_tsr(sha1_hex, &timesig_base64))\n+\t\tgoto err;\n+\n+\tret = 0;\n+err:\n+\tstrbuf_release(&timesig_base64);\n+\tstrbuf_release(&payload);\n+\treturn ret;\n+}\n+\n+int parse_timestamp(const char *buffer, size_t size, struct strbuf *timestamp,\n+\t\t    struct strbuf *payload)\n+{\n+\tint saw_timestamp = -1;\n+\tconst char *line, *tail, *next;\n+\n+\tline = buffer;\n+\ttail = buffer + size;\n+\tsaw_timestamp = 0;\n+\n+\t/* Search for beginning of time-stamp. */\n+\twhile (line < tail) {\n+\t\tnext = memchr(line, '\\n', tail - line);\n+\n+\t\tnext = next ? next + 1 : tail;\n+\t\tif (starts_with(line, time_sig_header) &&\n+\t\t    line[time_sig_header_len] == ' ')\n+\t\t\tbreak;\n+\n+\t\tstrbuf_add(payload, line, next - line);\n+\t\tline = next;\n+\t}\n+\n+\tif (line >= tail)\n+\t\treturn saw_timestamp;\n+\n+\t/*\n+\t * Found beginning of time-stamp.  Do not add -----BEGIN RFC3161-----,\n+\t * version information and the separating newline to the buffer.\n+\t */\n+\tline = memchr(line, '\\n', tail - line) + 1;\n+\tline = memchr(line, '\\n', tail - line) + 1;\n+\tline = memchr(line, '\\n', tail - line) + 1;\n+\tsaw_timestamp = 1;\n+\n+\t/* Read in time-stamp data. */\n+\twhile (line < tail) {\n+\t\tnext = memchr(line, '\\n', tail - line);\n+\n+\t\tnext = next ? next + 1 : tail;\n+\t\tif (line[0] == ' ')\n+\t\t\tline = line + 1;\n+\t\telse\n+\t\t\tbreak;\n+\n+\t\t/* do not add -----END RFC3161----- to buffer */\n+\t\tif (starts_with(line, ts_signature_end)) {\n+\t\t\tline = next;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tstrbuf_add(timestamp, line, next - line);\n+\t\tline = next;\n+\t}\n+\n+\twhile (line < tail) {\n+\t\tnext = memchr(line, '\\n', tail - line);\n+\t\tnext = next ? next + 1 : tail;\n+\n+\t\tif (starts_with(line, PGP_SIGNATURE))\n+\t\t\tbreak;\n+\n+\t\tstrbuf_add(payload, line, next - line);\n+\t\tline = next;\n+\t}\n+\n+\treturn saw_timestamp;\n+}\n+\n+/* Pass the prepared data to git-timestamp-util */\n+static int verify_tsr(char *sha1, struct strbuf *base64)\n+{\n+\tstruct child_process timeutil = CHILD_PROCESS_INIT;\n+\tconst char *args[] = {\n+\t\ttimeutil_cmd,\n+\t\t\"-v\",\n+\t\tsha1,\n+\t\tNULL\n+\t};\n+\n+\ttimeutil.argv = args;\n+\ttimeutil.in = -1;\n+\ttimeutil.out = 0;\n+\ttimeutil.git_cmd = 1;\n+\n+\tif (start_command(&timeutil))\n+\t\treturn error(_(\"could not run git-%s\"), timeutil_cmd);\n+\n+\tif (write_in_full(timeutil.in, base64->buf, base64->len)\n+\t\t\t  != base64->len) {\n+\t\tclose(timeutil.in);\n+\t\tfinish_command(&timeutil);\n+\t\treturn 1;\n+\t}\n+\tclose(timeutil.in);\n+\n+\tif (finish_command(&timeutil))\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+static void sha1_from_strbuf(struct strbuf *buf, unsigned char sha1[20])\n+{\n+\tgit_SHA_CTX c;\n+\n+\tgit_SHA1_Init(&c);\n+\tgit_SHA1_Update(&c, buf->buf, buf->len);\n+\tgit_SHA1_Final(sha1, &c);\n+}\n+\n+static void sha1_in_hex(struct strbuf *buf, char sha1_hex[40])\n+{\n+\tchar *tmp;\n+\tunsigned char sha1[20];\n+\n+\tsha1_from_strbuf(buf, sha1);\n+\ttmp = sha1_to_hex(sha1);\n+\tstrcpy(sha1_hex, tmp);\n+}\n+\ndiff --git a/rfc3161.h b/rfc3161.h\nnew file mode 100644\nindex 0000000..eac91ae\n--- /dev/null\n+++ b/rfc3161.h\n@@ -0,0 +1,12 @@\n+#ifndef RFC3161_H\n+#define RFC3161_H\n+\n+#define time_sig_header \"timesig\"\n+#define time_sig_header_len (sizeof(time_sig_header) - 1)\n+\n+int create_time_signature(struct strbuf *buffer, struct strbuf *sig);\n+int verify_time_signature(const char *buf, unsigned long size);\n+int parse_timestamp(const char *buffer, size_t size, struct strbuf *timestamp,\n+\t\t    struct strbuf *payload);\n+\n+#endif\n-- \n2.8.0.rc0.62.gfc8aefa.dirty\n"},{"id":"281075","messageId":"1458240421-3593-8-git-send-email-anton.wuerfel@fau.de","threadId":"41730","inReplyTo":"1458240421-3593-1-git-send-email-anton.wuerfel@fau.de","subject":"[PATCH 07/10] Add git-http-timestamp helper tool","fromName":"Anton Wuerfel","fromEmail":"anton.wuerfel@fau.de","sentAt":"2016-03-17T18:46:58Z","receivedAt":"2016-03-17T18:46:58Z","isPatch":true,"sender":{"key":"anton.wuerfel@fau.de","avatar":null},"body":"This commit adds the git-http-timestamp helper tool. It does all the\ncommunication to a Time Stamping Authority via libcurl and passes the received\ndata to the caller via stdout.\n\nLibcurl by default depends on GnuTLS, the new RFC3161 time-stamping\nfunctionality depends on libcrypto (OpenSSL) as GnuTLS does not support RFC3161\ntime-stamps. The git-http-timestamp helper tool is introduced to avoid linking\nOpenSSL and GnuTLS together in a single binary.\n\nSigned-off-by: Anton Würfel <anton.wuerfel@fau.de>\nSigned-off-by: Phillip Raffeck <phillip.raffeck@fau.de>\n---\n .gitignore       |  1 +\n Makefile         |  7 ++++++\n command-list.txt |  1 +\n http-timestamp.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 85 insertions(+)\n create mode 100644 http-timestamp.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 5087ce1..a3b270d 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -70,6 +70,7 @@\n /git-http-backend\n /git-http-fetch\n /git-http-push\n+/git-http-timestamp\n /git-imap-send\n /git-index-pack\n /git-init\ndiff --git a/Makefile b/Makefile\nindex 432c3de..c717af7 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1116,6 +1116,9 @@ else\n \t\t\tBASIC_CFLAGS += -DEXPAT_NEEDS_XMLPARSE_H\n \t\tendif\n \tendif\n+\n+\tPROGRAM_OBJS += http-timestamp.o\n+\tPROGRAMS += git-http-timestamp$X\n endif\n IMAP_SEND_LDFLAGS += $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO)\n \n@@ -2018,6 +2021,10 @@ git-remote-testsvn$X: remote-testsvn.o GIT-LDFLAGS $(GITLIBS) $(VCSSVN_LIB)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) \\\n \t$(VCSSVN_LIB)\n \n+git-http-timestamp$X: http.o http-timestamp.o GIT-LDFLAGS $(GITLIBS)\n+\t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n+\t\t$(CURL_LIBCURL) $(LIBS)\n+\n $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)\n \t$(QUIET_LNCP)$(RM) $@ && \\\n \tln $< $@ 2>/dev/null || \\\ndiff --git a/command-list.txt b/command-list.txt\nindex 2a94137..3e279c1 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -68,6 +68,7 @@ git-help                                ancillaryinterrogators\n git-http-backend                        synchingrepositories\n git-http-fetch                          synchelpers\n git-http-push                           synchelpers\n+git-http-timestamp                      purehelpers\n git-imap-send                           foreignscminterface\n git-index-pack                          plumbingmanipulators\n git-init                                mainporcelain           init\ndiff --git a/http-timestamp.c b/http-timestamp.c\nnew file mode 100644\nindex 0000000..86b42e0\n--- /dev/null\n+++ b/http-timestamp.c\n@@ -0,0 +1,76 @@\n+#include \"cache.h\"\n+#include \"strbuf.h\"\n+#include \"http.h\"\n+\n+static int obtain_tsr(struct strbuf *tsq, struct strbuf *tsr);\n+\n+static void usage_and_die(const char *name);\n+\n+static const char *config_tsa_url_key = \"ts.tsaurl\";\n+\n+int main(int argc, const char *argv[])\n+{\n+\tstruct strbuf tsr = STRBUF_INIT;\n+\tstruct strbuf tsq = STRBUF_INIT;\n+\tint ret;\n+\n+\tif (argc != 1)\n+\t\tusage_and_die(argv[0]);\n+\n+\tif (strbuf_fread(&tsq, 1024, stdin) < 0) {\n+\t\tstrbuf_release(&tsq);\n+\t\treturn error(_(\"strbuf_fread failed: %s\"), strerror(errno));\n+\t}\n+\n+\tret = obtain_tsr(&tsq, &tsr);\n+\n+\tif (!ret)\n+\t\twrite_in_full(fileno(stdout), tsr.buf, tsr.len);\n+\n+\tstrbuf_release(&tsr);\n+\tstrbuf_release(&tsq);\n+\treturn ret;\n+}\n+\n+static int obtain_tsr(struct strbuf *tsq, struct strbuf *tsr)\n+{\n+\tstruct strbuf content_type = STRBUF_INIT;\n+\tstruct http_request_options options = {0};\n+\n+\tchar *config_tsa_url;\n+\n+\tif (git_config_get_string(config_tsa_url_key, &config_tsa_url)) {\n+\t\tdie(_(\"git config option '%s' must be set\"),\n+\t\t      config_tsa_url_key);\n+\t}\n+\n+\t/* libcurl stuff */\n+\n+\thttp_init(NULL, config_tsa_url, 0);\n+\n+\tstrbuf_addstr(&content_type, \"application/timestamp-query\");\n+\toptions.post_content_type = &content_type;\n+\toptions.postfields = tsq;\n+\n+\tif (http_get_strbuf(config_tsa_url, tsr, &options)) {\n+\t\tstrbuf_release(&content_type);\n+\t\treturn error(_(\"sending time-stamp query failed\"));\n+\t}\n+\n+\tstrbuf_release(&content_type);\n+\tfree(config_tsa_url);\n+\thttp_cleanup();\n+\n+\treturn 0;\n+}\n+\n+static void usage_and_die(const char *name)\n+{\n+\tfprintf(stderr, \"Usage: %s\\n\\n\", name);\n+\tfputs(\"Obtain a trusted time-stamp from the Time Stamping Authority\\n\"\n+\t      \"specified in configuration variable `ts.tsaurl` and write the\\n\"\n+\t      \"result to stdout.\\n\",\n+\t      stderr);\n+\n+\texit(EXIT_FAILURE);\n+}\n-- \n2.8.0.rc0.62.gfc8aefa.dirty\n"},{"id":"281073","messageId":"1458240421-3593-9-git-send-email-anton.wuerfel@fau.de","threadId":"41730","inReplyTo":"1458240421-3593-1-git-send-email-anton.wuerfel@fau.de","subject":"[PATCH 08/10] Adding time-stamping helper tool","fromName":"Anton Wuerfel","fromEmail":"anton.wuerfel@fau.de","sentAt":"2016-03-17T18:46:59Z","receivedAt":"2016-03-17T18:46:59Z","isPatch":true,"sender":{"key":"anton.wuerfel@fau.de","avatar":null},"body":"This commit adds a helper tool called `git-timestamp-util`, which does the\nactual RFC3161 time-stamping work. It depends on libssl and libcrypto.\n\nIn particular, it is used for creating time-stamp signatures and for verifying\nthem.\n\nTo create a time-stamp signature, a Time Stamping Query (TSQ) is created and\npassed to the helper tool `git-http-timestamp`, which passes it to a Time\nStamping Authority and outputs a trusted Time Stamping Response (TSR). The TSR\nis then split into the time-stamp signature itself and the Time Stamping\nAutority's certificate. This certificate is stored in a repository-global TSA\nstore file called .git_tsa_store, whereas the raw time-stamp signature is passed\nto the caller to be stored in a git object. Splitting the TSR into the TSA's\ncertificate and the raw time-stamp signature is done to avoid redundancy as the\nTSA's certificate will likely not change over years.\n\nTo verify a time-stamp signature, a SHA-1 hash of the git object to be checked\nis passed along with its corresponding time-stamp signature. Identifying\ncertificate information like issuer and serial number is extracted from the\ntime-stamp signature. The tuple of issuer and serial number is then used to find\nthe actual certificate of the Time Stamping Autority in .git_tsa_store file.\nThe TSA's Certificate and the raw time-stamp signature are merged together and\nverified.\n\nSigned-off-by: Anton Würfel <anton.wuerfel@fau.de>\nSigned-off-by: Phillip Raffeck <phillip.raffeck@fau.de>\n---\n .gitignore       |   1 +\n Makefile         |   7 +\n command-list.txt |   1 +\n timestamp-util.c | 615 +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 624 insertions(+)\n create mode 100644 timestamp-util.c\n\ndiff --git a/.gitignore b/.gitignore\nindex a3b270d..08005ca 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -160,6 +160,7 @@\n /git-svn\n /git-symbolic-ref\n /git-tag\n+/git-timestamp-util\n /git-unpack-file\n /git-unpack-objects\n /git-update-index\ndiff --git a/Makefile b/Makefile\nindex c717af7..a0ab96a 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1142,6 +1142,9 @@ ifndef NO_OPENSSL\n \tifdef NO_HMAC_CTX_CLEANUP\n \t\tBASIC_CFLAGS += -DNO_HMAC_CTX_CLEANUP\n \tendif\n+\n+\tPROGRAM_OBJS += timestamp-util.o\n+\tPROGRAMS += git-timestamp-util$X\n else\n \tBASIC_CFLAGS += -DNO_OPENSSL\n \tBLK_SHA1 = 1\n@@ -2025,6 +2028,10 @@ git-http-timestamp$X: http.o http-timestamp.o GIT-LDFLAGS $(GITLIBS)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(CURL_LIBCURL) $(LIBS)\n \n+git-timestamp-util$X: timestamp-util.o GIT-LDFLAGS $(GITLIBS)\n+\t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n+\t\t$(LIBS)\n+\n $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)\n \t$(QUIET_LNCP)$(RM) $@ && \\\n \tln $< $@ 2>/dev/null || \\\ndiff --git a/command-list.txt b/command-list.txt\nindex 3e279c1..07a4cab 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -136,6 +136,7 @@ git-submodule                           mainporcelain\n git-svn                                 foreignscminterface\n git-symbolic-ref                        plumbingmanipulators\n git-tag                                 mainporcelain           history\n+git-timestamp-util                      purehelpers\n git-unpack-file                         plumbinginterrogators\n git-unpack-objects                      plumbingmanipulators\n git-update-index                        plumbingmanipulators\ndiff --git a/timestamp-util.c b/timestamp-util.c\nnew file mode 100644\nindex 0000000..fee4fc2\n--- /dev/null\n+++ b/timestamp-util.c\n@@ -0,0 +1,615 @@\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <unistd.h>\n+#include <openssl/bio.h>\n+#include <openssl/err.h>\n+#include <openssl/pem.h>\n+#include <openssl/rand.h>\n+#include <openssl/ts.h>\n+#include <openssl/bn.h>\n+\n+#include \"cache.h\"\n+#include \"run-command.h\"\n+#include \"strbuf.h\"\n+\n+/*\n+ * This code is based on the RFC3161 implementation in OpenSSL\n+ * by Zoltan Glozik.\n+ */\n+\n+#define NONCE_LENGTH (64)\n+#define ISSUER_LEN (1024)\n+\n+struct tsr_info {\n+\tchar issuer[ISSUER_LEN];\n+\tunsigned long serial;\n+\tX509 *cert;\n+};\n+\n+\n+static const char *timesig_cmd = \"http-timestamp\";\n+\n+static const char *config_ca_key = \"ts.capath\";\n+static const char *certstore_file = \".git_tsa_store\";\n+static const char *certstore_header_begin = \"-----BEGIN ENTRY-----\";\n+static const char *certstore_header_end = \"-----END ENTRY-----\";\n+\n+static TS_REQ *create_query(const char *digest);\n+static ASN1_INTEGER *create_nonce(int bits);\n+static TS_MSG_IMPRINT *create_msg_imprint(const char *digest);\n+\n+static int output_as_base64(TS_RESP *response);\n+static TS_RESP *strip_tsr(TS_RESP *response);\n+\n+static int do_verify(TS_RESP *response, const char *digest, const char *CApath,\n+\t\t     X509 *untrusted);\n+static X509_STORE *create_cert_store(const char *CApath);\n+static TS_VERIFY_CTX *create_verify_ctx(const char *digest, const char *CApath,\n+\t\t\t\t\tX509 *cert);\n+\n+static int append_certificate_to_store(struct tsr_info *info);\n+static X509 *find_certificate_in_store(struct tsr_info *info);\n+\n+static int extract_info_from_response(struct tsr_info *info, TS_RESP *response);\n+\n+static void usage_and_die(const char *name);\n+\n+static int create_tsr(const char *sha1);\n+static int verify_tsr(const char *sha1);\n+\n+/*\n+ * Request a TSR from a Time Stamping Authority. The TSR includes the\n+ * authority's public key. To avoid saving this public key for every\n+ * time-stamped git object, the public key is stripped from the TSR and saved\n+ * separately. By default, it is stored in .git_tsa_store file.\n+ */\n+static int create_tsr(const char *sha1)\n+{\n+\tTS_RESP *response = NULL;\n+\tTS_REQ *query = NULL;\n+\tX509 *cert = NULL;\n+\tBIO *out_bio = NULL;\n+\tBIO *in_bio = NULL;\n+\tstruct child_process timesig = CHILD_PROCESS_INIT;\n+\tstruct tsr_info info;\n+\tint retval = 1;\n+\tconst char *args[] = {\n+\t\ttimesig_cmd,\n+\t\tNULL\n+\t};\n+\n+\t/*\n+\t * Invoke git-http-timestamp to receive a TSR from\n+\t * the Time Stamping Authority.\n+\t */\n+\ttimesig.argv = args;\n+\ttimesig.in = -1;\n+\ttimesig.out = -1;\n+\ttimesig.git_cmd = 1;\n+\n+\tquery = create_query(sha1);\n+\tif (!query) {\n+\t\tERR_print_errors_fp(stderr);\n+\t\tgoto end;\n+\t}\n+\n+\tif (start_command(&timesig))\n+\t\treturn error(_(\"could not run git-%s\"), timesig_cmd);\n+\n+\tout_bio = BIO_new_fd(timesig.in, BIO_NOCLOSE | BIO_FP_TEXT);\n+\tif (!out_bio)\n+\t\tgoto end;\n+\n+\tif (!i2d_TS_REQ_bio(out_bio, query))\n+\t\tgoto end;\n+\n+\tclose(timesig.in);\n+\n+\tin_bio = BIO_new_fd(timesig.out, BIO_NOCLOSE);\n+\tif (!in_bio)\n+\t\tgoto end;\n+\n+\tresponse = d2i_TS_RESP_bio(in_bio, NULL);\n+\tif (!response)\n+\t\tgoto end;\n+\n+\tclose(timesig.out);\n+\n+\tif (finish_command(&timesig))\n+\t\tgoto end;\n+\n+\textract_info_from_response(&info, response);\n+\n+\t/* Add certificate to TSA store if it does not exist yet */\n+\tcert = find_certificate_in_store(&info);\n+\tif (!cert) {\n+\t\tretval = append_certificate_to_store(&info);\n+\t\tif (retval)\n+\t\t\tgoto end;\n+\t}\n+\n+\t/* Strip certificate from TSR */\n+\tstrip_tsr(response);\n+\tif (!response)\n+\t\tgoto end;\n+\n+\t/* Send stripped TSR to stdout */\n+\tif (output_as_base64(response))\n+\t\tgoto end;\n+\n+\tretval = 0;\n+\n+end:\n+\tBIO_free_all(out_bio);\n+\tBIO_free_all(in_bio);\n+\tTS_RESP_free(response);\n+\tTS_REQ_free(query);\n+\tX509_free(cert);\n+\treturn retval;\n+}\n+\n+/*\n+ * Verify a TSR passed via stdin. The base64 encoded TSR is combined with the\n+ * corresponding public key of the Time Stamping Authority (TSA) and then passed\n+ * to the `openssl ts -verify` command.\n+ */\n+static int verify_tsr(const char *sha1)\n+{\n+\tBIO *b64 = NULL;\n+\tBIO *in = NULL;\n+\tBIO *out = NULL;\n+\tTS_RESP *response = NULL;\n+\tTS_TST_INFO *tst_info = NULL;\n+\tX509 *cert = NULL;\n+\tint retval = 1;\n+\tchar *config_ca_path;\n+\tstruct tsr_info info;\n+\n+\t/* get config options */\n+\tif (git_config_get_pathname(config_ca_key,\n+\t\t\t\t    (const char **)&config_ca_path))\n+\t\tdie(_(\"git config option '%s' must be set\"), config_ca_key);\n+\n+\t/* prepare BIO-stdout */\n+\tout = BIO_new_fp(stdout, BIO_NOCLOSE);\n+\tif (!out)\n+\t\tgoto end;\n+\n+\t/* read in base64 encoded stripped tsr from stdin */\n+\tb64 = BIO_new(BIO_f_base64());\n+\tin = BIO_new_fp(stdin, BIO_NOCLOSE);\n+\tif (!in)\n+\t\tgoto end;\n+\n+\tin = BIO_push(b64, in);\n+\n+\tresponse = d2i_TS_RESP_bio(in, NULL);\n+\tif (!response)\n+\t\tgoto end;\n+\n+\textract_info_from_response(&info, response);\n+\tcert = find_certificate_in_store(&info);\n+\tif (!cert) {\n+\t\terror(_(\"certificate not found in %s\"), certstore_file);\n+\t\tgoto end;\n+\t}\n+\n+\tif (do_verify(response, sha1, config_ca_path, cert)) {\n+\t\terror(\"BAD time-stamp signature\");\n+\t\tERR_print_errors(out);\n+\t\tgoto end;\n+\t}\n+\n+\t/* prepare data for output */\n+\ttst_info = TS_RESP_get_tst_info(response);\n+\n+\tBIO_puts(out, \"Verified time-stamp: \");\n+\tASN1_GENERALIZEDTIME_print(out, tst_info->time);\n+\tBIO_printf(out, \"\\nTime Stamping Authority: %s\\n\",\n+\t\t   info.issuer);\n+\n+\tretval = 0;\n+\n+end:\n+\tTS_RESP_free(response);\n+\tBIO_free_all(in);\n+\tBIO_free_all(out);\n+\tfree(config_ca_path);\n+\n+\treturn retval;\n+}\n+\n+static int do_verify(TS_RESP *response, const char *digest, const char *CApath,\n+\t\t     X509 *cert)\n+{\n+\tTS_VERIFY_CTX *verify_ctx = NULL;\n+\tint ret = 0;\n+\n+\tverify_ctx = create_verify_ctx(digest, CApath, cert);\n+\tif (!verify_ctx)\n+\t\tgoto end;\n+\n+\tret = TS_RESP_verify_response(verify_ctx, response);\n+\n+end:\n+\t/*\n+\t * TS_VERIFY_CTX_free also cleans up the created X509_STORE, so no\n+\t * further action is needed.\n+\t */\n+\tTS_VERIFY_CTX_free(verify_ctx);\n+\n+\t/*\n+\t * Invert ret to follow git return semantics. 0 indicates success,\n+\t * anything else indicates errors.\n+\t */\n+\treturn !ret;\n+}\n+\n+static int append_certificate_to_store(struct tsr_info *info)\n+{\n+\tFILE *store = fopen(certstore_file, \"a\");\n+\n+\tif (!store) {\n+\t\treturn error(_(\"Failed to open the certificate store at \"\n+\t\t\t       \"%s: %s\"), certstore_file, strerror(errno));\n+\t}\n+\n+\tfprintf(store, \"%s\\nVersion: 1\\nSerial: %lu\\nIssuer: %s\\n\\n\",\n+\t\tcertstore_header_begin,\n+\t\tinfo->serial,\n+\t\tinfo->issuer\n+\t       );\n+\n+\tif (!PEM_write_X509(store, info->cert)) {\n+\t\tfclose(store);\n+\t\treturn 1;\n+\t}\n+\n+\tfprintf(store, \"%s\\n\", certstore_header_end);\n+\tfclose(store);\n+\n+\treturn 0;\n+}\n+\n+static X509 *find_certificate_in_store(struct tsr_info *info)\n+{\n+\tchar buf[1024];\n+\tchar issuer[ISSUER_LEN];\n+\tX509 *cert = NULL;\n+\tunsigned long serial;\n+\tFILE *store;\n+\n+\tstore = fopen(certstore_file, \"r\");\n+\tif (!store) {\n+\t\tif (errno == ENOENT)\n+\t\t\treturn NULL;\n+\t\tdie(_(\"Failed to open the certificate store at \"\n+\t\t\t       \"%s: %s\"), certstore_file, strerror(errno));\n+\t}\n+\n+\twhile (fgets(buf, 1024, store)) {\n+\t\tif (!starts_with(buf, certstore_header_begin))\n+\t\t\tcontinue;\n+\n+\t\tserial = 0;\n+\t\t*issuer = 0;\n+\t\t/* We are reading in an entry. Read in meta-data.*/\n+\t\twhile (fgets(buf, 1024, store)) {\n+\t\t\tif (starts_with(buf, \"Serial:\"))\n+\t\t\t\tsscanf(buf, \"Serial: %lu\\n\", &serial);\n+\n+\t\t\tif (starts_with(buf, \"Issuer:\"))\n+\t\t\t\tsscanf(buf, \"Issuer: %[^\\n]\\n\", issuer);\n+\n+\t\t\t/* A empty line separates meta-data from certificate */\n+\t\t\tif (!strcmp(buf, \"\\n\"))\n+\t\t\t       break;\n+\t\t\tif (starts_with(buf, certstore_header_end)) {\n+\t\t\t\tserial = 0;\n+\t\t\t\t*issuer = 0;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* Check if certificate matches */\n+\t\tif (serial != info->serial ||\n+\t\t    strcmp(issuer, info->issuer))\n+\t\t\tcontinue;\n+\n+\t\t/* Matching certificate found */\n+\t\tcert = PEM_read_X509(store, NULL, NULL, NULL);\n+\n+\t\tif (cert)\n+\t\t\tbreak;\n+\n+\t\t/* Certificate could not be read. Output a warning. */\n+\t\tfprintf(stderr, \"Warning: Failed to read certificate with serial: \"\n+\t\t\t\t\"%lu and issuer: %s. \"\n+\t\t\t\t\"It may be corrupted.\\n\", serial, issuer);\n+\t}\n+\n+\tfclose(store);\n+\treturn cert;\n+}\n+\n+/* Extract issuer, serial number and TSA's public key from a TS_RESP struct */\n+static int extract_info_from_response(struct tsr_info *info, TS_RESP *response)\n+{\n+\tPKCS7 *token;\n+\tPKCS7_SIGNER_INFO *pksi = NULL;\n+\tPKCS7_ISSUER_AND_SERIAL *pkis = NULL;\n+\n+\ttoken = response->token;\n+\tpksi = sk_PKCS7_SIGNER_INFO_value(token->d.sign->signer_info, 0);\n+\tpkis = pksi->issuer_and_serial;\n+\n+\tX509_NAME_get_text_by_NID(pkis->issuer, NID_commonName, info->issuer,\n+\t\t\t\t  ISSUER_LEN);\n+\tinfo->serial = ASN1_INTEGER_get(pkis->serial);\n+\tinfo->cert = sk_X509_value(token->d.sign->cert, 0);\n+\n+\treturn 0;\n+}\n+\n+/* Use libssl functions to convert data to base64 and output it via stdout */\n+static int output_as_base64(TS_RESP *response)\n+{\n+\tBIO *b64 = NULL;\n+\tBIO *out = NULL;\n+\n+\tb64 = BIO_new(BIO_f_base64());\n+\tout = BIO_new_fp(stdout, BIO_NOCLOSE);\n+\tout = BIO_push(b64, out);\n+\n+\tif (!i2d_TS_RESP_bio(out, response))\n+\t\treturn 1;\n+\n+\tBIO_flush(out);\n+\tBIO_free_all(out);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Strip TSR by removing all X509 certificates as they are stored in a dedicated\n+ * TSA certificate store.\n+ */\n+static TS_RESP *strip_tsr(TS_RESP *response)\n+{\n+\twhile (sk_X509_num(response->token->d.sign->cert) > 0)\n+\t\tsk_X509_pop(response->token->d.sign->cert);\n+\n+\treturn response;\n+}\n+\n+static TS_REQ *create_query(const char *digest)\n+{\n+\tint fail = 0;\n+\tTS_REQ *ts_req = NULL;\n+\tTS_MSG_IMPRINT *msg_imprint = NULL;\n+\tASN1_INTEGER *nonce = NULL;\n+\n+\t/* Creating request object. */\n+\tts_req = TS_REQ_new();\n+\tif (!ts_req)\n+\t\tgoto err;\n+\n+\t/* Setting version. */\n+\tif (!TS_REQ_set_version(ts_req, 1))\n+\t\tgoto err;\n+\n+\t/* Setting MSG_IMPRINT */\n+\tmsg_imprint = create_msg_imprint(digest);\n+\tif (!msg_imprint)\n+\t\tgoto err;\n+\tif (!TS_REQ_set_msg_imprint(ts_req, msg_imprint))\n+\t\tgoto err;\n+\n+\t/* Setting nonce. */\n+\tnonce = create_nonce(NONCE_LENGTH);\n+\tif (!nonce)\n+\t\tgoto err;\n+\tif (!TS_REQ_set_nonce(ts_req, nonce))\n+\t\tgoto err;\n+\n+\t/* Set certificate request flag. */\n+\tif (!TS_REQ_set_cert_req(ts_req, 1))\n+\t\tgoto err;\n+\n+\tfail = 1;\n+err:\n+\tif (!fail) {\n+\t\tTS_REQ_free(ts_req);\n+\t\tts_req = NULL;\n+\t}\n+\n+\tTS_MSG_IMPRINT_free(msg_imprint);\n+\tASN1_INTEGER_free(nonce);\n+\treturn ts_req;\n+}\n+\n+static TS_MSG_IMPRINT *create_msg_imprint(const char *digest)\n+{\n+\tlong len;\n+\tconst EVP_MD *md = NULL;\n+\tTS_MSG_IMPRINT *msg_imprint = NULL;\n+\tX509_ALGOR *algo = NULL;\n+\tunsigned char *data = NULL;\n+\tint fail = 1;\n+\n+\t/* Creating MSG_IMPRINT object. */\n+\tmsg_imprint = TS_MSG_IMPRINT_new();\n+\tif (!msg_imprint)\n+\t\tgoto err;\n+\n+\tdata = string_to_hex(digest, &len);\n+\tif (!TS_MSG_IMPRINT_set_msg(msg_imprint, data, len))\n+\t\tgoto err;\n+\n+\t/* Adding sha1 algorithm. */\n+\tmd = EVP_sha1();\n+\talgo = X509_ALGOR_new();\n+\tif (!algo)\n+\t\tgoto err;\n+\talgo->algorithm = OBJ_nid2obj(EVP_MD_type(md));\n+\tif (!algo->algorithm)\n+\t\tgoto err;\n+\talgo->parameter = ASN1_TYPE_new();\n+\tif (!algo->parameter)\n+\t\tgoto err;\n+\talgo->parameter->type = V_ASN1_NULL;\n+\tif (!TS_MSG_IMPRINT_set_algo(msg_imprint, algo))\n+\t\tgoto err;\n+\n+\tfail = 0;\n+\n+err:\n+\tif (fail) {\n+\t\tTS_MSG_IMPRINT_free(msg_imprint);\n+\t\tmsg_imprint = NULL;\n+\t}\n+\n+\tOPENSSL_free(data);\n+\tX509_ALGOR_free(algo);\n+\treturn msg_imprint;\n+}\n+\n+static ASN1_INTEGER *create_nonce(int bits)\n+{\n+\tunsigned char buf[20];\n+\tASN1_INTEGER *nonce = NULL;\n+\tint len = (bits - 1) / 8 + 1;\n+\tint i;\n+\n+\t/* Generating random byte sequence. */\n+\tif (len > (int) sizeof(buf))\n+\t\tgoto err;\n+\tif (RAND_bytes(buf, len) <= 0)\n+\t\tgoto err;\n+\n+\t/* Find the first non-zero byte and creating ASN1_INTEGER object. */\n+\tfor (i = 0; i < len && !buf[i]; ++i)\n+\t\t;\n+\tnonce = ASN1_INTEGER_new();\n+\tif (!nonce)\n+\t\tgoto err;\n+\tOPENSSL_free(nonce->data);\n+\n+\t/* Allocate at least one byte. */\n+\tnonce->length = len - i;\n+\tnonce->data = OPENSSL_malloc(nonce->length + 1);\n+\tif (!nonce->data)\n+\t\tgoto err;\n+\tmemcpy(nonce->data, buf + i, nonce->length);\n+\n+\treturn nonce;\n+ err:\n+\tASN1_INTEGER_free(nonce);\n+\treturn NULL;\n+}\n+\n+static TS_VERIFY_CTX *create_verify_ctx(const char *digest, const char *CApath,\n+\t\t\t\t\tX509 *cert)\n+{\n+\tTS_VERIFY_CTX *ctx = NULL;\n+\tunsigned char *sha1 = NULL;\n+\tlong imprint_len;\n+\tint f = 0;\n+\n+\tctx = TS_VERIFY_CTX_new();\n+\tif (!ctx)\n+\t\tgoto err;\n+\n+\tf = TS_VFY_VERSION | TS_VFY_SIGNER | TS_VFY_IMPRINT | TS_VFY_SIGNATURE;\n+\tsha1 = string_to_hex(digest, &imprint_len);\n+\tif (!sha1)\n+\t\tgoto err;\n+\n+\t/* Add digest to verification context. */\n+\tctx->imprint = sha1;\n+\tctx->imprint_len = imprint_len;\n+\n+\t/* Add the signature verification flag and arguments. */\n+\tctx->flags = f;\n+\n+\t/* Initialising the X509_STORE object. */\n+\tctx->store = create_cert_store(CApath);\n+\tif (!ctx->store)\n+\t\tgoto err;\n+\n+\t/* Add signing certificate of TSA */\n+\tctx->certs = sk_X509_new_null();\n+\tif (!ctx->certs)\n+\t\tgoto err;\n+\n+\tsk_X509_push(ctx->certs, cert);\n+\n+\n+\treturn ctx;\n+\n+err:\n+\t/*\n+\t * TS_VERIFY_CTX also cleans up *sha1 and the created X509 store, so no\n+\t * further action is needed.\n+\t */\n+\tTS_VERIFY_CTX_free(ctx);\n+\treturn NULL;\n+}\n+\n+static X509_STORE *create_cert_store(const char *CApath)\n+{\n+\tX509_STORE *cert_ctx = NULL;\n+\tX509_LOOKUP *lookup = NULL;\n+\tint ret;\n+\n+\tcert_ctx = X509_STORE_new();\n+\tlookup = X509_STORE_add_lookup(cert_ctx, X509_LOOKUP_hash_dir());\n+\tif (!lookup)\n+\t\tgoto err;\n+\n+\tret = X509_LOOKUP_add_dir(lookup, CApath, X509_FILETYPE_PEM);\n+\tif (!ret) {\n+\t\terror(_(\"Error loading directory %s\"), CApath);\n+\t\tgoto err;\n+\t}\n+\n+\treturn cert_ctx;\n+\n+err:\n+\tX509_STORE_free(cert_ctx);\n+\treturn NULL;\n+}\n+\n+\n+static void usage_and_die(const char *name)\n+{\n+\tfprintf(stderr, \"Usage: %s <-c | -v> [<sha1>]\\n\\n\", name);\n+\tfputs(\"-c: Create a time-stamp signature for the given SHA1-hash\\n\",\n+\t      stderr);\n+\tfputs(\"-v: Verify a time-stamp signature from data passed via stdin\\n\",\n+\t      stderr);\n+\n+\texit(EXIT_FAILURE);\n+}\n+\n+int main(int argc, char *argv[])\n+{\n+\tCRYPTO_malloc_init();\n+\tERR_load_crypto_strings();\n+\tOpenSSL_add_all_algorithms();\n+\n+\tif (argc != 3)\n+\t\tusage_and_die(argv[0]);\n+\n+\tif (!strcmp(argv[1], \"-c\"))\n+\t\treturn create_tsr(argv[2]);\n+\n+\telse if (!strcmp(argv[1], \"-v\"))\n+\t\treturn verify_tsr(argv[2]);\n+\n+\telse\n+\t\tusage_and_die(argv[0]);\n+\n+\treturn 0;\n+}\n-- \n2.8.0.rc0.62.gfc8aefa.dirty\n"},{"id":"281069","messageId":"1458240421-3593-10-git-send-email-anton.wuerfel@fau.de","threadId":"41730","inReplyTo":"1458240421-3593-1-git-send-email-anton.wuerfel@fau.de","subject":"[PATCH 09/10] Add time-stamping functionality to git verify-tag","fromName":"Anton Wuerfel","fromEmail":"anton.wuerfel@fau.de","sentAt":"2016-03-17T18:47:00Z","receivedAt":"2016-03-17T18:47:00Z","isPatch":true,"sender":{"key":"anton.wuerfel@fau.de","avatar":null},"body":"This commit introduces command line options for git verify-tag to allow\nverification of RFC3161 time-stamped tags.\n\nTo keep consistent with the current behavior of verifying gpg signatures, the\nreturn value of `git verify-tag` still indicates only the success of gpg\nsignature verification by default. To influence this behavior, the configuration\nvariable `ts.failonverify` is introduced.\n\nts.failonverify set to 0: use default behavior\n\tReturn 1 if gpg verification failed or no signature was found\n\tReturn 0 on success.\n\nIf config variable ts.failonverify is set, the return value includes the status\nof time-stamp verification in the second lowest bit. Possible return values are:\n\t0 - both gpg and time stamp verification succeeded\n\t1 - gpg failed, time stamp succeeded\n\t2 - gpg succeeded, time stamp failed\n\t3 - both gpg and time stamp verification failed\n\nCommand line parameters `-s` or `-t` override the configuration\nvariable mentioned above.\n\nSigned-off-by: Anton Würfel <anton.wuerfel@fau.de>\nSigned-off-by: Phillip Raffeck <phillip.raffeck@fau.de>\n---\n builtin/verify-tag.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++----\n 1 file changed, 57 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/verify-tag.c b/builtin/verify-tag.c\nindex 00663f6..67fd464 100644\n--- a/builtin/verify-tag.c\n+++ b/builtin/verify-tag.c\n@@ -12,9 +12,15 @@\n #include <signal.h>\n #include \"parse-options.h\"\n #include \"gpg-interface.h\"\n+#include \"rfc3161.h\"\n+\n+#define GPG_VERIFY_RET 4\n+#define TS_VERIFY_RET 8\n+\n+static const char *config_key_verify_ts = \"ts.failonverify\";\n \n static const char * const verify_tag_usage[] = {\n-\t\tN_(\"git verify-tag [-v | --verbose] <tag>...\"),\n+\t\tN_(\"git verify-tag [-s | --signature] [-t | --timestamp] [-v | --verbose] [--raw] <tag>...\"),\n \t\tNULL\n };\n \n@@ -31,7 +37,8 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)\n \tif (size == len) {\n \t\tif (flags & GPG_VERIFY_VERBOSE)\n \t\t\twrite_in_full(1, buf, len);\n-\t\treturn error(\"no signature found\");\n+\t\tprintf(\"pgp: no signature found\\n\");\n+\t\treturn 1;\n \t}\n \n \tret = check_signature(buf, len, buf + len, size - len, &sigc);\n@@ -47,7 +54,12 @@ static int verify_tag(const char *name, unsigned flags)\n \tunsigned char sha1[20];\n \tchar *buf;\n \tunsigned long size;\n-\tint ret;\n+\tint gpg_had_error = 0;\n+\tint ret = 0;\n+\tint config_failonverify = 0;\n+\tint ts_had_error = 0;\n+\n+\tgit_config_get_bool(config_key_verify_ts, &config_failonverify);\n \n \tif (get_sha1(name, sha1))\n \t\treturn error(\"tag '%s' not found.\", name);\n@@ -61,9 +73,48 @@ static int verify_tag(const char *name, unsigned flags)\n \tif (!buf)\n \t\treturn error(\"%s: unable to read file.\", name);\n \n-\tret = run_gpg_verify(buf, size, flags);\n+\tif (run_gpg_verify(buf, size, flags))\n+\t\tgpg_had_error = 1;\n+\n+#if defined(NO_CURL) || defined(NO_OPENSSL)\n+\tfputs(\"git has been compiled without RFC3161 time-stamp support. \"\n+\t      \"NO_CURL and NO_OPENSSL must not be defined\", stderr);\n+#else\n+\tif (verify_time_signature(buf, size))\n+\t\tts_had_error = 1;\n+#endif\n \n \tfree(buf);\n+\n+\t/*\n+\t * If the config variable ts.failonverify is not set, behave like older\n+\t * versions of git verify-tag:\n+\t * Return 1 if gpg verification failed or no signature was found\n+\t * Return 0 on success.\n+\t *\n+\t * If the config variable ts.failonverify is set and neither -s nor -t\n+\t * are set as command line parameters, the return value includes the\n+\t * status of time-stamp verification in the second lowest bit. Possible\n+\t * return values are:\n+\t * 0 - both gpg and time stamp verification succeeded\n+\t * 1 - gpg failed, time stamp succeeded\n+\t * 2 - gpg succeeded, time stamp failed\n+\t * 3 - both gpg and time stamp verification failed\n+\t *\n+\t * Command line parameters `-s` or `-t` override the configuration\n+\t * variable mentioned above.\n+\t */\n+\tif (flags & (GPG_VERIFY_RET | TS_VERIFY_RET)) {\n+\t\tif (flags & GPG_VERIFY_RET)\n+\t\t\tret = gpg_had_error;\n+\t\tif (flags & TS_VERIFY_RET)\n+\t\t\tret |= (ts_had_error << 1);\n+\t} else {\n+\t\tret = gpg_had_error;\n+\n+\t\tif (config_failonverify)\n+\t\t\tret |= (ts_had_error << 1);\n+\t}\n \treturn ret;\n }\n \n@@ -82,6 +133,8 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)\n \tconst struct option verify_tag_options[] = {\n \t\tOPT__VERBOSE(&verbose, N_(\"print tag contents\")),\n \t\tOPT_BIT(0, \"raw\", &flags, N_(\"print raw gpg status output\"), GPG_VERIFY_RAW),\n+\t\tOPT_BIT('s', \"signature\", &flags, N_(\"report return value of gpg signature\"), GPG_VERIFY_RET),\n+\t\tOPT_BIT('t', \"timestamp\", &flags, N_(\"report return value of time-stamp signature\"), TS_VERIFY_RET),\n \t\tOPT_END()\n \t};\n \n-- \n2.8.0.rc0.62.gfc8aefa.dirty\n"},{"id":"281072","messageId":"1458240421-3593-11-git-send-email-anton.wuerfel@fau.de","threadId":"41730","inReplyTo":"1458240421-3593-1-git-send-email-anton.wuerfel@fau.de","subject":"[PATCH 10/10] Add time-stamping functionality to git tag","fromName":"Anton Wuerfel","fromEmail":"anton.wuerfel@fau.de","sentAt":"2016-03-17T18:47:01Z","receivedAt":"2016-03-17T18:47:01Z","isPatch":true,"sender":{"key":"anton.wuerfel@fau.de","avatar":null},"body":"This commit introduces command line options for git tag to allow adding trusted\ntime-stamps from a Time Stamping Authority according to RFC3161.\n\nThe SHA-1 has used for a time-stamp signature is generated from the header data\nand the tag message, if present. After obtaining the time-stamp signature, it is\ninserted into the object header under the `timesig`-key in a custom PEM-like\nformat. If the tag is also GPG-signed, the GPG signature includes the time-stamp\nsignature to prevent attackers from altering the time-stamp signature or\nreplacing it.\n\nHowever, it is still possible to create tags with only a GPG signature or only a\ntime-stamp, although it is recommended to additionally GPG-sign time-stamp\nsignatures for the reasons stated above.\n\nIn contrast to the GPG signature, the time-stamp signatures are part of\nthe header, emulating the way GPG signatures of signed commits are stored. This\nfacilitates implementing RFC3161 time-stamps for commits eventually.\n\nSigned-off-by: Anton Würfel <anton.wuerfel@fau.de>\nSigned-off-by: Phillip Raffeck <phillip.raffeck@fau.de>\n---\n builtin/tag.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++---\n 1 file changed, 52 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 1705c94..9b3d2a1 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -18,9 +18,10 @@\n #include \"sha1-array.h\"\n #include \"column.h\"\n #include \"ref-filter.h\"\n+#include \"rfc3161.h\"\n \n static const char * const git_tag_usage[] = {\n-\tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]\"),\n+\tN_(\"git tag [-a | -s | -u <key-id> | -t] [-f] [-m <msg> | -F <file>] <tagname> [<head>]\"),\n \tN_(\"git tag -d <tagname>...\"),\n \tN_(\"git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]\"\n \t\t\"\\n\\t\\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]\"),\n@@ -118,6 +119,39 @@ static int do_sign(struct strbuf *buffer)\n \treturn sign_buffer(buffer, buffer, get_signing_key());\n }\n \n+static int do_timesig(struct strbuf *buffer)\n+{\n+\tstruct strbuf sig = STRBUF_INIT;\n+\tint inspos, copypos;\n+\n+\t/* find the end of the header */\n+\tinspos = strstr(buffer->buf, \"\\n\\n\") - buffer->buf + 1;\n+\n+\tif (create_time_signature(buffer, &sig)) {\n+\t\tstrbuf_release(&sig);\n+\t\treturn -1;\n+\t}\n+\n+\tfor (copypos = 0; sig.buf[copypos]; ) {\n+\t\tconst char *bol = sig.buf + copypos;\n+\t\tconst char *eol = strchrnul(bol, '\\n');\n+\t\tint len = (eol - bol) + !!*eol;\n+\n+\t\tif (!copypos) {\n+\t\t\tstrbuf_insert(buffer, inspos, time_sig_header,\n+\t\t\t\t      time_sig_header_len);\n+\t\t\tinspos += time_sig_header_len;\n+\t\t}\n+\t\tstrbuf_insert(buffer, inspos++, \" \", 1);\n+\t\tstrbuf_insert(buffer, inspos, bol, len);\n+\t\tinspos += len;\n+\t\tcopypos += len;\n+\t}\n+\tstrbuf_release(&sig);\n+\n+\treturn 0;\n+}\n+\n static const char tag_template[] =\n \tN_(\"\\nWrite a message for tag:\\n  %s\\n\"\n \t\"Lines starting with '%c' will be ignored.\\n\");\n@@ -193,8 +227,11 @@ static void write_tag_body(int fd, const unsigned char *sha1)\n \tfree(buf);\n }\n \n-static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)\n+static int build_tag_object(struct strbuf *buf, int sign, int timesig,\n+\t\t\t    unsigned char *result)\n {\n+\tif (timesig && do_timesig(buf) < 0)\n+\t\treturn error(_(\"unable to generate time-stamp signature\"));\n \tif (sign && do_sign(buf) < 0)\n \t\treturn error(_(\"unable to sign the tag\"));\n \tif (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0)\n@@ -205,6 +242,7 @@ static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)\n struct create_tag_options {\n \tunsigned int message_given:1;\n \tunsigned int sign;\n+\tunsigned int timesig;\n \tenum {\n \t\tCLEANUP_NONE,\n \t\tCLEANUP_SPACE,\n@@ -276,7 +314,7 @@ static void create_tag(const unsigned char *object, const char *tag,\n \n \tstrbuf_insert(buf, 0, header_buf, header_len);\n \n-\tif (build_tag_object(buf, opt->sign, result) < 0) {\n+\tif (build_tag_object(buf, opt->sign, opt->timesig, result) < 0) {\n \t\tif (path)\n \t\t\tfprintf(stderr, _(\"The tag message has been left in %s\\n\"),\n \t\t\t\tpath);\n@@ -350,6 +388,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\t     N_(\"tag message\"), parse_msg_arg),\n \t\tOPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n \t\tOPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n+\t\tOPT_BOOL('t', \"timestamp\", &opt.timesig, N_(\"add trusted RFC3161 time-stamp\")),\n \t\tOPT_STRING(0, \"cleanup\", &cleanup_arg, N_(\"mode\"),\n \t\t\tN_(\"how to strip spaces and #comments from message\")),\n \t\tOPT_STRING('u', \"local-user\", &keyid, N_(\"key-id\"),\n@@ -387,6 +426,16 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t}\n \tif (opt.sign)\n \t\tannotate = 1;\n+\n+#if defined(NO_CURL) || defined(NO_OPENSSL)\n+\tif (opt.timesig)\n+\t\treturn error(\"git has been compiled without RFC3161 time-stamp support. \"\n+\t\t\t     \"NO_CURL and NO_OPENSSL must not be defined\");\n+#else\n+\tif (opt.timesig)\n+\t\tannotate = 1;\n+#endif\n+\n \tif (argc == 0 && !cmdmode)\n \t\tcmdmode = 'l';\n \n-- \n2.8.0.rc0.62.gfc8aefa.dirty\n"}]}