gitversion-controlsigngit-sign

Git sign commit somewhere in history


On a number of parts of the Internet (such as here), it is implied that git commits must be signed when being done or never.

However, technically a signature on a commit is nothing more than a signature on the commit object (as shown here), that consists of a hash of the "tree" file (that is a list of hashes of the git objects), the hash of the parent and some metadata.

As a consequence, there seems to be nothing that prevents a commit to be signed a posteriori, without rewriting the entire history.

Is it really possible? Is there a recommended way of doing so? Would such an after-the-fact signature work nicely with pushes and pulls?


Solution

  • I don't believe you can do this without rewriting history. I just cloned the same repository twice to conduct a test. I made the same change in both repositories, then committed with the same log message ("foobar"), the only difference being that one was signed and one was not.

    # unsigned test
    parent 50c6dd65f1d7a240cf6b5c9585ce363ef4708d1e
    new    b3ff731922f80a417b84ed492537c1f7ba74715e
    
    # signed test
    parent 50c6dd65f1d7a240cf6b5c9585ce363ef4708d1e
    new    688b3be2e55558c45b00b6a6c02086a03768e02d
    

    As you can see, starting from the same parent (50c6dd65), the result is a two different commit hashes. So for un-pushed commits, it's not any different than any other history rewriting (and therefore it carries the same liabilities).

    In response to your comment asking if the hash changed merely because of a difference in timestamps, I don't believe so. If you inspect using cat-file:

    $ git cat-file -p 688b3be2e55558c45b00b6a6c02086a03768e02d
    tree 074e53e54670dea3502229e9494f3d571f5dcc16
    parent 50c6dd65f1d7a240cf6b5c9585ce363ef4708d1e
    author Dan Lowe <dan@XXXXXXXX.com> 1448768563 -0500
    committer Dan Lowe <dan@XXXXXXXX.com> 1448768563 -0500
    gpgsig -----BEGIN PGP SIGNATURE-----
     Version: GnuPG v1
    
     iQEVAwUAVlp0N1rGfrtJ2k+kAQIbYQf7BLx3/jqU/vwvoJOcbq5MPK0ok7B8mOaF
     VWhNCbAeOBMzXdrn8IQxY2xYcPsy+d6pNx6ZOF70L3VZm6rWFxNzZQRrjS4ByOAM
     VyoL8bXceMcrb/sQUHM5yTCaDcfoYx40K0q91XsGew2EIzNKcOraK1Ee4hEtPg1D
     ojyPVjiWz2qMJJ0YYAATSvWwlKFO2ShTC6tGZDHrx0e6BAEN5QS4KdGhNech/vpU
     IPFDjIKWtGPTbYY8Z95vKLAMYWPZDJ8j/x1gRytN8PDjRufRtpRnZMccB6JQoXNZ
     5L23WQFfUFeXRdWf0MtkrbrSwzuaaIF8l1oGYnEtYT6nOIktPT47Fw==
     =/U9b
     -----END PGP SIGNATURE-----
    
    foobar
    

    As I understand it, this metadata is all part of the input to the hash algorithm resulting in the commit ID. If that is the case, the presence of the gpgsig data here means it will always give you a different hash than the unsigned version of the commit.