Git sign commit somewhere in history

244 views Asked by At

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?

2

There are 2 answers

3
Dan Lowe On BEST ANSWER

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 <[email protected]> 1448768563 -0500
committer Dan Lowe <[email protected]> 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.

0
mgarciaisaia On

Every git object is identified by it's own SHA-1 hash. So they are all immutable.

If you sign a commit you've previously done, you are, in fact, duplicating that commit, but in a signed way. So the original commit is still there - you can even git show it if you remember it's hash or have any ref pointing at it. What you'd do is to create a new commit with equivalent changes and metadata, but with a signature added, and make the ref you are currently on - ie, your current branch - point at it.

The commit's tree and every other depending object will be the same. But, as the commit itself has changed, you get a new commit object - so you are re-writing history.