Search code examples
gitgnupg

What data is being signed when you `git commit --gpg-sign=<key-id>`?


I'm trying to figure out how to sign/verify commits by hand, but I can't figure out what data is being signed to create the signature. In other words, I can't figure out what <data> in gpg --verify <commit-sig> <data> needs to be.

Here's the relevant bit of git's source code: https://github.com/git/git/blob/master/commit.c#L1047-L1231 but I'm also new to C.


Here's some example data:

In a fresh git repo, I create a file ledger.txt and commit it with a signed commit:

git config --global user.signingkey 7E482429
git init
echo "EAC5-531F-38E8-9670-81AE-4E77-C7AA-5FC3-7E48-2429 1\n" > ledger.txt
git add ledger.txt
git commit -m "Initial commit" --gpg-sign=7E482429

And here it is in the log:

git log --show-signature

    commit 876793da21833b5b8197b08462523fd6aad3e5ba
    gpg: Signature made Fri May  9 20:01:55 2014 CDT using RSA key ID 7E482429
    gpg: Good signature from "Dan Neumann <danrodneu@gmail.com>"
    Author: Dan Neumann <danrodneu@gmail.com>
    Date:   Fri May 9 20:01:55 2014 -0500

        Initial commit

Here's the pretty-printed commit object (which lives in .git/objects/87/6793da21833b5b8197b08462523fd6aad3e5ba):

git cat-file -p 876793da21833b5b8197b08462523fd6aad3e5ba

tree 70e7c184c3a89c749174b4987830c287fd78952d
author Dan Neumann <danrodneu@gmail.com> 1399683715 -0500
committer Dan Neumann <danrodneu@gmail.com> 1399683715 -0500
gpgsig -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1

 iQEcBAABAgAGBQJTbXqDAAoJEMeqX8N+SCQpTBIH/3zCpf0w0+xp8hkwz7dTV9Bw
 ercZp4UpxKV1HgqCxu2r/nGIuZyabLwTis1rcwXOVC4DgRxO0f2BiP0xnyL3OhJu
 CKh8l+HZvvGqVH3Dopm0D/kOxDAWHcjokbyzWBbYJX6WhvT8OI7SSYmwuF4r610h
 hkZ1xgjo4p1x9WegY296PzA1wEe6yy9BvvdIpJHoqBVKClgFrZvtE5PidbrAyLGF
 Kl/2f0K3peBdo6XP0Zaml8NyQlFmAlCV831hHgUmZsBSRpgh/WNvrDSNILTlFJgY
 BOPb2yPP+tiJOXYB66MsjQY9GlX7n43miu5wMtdk1AGqh+26OExbSrZcYVFLk4w=
 =sRee
 -----END PGP SIGNATURE-----

Initial commit

And here are the actual contents of the commit object file:

hexdump .git/objects/87/6793da21833b5b8197b08462523fd6aad3e5ba | \
zlib-decompress | \
bin-to-ascii

commit 671\0tree 70e7c184c3a89c749174b4987830c287fd78952d\nauthor Dan Neumann <danrodneu@gmail.com> 1399683715 -0500\ncommitter Dan Neumann <danrodneu@gmail.com> 1399683715 -0500\ngpgsig -----BEGIN PGP SIGNATURE-----\n Version: GnuPG v1\n \n iQEcBAABAgAGBQJTbXqDAAoJEMeqX8N+SCQpTBIH/3zCpf0w0+xp8hkwz7dTV9Bw\n ercZp4UpxKV1HgqCxu2r/nGIuZyabLwTis1rcwXOVC4DgRxO0f2BiP0xnyL3OhJu\n CKh8l+HZvvGqVH3Dopm0D/kOxDAWHcjokbyzWBbYJX6WhvT8OI7SSYmwuF4r610h\n hkZ1xgjo4p1x9WegY296PzA1wEe6yy9BvvdIpJHoqBVKClgFrZvtE5PidbrAyLGF\n Kl/2f0K3peBdo6XP0Zaml8NyQlFmAlCV831hHgUmZsBSRpgh/WNvrDSNILTlFJgY\n BOPb2yPP+tiJOXYB66MsjQY9GlX7n43miu5wMtdk1AGqh+26OExbSrZcYVFLk4w=\n =sRee\n -----END PGP SIGNATURE-----\n\nInitial commit\n

Solution

  • After reading the code in commit_tree_extended, it seems the data used to sign is the part from "tree" to the end of the comment, of course excluding the signature.

    In your example, it should be:

    tree 70e7c184c3a89c749174b4987830c287fd78952d
    author Dan Neumann <danrodneu@gmail.com> 1399683715 -0500
    committer Dan Neumann <danrodneu@gmail.com> 1399683715 -0500
    
    Initial commit
    

    From the git source:

    Init of buffer:

    strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
    strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
    

    Parent commits traversing:

    /*
    * NOTE! This ordering means that the same exact tree merged with a
    * different order of parents will be a _different_ changeset even
    * if everything else stays the same.
    */
    while (parents) {
        struct commit_list *next = parents->next;
        struct commit *parent = parents->item;
    
        strbuf_addf(&buffer, "parent %s\n",
        sha1_to_hex(parent->object.sha1));
        free(parents);
        parents = next;
    }
    

    Person/date information:

    if (!author)
        author = git_author_info(IDENT_STRICT);
    strbuf_addf(&buffer, "author %s\n", author);
    strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_STRICT));
    if (!encoding_is_utf8)
        strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
    
    while (extra) {
        add_extra_header(&buffer, extra);
        extra = extra->next;
    }
    strbuf_addch(&buffer, '\n');
    

    The comment & encoding check:

    /* And add the comment */
    strbuf_addbuf(&buffer, msg);
    
    /* And check the encoding */
    if (encoding_is_utf8 && !verify_utf8(&buffer))
        fprintf(stderr, commit_utf8_warn);
    

    That's where the signing happens. Signature will be added after the header.

    if (sign_commit && do_sign_commit(&buffer, sign_commit))
        return -1;
    

    There would be parent information too if your commit had some.