Summary
Occasionally I hear that people are using git signed commits. IMO they should probably be doing something else. I am not aware of any use case for which signed git commits are the right answer. All uses should be replaced by signed tags, or signed pushes.
Analysis
The root cause of the problem is that a commit does not say which branch it is destined for. The same commit (tree, message, and ancestry) might be a debugging patch - not intended for deployment, even harmful; or it might be unfinished and buggy; or it might be ready for deployment.
In practical terms, if your system is set up to require every commit to be signed, the expected development model is that developers will configure their git to sign every commit. (The alternative is to batch-rewrite branches later, to add the signatures, which is an obvious subversion of the intention that signed commits were to provide traceability.)
If you make your developers sign every commit as they go, they will sign lots and lots of junk. Test commits, unfinished branches, local experiments, etc. etc. Whatever way your signatures are supposed to be part of your security assurance, is subvertible by anyone who can find copies of these signed junk branches - and git of course encourages promiscuous sharing of branches, including works in progress. (Usually that promiscuious sharing is good.)
Furthermore, developers will have to have their signing key continuously available; so to the extent that that signing key is important for your system's integrity, it is weakened.
What is really required is that person who approves a push to master should makes a signature declaring what they intend. It is at that point that a human tells their computer what the destination of a branch is supposed to be - before that, the branch is an unfinished work-in-progress.
The same is true of other kinds of publication operations where it looks like signed commits might be useful. For example, submission of a pull request for review: you want the signature to say what the person making it intends: ie, that this is a pull request, which branch it is targeted for, etc.
There are two git features that can be used to implement what is really needed:
Signed pushes
git supports attaching a gpg signature to ‘git push’. This would be precisely right for many applications.
The signature covers precisely what it ought to: it describes the set of refs that should be updated, including their previous values. So (if you have a workflow involving reviews) the signature shows whether the signer intended “this should be reviewed as a submission to master” (a request to the reviewer) or “I have reviewed and approved this and it should be pushed to master immediately” (an instruction to the repository). You can tell the difference between “this is intended for master” and “this is intended as a stopgap to fix user issue in ticket #NNNN”.
Unfortunately the git server side it is afflicted by a
number of annoyances, and a
lack of good docs, which make verification and publication of the push history awkward.
By default the git server in the main git project does not record push signatures anywhere. Obviously you would need to store them. There are some
semi[1]
standard approaches to this problem, implemented by some other git server projects.
[1] Javascript needed to even see this. Sorry.
Signed tags
An obvious alternative feature is signed tags. A tag has a name; furthermore, it has an associated message. If necessary the message can carry metadata about the author's intent. (Commits can carry such metadata too but it is not desirable to rewrite a commit just to change its intended destination.)
So it is possible to use signed tags to do the job of signed pushes.
In this model, every push to every controlled branch is associated with a signed tag. The signed tag's name identifies the branch it is intended for. The branch update should be accepted if its tag refers to commit which is descended from the current HEAD. Later verification involves observing that the commit objects referred to by the signed tags form the expected DAG.
(The
dgit git repository I run for Debian verifies pushes by looking for particular signed tags.)
Signing only the merges to master
There is one caveat to my assertion that signed commits are useless. A signed merge commit can sometimes do the job of a signed push.
In this use model, mainline only advances by merges, and the only signed commits are the merges which bless other branches into mainline. The rest of the merged branch consists of unsigned commits.
If your tools support only signed commits, rather than signed pushes (or don't record push signatures), and can't be made to do useful verification of signed tags, maybe you could get them to expect (only) the mainline merges to be signed.
There would be some subtleties here - for example, because merges can be nontrivial. I wouldn't recommend this approach without thinking harder about it.