{"id":16612635,"url":"https://github.com/cgwalters/git-evtag","last_synced_at":"2025-09-20T14:32:19.536Z","repository":{"id":34125963,"uuid":"37957708","full_name":"cgwalters/git-evtag","owner":"cgwalters","description":"Extended verification for git tags","archived":false,"fork":false,"pushed_at":"2022-11-21T14:52:35.000Z","size":180,"stargazers_count":132,"open_issues_count":12,"forks_count":13,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-01-11T18:47:04.477Z","etag":null,"topics":["git","gpg-signature","sha1"],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cgwalters.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-06-24T02:34:47.000Z","updated_at":"2024-11-15T23:32:47.000Z","dependencies_parsed_at":"2023-01-15T04:45:50.385Z","dependency_job_id":null,"html_url":"https://github.com/cgwalters/git-evtag","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgwalters%2Fgit-evtag","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgwalters%2Fgit-evtag/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgwalters%2Fgit-evtag/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgwalters%2Fgit-evtag/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cgwalters","download_url":"https://codeload.github.com/cgwalters/git-evtag/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233670645,"owners_count":18711696,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["git","gpg-signature","sha1"],"created_at":"2024-10-12T01:43:03.471Z","updated_at":"2025-09-20T14:32:14.276Z","avatar_url":"https://github.com/cgwalters.png","language":"C","funding_links":[],"categories":["git"],"sub_categories":[],"readme":"# git-evtag\n\n`git-evtag` can be used as a replacement for `git-tag -s`.  It\nwill generate a strong checksum (called `Git-EVTag-v0-SHA512`) over the\ncommit, tree, and blobs it references (and recursively over submodules).\nA primary rationale for this is that the underlying SHA1 algorithm of\ngit is [under increasing threat](https://marc.info/?l=git\u0026m=148786876821543\u0026w=2).\nFurther, a goal here is to create a checksum that covers the entire source of\na single revision as a replacement for tarballs + checksums.\n\ngit-evtag was originally discussed (long before the SHA1 collision of February 2017)\non the git mailing list:\n\n - [marc.info](https://marc.info/?l=git\u0026m=142513489318999\u0026w=3)\n - [nabble.com](http://git.661346.n2.nabble.com/weaning-distributions-off-tarballs-extended-verification-of-git-tags-td7626117.html)\n\n### Getting git-evtag\n\nSee also the [the Node.js implementation](https://github.com/indutny/git-secure-tag).\n\n - [Fedora package](https://src.fedoraproject.org/rpms/git-evtag)\n - Building from source: Requires glib2 and libgit2.\n\n### Using git-evtag\n\nCreate a new `v2015.10` tag, covering the `HEAD` revision with GPG\nsignature and `Git-EVTag-v0-SHA512`:\n\n```\n$ git-evtag sign v2015.10\n ( type your tag message, note a Git-EVTag-v0-SHA512 line in the message )\n$ git show v2015.10\n ( Note signature covered by GPG signature )\n```\n\nVerify a tag:\n\n```\n$ git-evtag verify v2015.10\ngpg: Signature made Sun 28 Jun 2015 10:49:11 AM EDT\ngpg:                using RSA key 0xDC45FD5921C13F0B\ngpg: Good signature from \"Colin Walters \u003cwalters@redhat.com\u003e\" [ultimate]\ngpg:                 aka \"Colin Walters \u003cwalters@verbum.org\u003e\" [ultimate]\nPrimary key fingerprint: 1CEC 7A9D F7DA 85AB EF84  3DC0 A866 D7CC AE08 7291\n     Subkey fingerprint: AB92 8A9C F8DD 0629 09C3  7BBD DC45 FD59 21C1 3F0B\nSuccessfully verified: Git-EVTag-v0-SHA512: b05f10f9adb0eff352d90938588834508d33fdfcedbcfc332999ee397efa321d1f49a539f1b82f024111a281c1f441002e7f536b06eb04d41857b01636f6f268\n```\n\n### Replacing tarballs - i.e. be the primary artifact\n\nThis is similar to what project distributors often accomplish by using\n`git archive`, or `make dist`, or similar tools to generate a tarball,\nand then checksumming that, and (ideally) providing a GPG signature\ncovering it.\n\n### Tarball reproducibility\n\nThe problem with `git archive` and `make dist` is that tarballs (and\nother tools like zip files) are not easily reproducible *exactly* from\na git repository commit.  The authors of git reserve the right to\nchange the file format output by `git archive` in the future.  Also,\nthere are a variety of reasons why compressors like `gzip` and `xz`\naren't necessarily reproducible, such as compression levels, included\ntimestamps, optimizations in the algorithm, etc.  See\n[Pristine tar](https://salsa.debian.org/debian/pristine-tar)\nfor some examples of the difficulties involved (e.g. trying to\nretroactively guess the compression level arguments from the xz\ndictionary size).\n\nIf the checksum is not reproducible, it becomes much more difficult to\neasily and reliably verify that a generated tarball contains the same\nsource code as a particular git commit.\n\nWhat `git-evtag` implements is an algorithm for providing a strong\nchecksum over the complete source objects for the target commit (+\ntrees + blobs + submodules).  Then it's integrated with GPG for\nend-to-end verification.  (Although, one could also wrap the checksum\nin X.509 or some other public/private signature solution).\n\nThen no out of band distribution mechanism is necessary, and better,\nthe checksums strengthen the ability to verify integrity of the git\nrepository.\n\n(And if you want to avoid downloading the entire history, that's what\n`git clone --depth=1` is for.)\n\n### Git and SHA1\n\nNEW!  The first SHA1 collision was announced February 23, 2017:\n\n - https://security.googleblog.com/2017/02/announcing-first-sha1-collision.html\n - http://shattered.io/\n\nGit uses a modified Merkle tree with SHA1, which means that if an\nattacker managed to create a SHA1 collision for a source file object\n(git blob), it would affect *all* revisions and checkouts -\ninvalidating the security of *all* GPG signed tags whose commits point\nto that object.\n\nNow, the author of this tool believes that *today*, GPG signed git\ntags are fairly secure, especially if one is careful to ensure\ntransport integrity (e.g. pinned TLS certificates from the origin).\n\n### The Git-EVTag algorithm (v0)\n\nThere is currently only one version of the `Git-EVTag` algorithm,\ncalled `v0` - and it only supports\n[SHA-512](https://en.wikipedia.org/wiki/SHA-2).  It is declared\nstable.  All further text refers to this version of the algorithm.  In\nthe unlikely event that it is necessary to introduce a new version,\nthis tool will support all known versions.\n\n`Git-EVTag-v0-SHA512` covers the complete contents of all objects for\na commit; again similar to checksumming `git archive`, except\nreproducible.  Each object is added to the checksum in its raw\ncanonicalized form, including the header.\n\nFor a given commit (in Rust-style pseudocode):\n\n```rust\nfn git_evtag(repo: GitRepo, commitid: String) -\u003e SHA512 {\n    let checksum = new SHA512();\n    walk_commit(repo, checksum, commitid)\n    return checksum\n}\n\nfn walk_commit(repo: GitRepo, checksum : SHA512, commitid : String) {\n    checksum_object(repo, checksum, commitid)\n    let treeid = repo.load_commit(commitid).treeid();\n    walk(repo, checksum, treeid)\n}\n\nfn checksum_object(repo: GitRepo, checksum: SHA512, objid: String) -\u003e () {\n    // This is the canonical header of the object; \u003ctypename\u003e \u003clength (ascii base 10)\u003e\n    // https://git-scm.com/book/en/v2/Git-Internals-Git-Objects#Object-Storage\n    let header : \u0026str = repo.load_object_header(objid);\n    // The NUL byte after the header, explicitly included in the checksum\n    let nul = [0u8];\n    // The remaining raw content of the object as a byte array\n    let body : \u0026[u8] = repo.load_object_body(objid);\n    \n    checksum.update(header.as_bytes())\n    checksum.update(\u0026nul);\n    checksum.update(body)\n}\n\nfn walk(repo: GitRepo, checksum: SHA512, treeid: String) -\u003e () {\n    // First, add the tree object itself\n    checksum_object(repo, checksum, treeid);\n    let tree = repo.load_tree(treeid);\n    for child in tree.children() {\n        match childtype {\n            Blob(blobid) =\u003e checksum_object(repo, checksum, blobid),\n            Tree(child_treeid) =\u003e walk(repo, checksum, child_treeid),\n            Commit(commitid, path) =\u003e {\n                let child_repo = repo.get_submodule(path)\n                walk_commit(child_repo, checksum, commitid)\n            }\n        }\n    }\n}\n```\n\nThis strong checksum, can be verified reproducibly offline after\ncloning a git repository for a particular tag.  When covered by a GPG\nsignature, it provides a strong end-to-end integrity guarantee.\n\nIt's quite inexpensive and practical to compute `Git-EVTag-v0-SHA512`\nonce per tag/release creation.  At the time of this writing, on the\nLinux kernel (a large project by most standards), it takes about 5\nseconds to compute on this author's laptop.  On most smaller projects,\nit's completely negligible.\n\n### Aside: other aspects of tarballs\n\nThis project is just addressing one small part of the larger\ngit/tarball question.  Anything else is out of scope, but a brief\ndiscussion of other aspects is included below.\n\nHistorically, many projects include additional content in tarballs.\nFor example, the GNU Autotools pregenerate a `configure` script from\n`configure.ac` and the like.  Other projects don't include\ntranslations in git, but merge them out of band when generating\ntarballs.\n\nThere are many other things like this, and they all harm\nreproducibility and continuous integration/delivery.\n\nFor example, while many of my projects use Autotools, I simply have\ndownstream authors run `autogen.sh`.  It works just fine - the\nautotools are no longer changing often, and many downstreams want to\ndo it anyways.\n\nFor the translation issue, note that bad translations can actually\ncrash one's application.  If they're part of the git repository, they\ncan be more easily tested as a unit continuously.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcgwalters%2Fgit-evtag","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcgwalters%2Fgit-evtag","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcgwalters%2Fgit-evtag/lists"}