Search code examples
gitgithubdigital-signaturecommit

Creating a signed commit via API


I am working on creating a signed commit via API in GitHub workflow so I just read document https://developer.github.com/v3/git/commits/#create-a-commit

I am creating a payload file with a sample data tree

827efc6d56897b048c772eb4087f854f46256132 parent
7d1b31e74ee336d15cbd21741bc88a537ed063a0 author Mona Octocat
<octocat@github.com> 1215576810 +1200 committer Mona Octocat
<octocat@github.com> 1215576810 +1200

my commit message

But I am getting Invalid in the sign commit I want to know what actually is required to do a signed commit via API as I am unable to find any blog or post.


Solution

  • First of all, you should try to understand how a Git commit is signed.

    To save some text, refer to What data is being signed when you `git commit --gpg-sign=<key-id>`? for details of the signed data - I'm going to only reproduce it here. Apparently you won't have GitHub's private key, so you must bring your own key. Upload it in GitHub settings so it becomes "trusted".

    First, perform the to-be-signed commit locally so you have the commit data. For example, I'm using the HEAD commit of my repository, at the time of writing this answer:

    ~ $ cd iBug-source
    ~/iBug-source $ git log -1 HEAD
    commit 351e7fe08176e35a9e4c91be2122921ada3cac3a (HEAD -> master, origin/master, origin/HEAD)
    Author: iBug <git@ibugone.com>
    Date:   Mon Nov 16 02:51:19 2020 +0800
    
        Force redirect
    

    Extract the payload for the GPG signature (the message part) from Git. You'll need to strip off the trailing newline (as I do with perl).

    ~/iBug-source $ git cat-file commit HEAD
    tree fe9d12667f47065738ebcb3f6dd665a4150be267
    parent fb4c5fb11f79142fc1f6f86fd7442274839626fb
    author iBug <git@ibugone.com> 1605466279 +0800
    committer iBug <git@ibugone.com> 1605466626 +0800
    
    Force redirect
    ~/iBug-source $ git cat-file commit HEAD | perl -pe 'chomp if eof' > commit
    

    Now I have the message payload in the commit file. I want to create a signature using my own key.

    Because Git expects a signature algorithm of SHA-1, I supplied --digest-algo SHA1 to the CLI invocation. I also added --clear-sign (signature only, do not include message body) and --armor (output ASCII armor format)

    ~/iBug-source $ gpg --clear-sign --digest-algo SHA1 --armor --local-user 0xA2C63304 commit
    

    The above command produced the file commit.asc with the following content:

    -----BEGIN PGP SIGNATURE-----
    
    iQIzBAEBAgAdFiEE1KqdrSj5MOe58w687j9yiKLGMwQFAl+ynVQACgkQ7j9yiKLG
    MwR21BAAp3pzyUhA2/5tn/DrO+bbD9X9BQ6GHLHtiaG8gjuWmaGHzjR2XUugTrRl
    aOluWR6//yNR9Uf3qIyxZahRYWYVy3Pl2UK8C+4s4alo7IjiF/7oKD3OVu5bjLvm
    GcbUfeyJQtOkNNH5o0o/einIoqhNCNgiFWjjsLcxPsG2bsNnF5Kmb8ONS3gArJQB
    7wT68sdj/oH82zCJU6bgEXohv3f+ZS82e8jX5jJBRL+ljz3crUl3DsgjsoKJiiUp
    ZjcfNffNQu4wEB6XK2zca5IgGfcBO3MF0yA13sh1JwKa54ZEikAI4T5lVfRkjUn7
    LPkwpMhw3033NyyrTFXF48i9oRSoMocJYmDOncY8Mgc+CJArvn/fT34bZ9rXH01Y
    qpeSAZv7AgyXc3jSQHZPjo76i/C9BwwZ1EoGUm4svom/0ejnOteM1Ff3grVnqipX
    Xo78a1BYHr0aLBxPpPaHMRlOdcMo0UYnqIm+P7VXtY0WxvPjXgemtSsXYrAMKSaa
    sAJ5Dv0jqYwhbQcVb5sGLC8zg+QmSbhV4HbrXmOcP8QC9H89EJSPzLQivQePGZrQ
    284vWTueNk68NyUQ5BUfXLIjYX/n6kgOeISNcvhDCVgWkvZNfN57fEOtq2FTsFKz
    Dg4ukCQkabA+lFB3AiVdhhLZT5ucjSFFfnLUkwaULRP5XEgQhH8=
    =YxhM
    -----END PGP SIGNATURE-----
    

    This is the signature you want in the signature JSON field, as given by GitHub documentation.

    Construct the API payload JSON:

    {
      "message": "Force redirect",
      "author": {
        "name": "iBug",
        "email": "git@ibugone.com",
        "date": "2020-11-16T02:51:19+08:00"
      },
      "committer": {
        "name": "iBug",
        "email": "git@ibugone.com",
        "date": "2020-11-16T02:57:06+08:00"
      },
      "parents": [
        "fb4c5fb11f79142fc1f6f86fd7442274839626fb"
      ],
      "tree": "fe9d12667f47065738ebcb3f6dd665a4150be267",
      "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAEBAgAdFiEE1KqdrSj5MOe58w687j9yiKLGMwQFAl+ynVQACgkQ7j9yiKLG\nMwR21BAAp3pzyUhA2/5tn/DrO+bbD9X9BQ6GHLHtiaG8gjuWmaGHzjR2XUugTrRl\naOluWR6//yNR9Uf3qIyxZahRYWYVy3Pl2UK8C+4s4alo7IjiF/7oKD3OVu5bjLvm\nGcbUfeyJQtOkNNH5o0o/einIoqhNCNgiFWjjsLcxPsG2bsNnF5Kmb8ONS3gArJQB\n7wT68sdj/oH82zCJU6bgEXohv3f+ZS82e8jX5jJBRL+ljz3crUl3DsgjsoKJiiUp\nZjcfNffNQu4wEB6XK2zca5IgGfcBO3MF0yA13sh1JwKa54ZEikAI4T5lVfRkjUn7\nLPkwpMhw3033NyyrTFXF48i9oRSoMocJYmDOncY8Mgc+CJArvn/fT34bZ9rXH01Y\nqpeSAZv7AgyXc3jSQHZPjo76i/C9BwwZ1EoGUm4svom/0ejnOteM1Ff3grVnqipX\nXo78a1BYHr0aLBxPpPaHMRlOdcMo0UYnqIm+P7VXtY0WxvPjXgemtSsXYrAMKSaa\nsAJ5Dv0jqYwhbQcVb5sGLC8zg+QmSbhV4HbrXmOcP8QC9H89EJSPzLQivQePGZrQ\n284vWTueNk68NyUQ5BUfXLIjYX/n6kgOeISNcvhDCVgWkvZNfN57fEOtq2FTsFKz\nDg4ukCQkabA+lFB3AiVdhhLZT5ucjSFFfnLUkwaULRP5XEgQhH8=\n=YxhM\n-----END PGP SIGNATURE-----\n"
    }
    

    Send it to GitHub API:

    ~/iBug-source $ curl -X POST \
      -H 'Authorization: token xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
      -H 'Accept: application/vnd.github.v3+json' \
      -H 'Content-Type: application/json' \
      --data @payload.json \
      https://api.github.com/repos/iBug/iBug-source/git/commits
    

    See the response (truncated):

    {
      "sha": "36105785c8665a400226c54a16cc4583b8f28ebd",
      // Truncated
    }
    

    See the commit (archive) on GitHub website in action!

    image


    Obviously, all the above steps can be done with your favorite toolchain, and you don't even have to perform the git commit. For example, the manually-constructed Git object is as follows, without the trailing newline:

    tree fe9d12667f47065738ebcb3f6dd665a4150be267
    parent fb4c5fb11f79142fc1f6f86fd7442274839626fb
    author iBug <git@ibugone.com> 1605466279 +0800
    committer iBug <git@ibugone.com> 1605466626 +0800
    gpgsig -----BEGIN PGP SIGNATURE-----
     
     iQIzBAEBAgAdFiEE1KqdrSj5MOe58w687j9yiKLGMwQFAl+ynVQACgkQ7j9yiKLG
     MwR21BAAp3pzyUhA2/5tn/DrO+bbD9X9BQ6GHLHtiaG8gjuWmaGHzjR2XUugTrRl
     aOluWR6//yNR9Uf3qIyxZahRYWYVy3Pl2UK8C+4s4alo7IjiF/7oKD3OVu5bjLvm
     GcbUfeyJQtOkNNH5o0o/einIoqhNCNgiFWjjsLcxPsG2bsNnF5Kmb8ONS3gArJQB
     7wT68sdj/oH82zCJU6bgEXohv3f+ZS82e8jX5jJBRL+ljz3crUl3DsgjsoKJiiUp
     ZjcfNffNQu4wEB6XK2zca5IgGfcBO3MF0yA13sh1JwKa54ZEikAI4T5lVfRkjUn7
     LPkwpMhw3033NyyrTFXF48i9oRSoMocJYmDOncY8Mgc+CJArvn/fT34bZ9rXH01Y
     qpeSAZv7AgyXc3jSQHZPjo76i/C9BwwZ1EoGUm4svom/0ejnOteM1Ff3grVnqipX
     Xo78a1BYHr0aLBxPpPaHMRlOdcMo0UYnqIm+P7VXtY0WxvPjXgemtSsXYrAMKSaa
     sAJ5Dv0jqYwhbQcVb5sGLC8zg+QmSbhV4HbrXmOcP8QC9H89EJSPzLQivQePGZrQ
     284vWTueNk68NyUQ5BUfXLIjYX/n6kgOeISNcvhDCVgWkvZNfN57fEOtq2FTsFKz
     Dg4ukCQkabA+lFB3AiVdhhLZT5ucjSFFfnLUkwaULRP5XEgQhH8=
     =YxhM
     -----END PGP SIGNATURE-----
     
    
    Force redirect
    

    Using git hash-object -t commit on the file, the same commit SHA is reproduced.

    Now trying to store this crafted commit object into Git database.

    Prepend with the object type header:

    ~/iBug-source $ printf 'commit %s\0' $(wc -c < commit) | cat - commit > object
    

    Get the hash of the complete object:

    ~/iBug-source $ sha1sum object
    36105785c8665a400226c54a16cc4583b8f28ebd
    

    Create directory for the object:

    ~/iBug-source $ mkdir -p .git/objects/36/
    

    Compress the object using zlib (.zz) algorithm and save the object:

    ~/iBug-source $ pigz -cz object > .git/objects/36/105785c8665a400226c54a16cc4583b8f28ebd
    

    Try if Git recognized this crafted object:

    ~/iBug-source $ git log -1 36105785c8665a400226c54a16cc4583b8f28ebd
    commit 36105785c8665a400226c54a16cc4583b8f28ebd
    Author: iBug <git@ibugone.com>
    Date:   Mon Nov 16 02:51:19 2020 +0800
    
        Force redirect