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?
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.