From a65afb757e2675eb8889a9ce1f8809434cdb3af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 8 Feb 2016 18:51:13 +0100 Subject: [PATCH] Introduce git_commit_extract_signature This returns the GPG signature for a commit and its contents without the signature block, allowing for the verification of the commit's signature. --- include/git2/commit.h | 12 ++++++ src/commit.c | 86 +++++++++++++++++++++++++++++++++++++++++++ tests/commit/parse.c | 49 ++++++++++++++++++++++++ 3 files changed, 147 insertions(+) diff --git a/include/git2/commit.h b/include/git2/commit.h index 34d29ed81..a92277417 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -263,6 +263,18 @@ GIT_EXTERN(int) git_commit_nth_gen_ancestor( */ GIT_EXTERN(int) git_commit_header_field(git_buf *out, const git_commit *commit, const char *field); +/** + * Extract the signature from a commit + * + * @param signature the signature block + * @param signed_data signed data; this is the commit contents minus the signature block + * @param repo the repository in which the commit exists + * @param commit_id the commit from which to extract the data + * @param field the name of the header field containing the signature + * block; pass `NULL` to extract the default 'gpgsig' + */ +GIT_EXTERN(int) git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_repository *repo, git_oid *commit_id, const char *field); + /** * Create new commit in the repository from a list of `git_object` pointers * diff --git a/src/commit.c b/src/commit.c index 81aae489f..c5e580139 100644 --- a/src/commit.c +++ b/src/commit.c @@ -616,3 +616,89 @@ oom: giterr_set_oom(); return -1; } + +int git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_repository *repo, git_oid *commit_id, const char *field) +{ + git_odb_object *obj; + git_odb *odb; + const char *buf; + const char *h, *eol; + int error; + + git_buf_sanitize(signature); + git_buf_sanitize(signed_data); + + if (!field) + field = "gpgsig"; + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) + return error; + + if ((error = git_odb_read(&obj, odb, commit_id)) < 0) + return error; + + buf = git_odb_object_data(obj); + + while ((h = strchr(buf, '\n')) && h[1] != '\0' && h[1] != '\n') { + h++; + if (git__prefixcmp(buf, field)) { + if (git_buf_put(signed_data, buf, h - buf) < 0) + return -1; + + buf = h; + continue; + } + + h = buf; + h += strlen(field); + eol = strchr(h, '\n'); + if (h[0] != ' ') { + buf = h; + continue; + } + if (!eol) + goto malformed; + + h++; /* skip the SP */ + + git_buf_put(signature, h, eol - h); + if (git_buf_oom(signature)) + goto oom; + + /* If the next line starts with SP, it's multi-line, we must continue */ + while (eol[1] == ' ') { + git_buf_putc(signature, '\n'); + h = eol + 2; + eol = strchr(h, '\n'); + if (!eol) + goto malformed; + + git_buf_put(signature, h, eol - h); + } + + if (git_buf_oom(signature)) + goto oom; + + git_odb_object_free(obj); + return git_buf_puts(signed_data, eol+1); + } + + giterr_set(GITERR_INVALID, "this commit is not signed"); + error = GIT_ENOTFOUND; + goto cleanup; + +malformed: + giterr_set(GITERR_OBJECT, "malformed header"); + error = -1; + goto cleanup; +oom: + giterr_set_oom(); + error = -1; + goto cleanup; + +cleanup: + git_odb_object_free(obj); + git_buf_clear(signature); + git_buf_clear(signed_data); + return error; +} diff --git a/tests/commit/parse.c b/tests/commit/parse.c index 388da078a..f518e3878 100644 --- a/tests/commit/parse.c +++ b/tests/commit/parse.c @@ -456,3 +456,52 @@ cpxtDQQMGYFpXK/71stq\n\ git_buf_free(&buf); git_commit__free(commit); } + +void test_commit_parse__extract_signature(void) +{ + git_odb *odb; + git_oid commit_id; + git_buf signature = GIT_BUF_INIT, signed_data = GIT_BUF_INIT; + const char *gpgsig = "-----BEGIN PGP SIGNATURE-----\n\ +Version: GnuPG v1.4.12 (Darwin)\n\ +\n\ +iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ +o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ +JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ +AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ +SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ +who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ +6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ +cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ +c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ +ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ +7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ +cpxtDQQMGYFpXK/71stq\n\ +=ozeK\n\ +-----END PGP SIGNATURE-----"; + + const char *data = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ +parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +\n\ +a simple commit which works\n"; + + + cl_git_pass(git_repository_odb__weakptr(&odb, g_repo)); + cl_git_pass(git_odb_write(&commit_id, odb, passing_commit_cases[4], strlen(passing_commit_cases[4]), GIT_OBJ_COMMIT)); + + cl_git_pass(git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, NULL)); + cl_assert_equal_s(gpgsig, signature.ptr); + cl_assert_equal_s(data, signed_data.ptr); + + git_buf_clear(&signature); + git_buf_clear(&signed_data); + + cl_git_pass(git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, "gpgsig")); + cl_assert_equal_s(gpgsig, signature.ptr); + cl_assert_equal_s(data, signed_data.ptr); + + git_buf_free(&signature); + git_buf_free(&signed_data); +}