{"id":15010913,"url":"https://github.com/haskell/hackage-security","last_synced_at":"2025-05-16T06:06:56.202Z","repository":{"id":31300663,"uuid":"34862856","full_name":"haskell/hackage-security","owner":"haskell","description":"Hackage security framework based on TUF (The Update Framework)","archived":false,"fork":false,"pushed_at":"2025-05-05T10:03:07.000Z","size":1293,"stargazers_count":57,"open_issues_count":54,"forks_count":49,"subscribers_count":19,"default_branch":"master","last_synced_at":"2025-05-05T11:24:06.999Z","etag":null,"topics":["cabal","hackage","haskell","security","tuf"],"latest_commit_sha":null,"homepage":"http://hackage.haskell.org/package/hackage-security","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/haskell.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2015-04-30T15:46:57.000Z","updated_at":"2025-05-05T10:03:11.000Z","dependencies_parsed_at":"2023-01-14T19:01:05.314Z","dependency_job_id":"dfe64b1d-0eac-4420-a0c0-8f27db4f012d","html_url":"https://github.com/haskell/hackage-security","commit_stats":{"total_commits":526,"total_committers":35,"mean_commits":"15.028571428571428","dds":"0.45817490494296575","last_synced_commit":"8ada49de74456f3aebd2ae08cf11151acbf8ef05"},"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haskell%2Fhackage-security","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haskell%2Fhackage-security/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haskell%2Fhackage-security/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haskell%2Fhackage-security/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/haskell","download_url":"https://codeload.github.com/haskell/hackage-security/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254478190,"owners_count":22077676,"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":["cabal","hackage","haskell","security","tuf"],"created_at":"2024-09-24T19:37:10.932Z","updated_at":"2025-05-16T06:06:51.186Z","avatar_url":"https://github.com/haskell.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hackage Security [![Hackage version](https://img.shields.io/hackage/v/hackage-security.svg?label=Hackage)](https://hackage.haskell.org/package/hackage-security) [![Stackage version](https://www.stackage.org/package/hackage-security/badge/lts?label=Stackage)](https://www.stackage.org/package/hackage-security)\n\nThis is a library for Hackage security based on [TUF, The Update\nFramework][TUF]. In addition to the TUF website, the [blog post][blogpost] that\nannounced the framework is a good source of information.\n\n## Background Information\n\nThe Hackage security process is an implementation of [_The Update Framework_\n(TUF)][TUF], intended to stop software supply-chain attacks.  TUF provides both\nindex signing, which prevents mirrors or other middlemen from tampering with the\ncontents of a software repository, and author signing, which prevents\nrepositories from tampering with the contents of hosted packages.  Thus far,\nhowever, Hackage implements only index signing.  Additionally, Hackage differs\nsomewhat from TUF's assumptions, and thus does things a little differently.\nRather than attempting to describe the diff against TUF, this document simply\ndescribes how Hackage's security features work.\n\nAs an instance of TUF, the Hackage security process uses much of its jargon.  In\nparticular, a _role_ refers to a manner of use of a particular key.  A given key\nmight, in principle, be used in multiple roles - for instance, the same key\ncould be used to sign timestamps and mirrors.  In this context, \"the timestamp\nkey\" and \"the mirror key\" would refer to the same key used in two different\nways. Each role is assigned a _threshold_ that states how many signatures are\nexpected for a given role. For instance, in Hackage, the timestamp role requires\njust one signature, while the root role requires three signatures. Similarly,\nkeys are distinguished from key IDs, which are hashes of the key content.  With\nthe `ed25519` keys used by this process, they are the same length, so be\ncareful.\n\nHackage provides the following to build tools:\n * An index, which contains the metadata for every package\n * Packages, which contain the actual code\n * A timestamp, with a frequently-updated signature that expires regularly\n\nMalicious mirrors could attempt to interpose incorrect information into either.\nHackage cryptographically signs the index, providing evidence of its\nauthenticity, and the index itself contains hashes of each package file.  Thus,\nmirrors cannot interpose new metadata or new packages, because both are secured\nby the signature.  Additionally, these features prevent man-in-the-middle\nattacks against both Hackage and its mirrors, domain hijacking, and rollback\nattacks.  The signed timestamp ensures that clients can detect replay attacks\nthat are denying them new packages.  They do not prevent malicious or\ncompromised package authors from uploading malware to the index, nor do they\nprotect against the Hackage server itself being compromised.\n\n## Brief overview of Hackage and cabal-install\n\nHackage makes all packages available from a single directory `/package`; for\nexample, version 1.0 of package Foo is available at `/package/Foo-1.0.tar.gz`\n(see [Footnote: Paths](#paths)).\n\nAdditionally, Hackage offers a tarball, variously known as \u0026ldquo;the\nindex\u0026rdquo; or \u0026ldquo;the index tarball\u0026rdquo;, located at `/00-index.tar.gz`.\nThe index tarball contains the `.cabal` for all packages available on the\nserver; `cabal-install` downloads this file whenever you call `cabal update` to\nfigure out which packages are available, and it uses this file when you `cabal\ninstall` a package to figure out which dependencies to install. The `.cabal`\nfile for Foo-1.0 is located at `Foo/1.0/Foo.cabal` in the index. (One side goal\nof the Hackage Security project is to make downloading the index incremental.)\n\nNote that although Hackage additionally offers the (latest version of) the\n`.cabal` file at `/package/Foo-1.0/Foo.cabal`, this is never used by\n`cabal-install`.\n\nIn order to distinguish between paths on the server and paths in the index we\nwill qualify them as `\u003crepo\u003e/package/Foo-1.0.tar.gz` and\n`\u003cindex\u003e/Foo/1.0/Foo.cabal` respectively, both informally in this text and in\nformal delegation rules.\n\n## Formats and Tools\n\nThe files that are to be signed contain JSON objects that have two fields:\n`signatures` and `signed`.  The Hackage signing tools will not sign any other\nformat.  The `signatures` field is expected to be an array of signatures, while\nthe `signed` field may consist of arbitrary JSON.  When signing, the signature\nis actually applied to the [canonical\nJSON](https://gibson042.github.io/canonicaljson-spec/) rendering of the contents\nof the `signed` field.  This allows multiple signatures to be independently\ncreated and added, because new signatures do not sign the prior signatures.\n\nThere are two tools that are relevant:\n[hackage-root-tool](https://github.com/haskell/hackage-security/tree/master/hackage-root-tool)\nand\n[hackage-repo-tool](https://github.com/haskell/hackage-security/tree/master/hackage-repo-tool).\n`hackage-root-tool` is a minimal implementation of the cryptography, intended to\nbe as small as possible so that it can be audited and run on an offline machine.\n`hackage-repo-tool`, on the other hand, has a number of features for managing\nfile-based Hackage repositories in addition to signing.\n\n## Keys and Participants\n\n### The Root of Trust\n\nThe file `root.json` describes all of the keys used in Hackage Security. It\nlists the public parts of the keys, enumerates the roles used, and assigns keys\nand thresholds to each role. It is signed by keys in the root role.\n\n### Root Keys\n\nThe Hackage root keys are held by trusted members of the Haskell community. A\nsignature is valid when three keyholders have signed.  This means that the\noverall system is not vulnerable to a single key being compromised; nor can\nservice be denied by a single key being lost.  Keyholders are strongly\nencouraged to keep their keys very secure.  The current collection of\nkeyholders, plus signatures that demonstrate that they have the keys, is\navailable at https://github.com/haskell-infra/hackage-root-keys .\n\nRoot keys present a bootstrapping problem: how is a build tool to know whether\nit should trust a newly-downloaded `root.json`? To work around this, the public\npart of the root keys and the threshold policy for the root role are shipped\nwith the build tools that need to verify Hackage downloads. Once this\n`root.json` has been verified, its policies override the built-in bootstrapping\nkeys.\n\nBecause these root keys are so difficult to replace, they are not used for\noperations.  The root keys are used to delegate roles to a set of operational\nkeys, and these operational keys are used for the daily signing of indices by\nHackage.\n\n### Operational Keys\n\nThe operational keys are signed by the root keys.  Build tools have no in-built\nknowledge of them, but can instead discover them through `root.json`. The\noperational private keys are kept secure by the Hackage administrators, but\nbecause they are on an online machine, they are more vulnerable than the root\nkeys.\n\nOperational keys fulfill two roles:\n * **Snapshot keys** are used to sign the Hackage index.\n * **Timestamp keys** are used to sign the frequently-updated timestamp file.\n \n\n## Mirrors\n\nA list of authorized mirrors of Hackage is provided in a file called\n`mirrors.json`.  This list is signed by the mirror key.  The mirrors list\nexpires annually and must be re-signed by the mirror key.  Clients check that\nthe mirror key is signed by the root key, and that the mirror list is signed by\nthe mirror key, before accepting a mirror list.  According to the [TUF\nspec](https://theupdateframework.github.io/specification/latest/#mirrors),\n\n\u003e The importance of using signed mirror lists depends on the application and the users of that application. There is minimal risk to the application’s security from being tricked into contacting the wrong mirrors. This is because the framework has very little trust in repositories.\n\nThe mirror list being signed is mostly for the sake of completeness, rather than out of concern for a particular threat.\n\n\n## Comparison with TUF\n\nIn this section we highlight some of the differences in the specifics of the\nimplementation; generally we try to follow the TUF model as closely as\npossible.\n\nThis section is not (currently) intended to be complete.\n\n### Targets\n\nIn the current proposal we do not yet implement author signing, but when we do\nimplement author signing we want to have a smooth transition, and moreover we\nwant to be able to have a mixture of packages, some of which are author signed\nand some of which are not. That is, package authors must be able to opt-in to\nauthor signing (or not).\n\n#### Unsigned packages\n\n##### Package specific metadata\n\nThe package metadata files (\u0026ldquo;target files\u0026rdquo;) will be stored _in the\nindex_. The metadata for Foo 1.0 will be stored in\n`\u003cindex\u003e/Foo/1.0/package.json`, and will contain the hash and size of the\npackage tarball:\n\n``` javascript\n{ \"signed\" : {\n     \"_type\"   : \"Targets\"\n   , \"version\" : VERSION\n   , \"expires\" : never\n   , \"targets\" : { \"\u003crepo\u003e/package/Foo-1.0.tar.gz\" : FILEINFO }\n   }\n, \"signatures\" : []\n}\n```\n\nNote that expiry dates are relevant only for information that we expect to\nchange over time (such as the snapshot). Since packages are immutable, they\ncannot expire. (Additionally, there is no point adding an expiry date to files\nthat are protected only the snapshot key, as the snapshot _itself_ will expire).\n\nIt is not necessary to list the file info of the `.cabal` files here: `.cabal`\nfiles are listed by value in the index tarball, and are therefore already\nprotected by the snapshot key (but see [author signing](#author-signing)).\n\n##### Delegation\n\n[Conceptually speaking](#phase1-shortcuts) we then need a top-level target file\n`\u003cindex\u003e/targets.json` that contains the required delegation information:\n\n``` javascript\n{ \"signed\" : {\n      \"_type\"       : Targets\n    , \"version\"     : VERSION\n    , \"expires\"     : never\n    , \"targets\"     : []\n    , \"delegations\" : {\n          \"keys\"  : []\n        , \"roles\" : [\n               { \"name\"      : \"\u003cindex\u003e/$NAME/$VERSION/package.json\"\n               , \"keyids\"    : []\n               , \"threshold\" : 0\n               , \"path\"      : \"\u003crepo\u003e/package/$NAME-$VERSION.tar.gz\"\n               }\n             ]\n       }\n    }\n, \"signatures\" : /* target keys */\n}\n```\n\nThis file itself is signed by the target keys (kept offline by the Hackage\nadmins).  \n\nNote that this file uses various extensions to TUF spec:\n\n* We can use wildcards in names as well as in paths. This means that we list a\n  \u003cb\u003esingle\u003c/b\u003e path with a \u003cb\u003esingle\u003c/b\u003e replacement name. (Alternatively, we\n  could have a list of pairs of paths and names.)\n* Paths contain namespaces (`\u003crepo\u003e` versus `\u003cindex\u003e`)\n* Wildcards have more structure than TUF provides for.\n\nThe first one of these is the most important, as it has some security\nimplications; see comments below.\n\nNew unsigned packages, as well as new versions of existing unsigned packages,\ncan be uploaded to Hackage without any intervention from the Hackage admins (the\noffline target keys are not required).\n\n##### Security\n\nThis setup is sufficient to allow for untrusted mirrors: since they do not have\naccess to the snapshot key, they (or a man-in-the-middle) cannot change which\npackages are visible or change the packages themselves.\n\nHowever, since the snapshot key is stored on the server, if the server itself is\ncompromised almost all security guarantees are void.\n\n#### \u003ca name=\"author-signing\"\u003eSigned packages\u003c/a\u003e\n\nWe sketch the design here only, we do not actually intend to implement this yet\nin phase 1 of the project.\n\n##### Package specific metadata\n\nAs for unsigned packages, we keep metadata specific for each package version.\nUnlike for unsigned packages, however, we store two files: one that can be\nsigned by the package author, and one that can be signed by the Hackage\ntrustees, who can upload new `.cabal` file revisions but not change the\npackage contents.\n\nAs before we still have `\u003cindex\u003e/Foo/1.0/package.json` containing\n\n``` javascript\n{ \"signed\" : {\n     \"_type\"   : \"Targets\"\n   , \"version\" : VERSION\n   , \"expires\" : never\n   , \"targets\" : { \"\u003crepo\u003e/package/Foo-1.0.tar.gz\" : FILEINFO }\n   }\n, \"signatures\" : /* signatures from package authors */\n}\n```\n\nIt is not necessary to separately sign the `.cabal` file that is listed _inside_\nthe package `.tar.gz` file. However, this `.cabal` file may not match the one in\nthe index, either because a Hackage trustee uploaded a revision, or because of\nan malicious attempt to fool the solver in installing different dependencies\nthan intended.\n\nTherefore, unlike for unsigned packages, listing the file info for the `.cabal`\nfile in the index is useful for signed packages: although the `.cabal` files are\nlisted by value in the index tarball, the index is only signed by the snapshot\nkey. We may want to additionally check that the `.cabal` are properly author\nsigned too. We record this in a different file `\u003cindex\u003e/Foo/1.0/revisions.json`,\nwhich can be signed by either the package authors or the Hackage trustees.\n\n``` javascript\n{ \"signed\" : {\n     \"_type\"   : \"Targets\"\n   , \"version\" : VERSION\n   , \"expires\" : never\n   , \"targets\" : { \"\u003cindex\u003e/Foo/1.0/Foo.cabal\" : FILEINFO }\n   }\n, \"signatures\" : /* signatures from package authors or Hackage trustees */\n}\n```\n\n##### Delegation\n\nDelegation for signed packages is a bit more complicated. We extend the\ntop-level targets file to\n\n``` javascript\n{ \"signed\" : {\n      \"_type\"       : Targets\n    , \"version\"     : VERSION\n    , \"expires\"     : EXPIRES\n    , \"targets\"     : []\n    , \"delegations\" : {\n          \"keys\"  : /* Hackage trustee keys */\n        , \"roles\" : [\n               // Delegation for unsigned packages\n               { \"name\"      : \"\u003cindex\u003e/$NAME/$VERSION/package.json\"\n               , \"keyids\"    : []\n               , \"threshold\" : 0\n               , \"path\"      : \"\u003crepo\u003e/package/$NAME-$VERSION.tar.gz\"\n               }\n             // Delegation for package Bar\n             , { \"name\"      : \"\u003cindex\u003e/Bar/authors.json\"\n               , \"keyids\"    : /* top-level target keys */\n               , \"threshold\" : THRESHOLD\n               , \"path\"      : \"\u003crepo\u003e/package/Bar-$VERSION.tar.gz\"\n               }\n             , { \"name\"      : \"\u003cindex\u003e/Bar/authors.json\"\n               , \"keyids\"    : /* top-level target keys */\n               , \"threshold\" : THRESHOLD\n               , \"path\"      : \"\u003cindex\u003e/Bar/$VERSION/Bar.cabal\"\n               }\n             , { \"name\"      : \"\u003cindex\u003e/Bar/$VERSION/revisions.json\"\n               , \"keyids\"    : /* Hackage trustee key IDs  */\n               , \"threshold\" : THRESHOLD\n               , \"path\"      : \"\u003cindex\u003e/Bar/$VERSION/Bar.cabal\"\n               }\n             // .. delegation for other signed packages ..\n             ]\n        }\n    }\n, \"signatures\" : /* target keys */\n}\n```\n\nSince this lists all signed packages, we must list an expiry date here so that\nattackers cannot mount freeze attacks (although this is somewhat less of an\nissue here as freezing this list would make an entire new package, rather than\na new package version, invisible).\n\nThis \u0026ldquo;middle level\u0026rdquo; targets file `\u003cindex\u003e/Bar/authors.json`\nintroduces the package author/maintainer keys and contains further delegation\ninformation:\n\n``` javascript\n{ \"signed\" : {\n      \"_type\"       : \"Targets\"\n    , \"version\"     : VERSION\n    , \"expires\"     : never\n    , \"targets\"     : {}\n    , \"delegations\" : {\n          \"keys\"  : /* package maintainer keys */\n        , \"roles\" : [\n              { \"name\"      : \"\u003cindex\u003e/Bar/$VERSION/package.json\"\n              , \"keyids\"    : /* package maintainer key IDs */\n              , \"threshold\" : THRESHOLD\n              , \"path\"      : \"\u003crepo\u003e/Bar/$VERSION/Bar-$VERSION.tar.gz\"\n              }\n            , { \"name\"      : \"\u003cindex\u003e/Bar/$VERSION/revisions.json\"\n              , \"keyids\"    : /* package maintainer key IDs */\n              , \"threshold\" : THRESHOLD\n              , \"path\"      : \"\u003cindex\u003e/Bar/$VERSION/Bar.cabal\"\n              }\n            ]\n    }\n, \"signatures\" : /* signatures from top-level target keys */\n}\n```\n\nSome notes:\n\n1. When a new signed package is introduced, the Hackage admins need to create\n   and sign a new `targets.json` that lists the package author keys and\n   appropriate delegation information, as well as add corresponding entries to\n   the top-level delegation file. However, once this is done, the Hackage admins\n   do not need to be involved when package authors wish to upload new versions.\n\n2. When package authors upload a new version, they need to sign only a single\n   file that contains the information about that particular version.\n\n3. Both package authors (through the package-specific \u0026ldquo;middle level\u0026rdquo;\n   delegation information) and Hackage trustees (through the top-level\n   delegation information) can sign `.cabal` file revisions, but only authors\n   can sign the packages themselves.\n\n4. Hackage trustees are listed only in the top-level delegation information, so\n   when the set of trustees changes we only need to modify one file (as opposed\n   to each middle-level package delegation information).\n\n5. For signed packages that do not want to allow Hackage trustees to sign\n   `.cabal` file revisions we can just omit the corresponding entry from the\n   top-level delegations file.\n\n6. There are two kinds of rule overlaps in these delegation rules:\n   `\u003crepo\u003e/package/Bar-1.0.tar.gz` will match against the rule for unsigned\n   packages (`\u003crepo\u003e/package/$NAME-$VERSION.tar.gz`) and against the rule for\n   signed packages (`\u003crepo\u003e/package/Bar-$VERSION.tar.gz`). It is important\n   here that the signed rule take precedence, because author signed packages\n   _must_ be author signed. The priority scheme can be simple: more specific\n   rules should take precedence (the TUF specification leaves the priority\n   scheme used open).\n\n   The second kind of overlap occurs between the rule\n   for the `.cabal` file in the the top-level `targets.json` and the\n   corresponding rule in `authors.json`; in this case both rules match against\n   precisely the same path (`\u003cindex\u003e/Bar/$VERSION/Bar.cabal`), and indeed in\n   this case there is no priority: as long as either rule matches\n   (that is, the file is either signed by a package author or by a Hackage\n   trustee) we're okay.\n\n##### Transition packages from `unsigned` to `signed`\n\nWhen a package that previously did not opt-in to author signing now wants\nauthor-signing, we just need to add the appropriate entries to the top-level\ndelegation file and set up the appropriate middle-level delegation information.\n\n##### Security\n\nWhen the snapshot key is compromised, attackers still do not have access to\npackage author keys, which are strictly kept offline. However, they can still\nmount freeze attacks on packages versions, because there is no file (which is\nsigned with offline key) listing which versions are available.\n\nWe could increase security here by changing the middle-level `authors.json` to\nremove the wildcard rule, list all versions explicitly, and change the top-level\ndelegation information to say that the middle-level file should be signed by\nthe package authors instead.\n\nNote that we do not use a wildcard for signed packages in the top-level\n`targets.json` for a similar reason: by listing all packages that we expect to\nbe signed explicitly, we have a list of signed packages which is signed by\noffline keys (in this case, the target keys).\n\n### Snapshot\n\n#### Interaction with the index tarball\n\nAccording to the official specification we should have a file `snapshot.json`,\nsigned with the snapshot key, which lists the hashes of _all_ metadata files in\nthe repo. In Hackage however we have the index tarball, which _contains_ most of\nthe metadata files in the repo (that is, it contains all the `.cabal` files, but\nit also contains all the various `.json` files). The only thing that is missing\nfrom the index tarball, compared to the `snapshot.json` file from the TUF spec,\nis the version, expiry time, and signatures. Therefore our `snapshot.json` looks\nlike\n\n``` javascript\n{ \"signed\" : {\n      \"_type\"   : \"Snapshot\"\n    , \"version\" : VERSION\n    , \"expires\" : EXPIRES\n    , \"meta\"    : {\n           \"root.json\"    : FILEINFO\n         , \"mirrors.json\" : FILEINFO\n         , \"index.tar\"    : FILEINFO\n         , \"index.tar.gz\" : FILEINFO\n        }\n    }\n, \"signatures\" : /* signatures from snapshot key */\n}\n```\n\nThen the combination of `snapshot.json` together with the index tarball is\na strict superset of the information in TUF's `snapshot.json` (instead of\ncontaining the hashes of the metadata in the repo, it contains the actual\nmetadata themselves).\n\nWe list the file info of the root and mirrors metadata explicitly, rather than\nrecording it in the index tarball, so that we can check them for updates during\nthe update process  (section 5.1, \u0026ldquo;The Client Application\u0026rdquo;, of the\nTUF spec) without downloading the entire index tarball.\n\n### Out-of-tarball targets\n\nAll versions of all packages are listed, along with their full `.cabal` file, in\nthe Hackage index. This is useful because the constraint solver needs most of\nthis information anyway. However, when we add additional kinds of targets we may\nnot wish to add these to the index: people who are not interested in these new\ntargets should not, or only minimally, be affected. In particular, new releases\nof these new kinds of targets should not result in a linear increase in what\nclients who are not interested in these targets need to download whenever they\ncall `cabal install`.\n\nTo support these out-of-tarball targets we can use the regular TUF setup. Since\nthe index does not serve as an exhaustive list of which targets (and which\ntarget versions) are available, it becomes important to have target metadata\nthat list all targets exhaustively, to avoid freeze attacks. The file\ninformation (hash and filesize) of all these target metadata files must, by the\nTUF spec, be listed in the top-level snapshot; we should thus avoid introducing\ntoo many of them (in particular, we should avoid requiring a new metadata file\nfor each new version of a particular kind of target).\n\nIt is important to store OOT targets under a different prefix than `/package` to\navoid name clashes.\n\n#### Collections\n\n[Package collections][CabalHell1] are a new Hackage feature that's [currently in\ndevelopment][ZuriHac]. We want package collections to be signed, just like\nanything else.\n\nLike packages, collections are versioned and immutable, so we have\n\n```\ncollection/StackageNightly-2015.06.02.collection\ncollection/StackageNightly-2015.06.03.collection\ncollection/StackageNightly-...\ncollection/DebianJessie-...\ncollection/...\n```\n\nAs for packages, collections should be able to opt-in for author signing (once\nwe support author signing), but we should also support not-author-signed\n(\u0026ldquo;unsigned\u0026rdquo;) collections. Moreover, it should be possible for people\nto create new unsigned collections without the involvement of the Hackage\nadmins. This rules out listing all collections explicitly in the top-level\n`targets.json` (which is signed with offline target keys).\n\nBelow we sketch a possible design (we may want to tweak this further).\n\n##### Unsigned collections\n\nFor unsigned collections we add a single delegation rule to the top-level\n`targets.json`:\n\n``` javascript\n{ \"name\"      : \"\u003crepo\u003e/collection/collections.json\"\n, \"keyids\"    : /* snapshot key */\n, \"threshold\" : 1\n, \"path\"      : \"\u003crepo\u003e/collection/$NAME-$VERSION.collection\"\n}\n```\n\nThe middle-level `collections.json`, signed with the snapshot role, lists\ndelegation rules for all available collections:\n\n``` javascript\n[ { \"name\"      : \"\u003crepo\u003e/collection/StackageNightly.json\"\n  , \"keyids\"    : /* snapshot key */\n  , \"threshold\" : 1\n  , \"path\"      : \"\u003crepo\u003e/collection/StackageNightly-$VERSION.collection\"\n  }\n, { \"name\"      : \"\u003crepo\u003e/collection/DebianJessie.json\"\n  , \"keyids\"    : /* snapshot key */\n  , \"threshold\" : 1\n  , \"path\"      : \"\u003crepo\u003e/collection/DebianJessie-$VERSION.collection\"\n  }\n, ...\n]\n```\n\nwhere `StackageNightly` and `DebianJessie` are two package collection (the\n[Stackage Nightly][StackageNightly] collection and the set of Haskell package\ndistributed with the [Debian Jessie][DebianJessie] Linux distribution).\n\nThe final per-collection targets metadata finally lists all versions:\n\n``` javascript\n{ \"signed\" : {\n     \"_type\"   : \"Targets\"\n   , \"version\" : VERSION\n   , \"expires\" : /* expiry */\n   , \"targets\" : {\n         \"\u003crepo\u003e/collection/StackageNightly-2015.06.02.collection\" : FILEINFO\n       , \"\u003crepo\u003e/collection/StackageNightly-2015.06.03.collection\" : FILEINFO\n       , ...\n       }\n   }\n, \"signatures\" : /* signed with snapshot role */\n}\n```\n\nSince we cannot rely on the index to have the list of all version we must list\nall versions explicitly here rather than using wildcards. Note that this means\nthat the snapshot will get a new entry for each new collection introduced, but\nnot for each new _version_ of each collection.\n\n##### Author-signed collections\n\nFor author-signed collections we only need to make a single change. Suppose that\nthe `DebianJessie` collection is signed. Then we move the rule for\n`DebianJessie` from `collection/collections.json` and instead list it in the\ntop-level `targets.json` (as for packages, introducing a signed collection\nnecessarily requires the involvement of the Hackage admins):\n\n``` javascript\n{ \"name\"      : \"\u003crepo\u003e/collection/DebianJessie.json\"\n, \"keyids\"    : /* DebianJessie maintainer keys */\n, \"threshold\" : /* threshold */\n, \"path\"      : \"\u003crepo\u003e/collection/DebianJessie-$VERSION.collection\"\n}\n```\n\nNo other changes are required (apart from of course that\n`\u003crepo\u003e/collection/DebianJessie.json` will now be signed with the\n`DebianJessie` maintainer keys rather than the snapshot key). As for packages,\nthis requires a priority scheme for delegation rules.\n\n(One difference between this scheme and the scheme we use for packages is that\nthis means that the top-level `targets.json` file lists all keys for all\ncollection authors. If we want to avoid that we need a further level of\nindirection.)\n\nNote that in a sense author-signed collections are snapshots of the server. As\nsuch, it would be good if these collections listed the file info (hashes and\nfilesizes) of the packages the collection.\n\n## Project phases and shortcuts\n\nPhase 1 of the project will implement the basic TUF framework, but leave out\nauthor signing; support for author signed packages (and other targets) will\nadded in phase 2.\n\n### \u003ca name=\"phase1-shortcuts\"\u003eShortcuts taken in phase 1 (aka TODOs for phase 2)\u003c/a\u003e\n\nThis list is currenty not exhaustive.\n\n#### Core library\n\n* Although the infrastructure is in place for [target metadata][Targetshs],\n  including typed data types representing [pattern matches and\n  replacements][Patternshs], we have not yet actually implementing target\n  delegation proper. We don't need to: we only support one kind of target\n  (not-author-signed packages), and we know statically where the target\n  information for packages can be found (in `/package/version/targets.json`).\n  This is currently hardcoded.  \n\n  Once we have author signing we need a proper implementation of delegation,\n  including a priority scheme between rules. This will also involve lazily\n  downloading additional target metadata.\n\n* Out-of-tarballs targets are not yet implemented. The main difficulty here is\n  that they require a proper implementation of delegation; once that is done\n  (required anyway for author signing) support for OOT targets should be\n  straightforward.\n\n#### Integration in `cabal-install`\n\n* The cabal integration uses the `hackage-security` library to check for updates\n  (that is, update the local copy of the index) and to download packages\n  (verifying the downloaded file against the file info that was recorded in\n  the package metadata, which itself is stored in the index). However, it does\n  not use the library to get a list of available packages, nor to access\n  `.cabal` files.\n\n  Once we have author signing however we may want to do additional checks:\n\n  * We should look at the top-level `targets.json` file (in addition to the\n    index) to figure out which packages are available. (The top-level targets\n    file, signed by offline keys, will enumerate all author-signed packages.)\n\n  * If we do allow package authors to sign list of package versions (as detailed\n    above) we should use these \u0026ldquo;middle level\u0026rdquo; target files to figure\n    out which versions are available for these packages.\n\n  * We might want to verify the `.cabal` files to make sure that they match the\n    file info listed in the now author-signed metadata.\n\n  Therefore `cabal-install` should be modified to go through the\n  `hackage-security`  library get the list of available packages, package\n  versions, and to access the actual `.cabal` files.\n\n## Open questions / TODOs\n\n* The set of maintainers of a package can change over time, and can even change\n  so much that the old maintainers of a package are no longer maintainters.\n  But we would still like to be able to install and verify old packages. How\n  do we deal with this?\n\n## Ongoing Maintenance\n\n### Mirror Keys: Every Year\n\nThe mirror list requires annual resigning by a holder of a mirror key.\nTo do this, use the following steps:\n\n 1. Install `hackage-root-tool` on the signing machine and ensure that the key is present.\n 2. Create a new `mirrors.json` file by incrementing the version field of the existing file and adding a year to the expiration date. Delete the signature(s), replacing them with an empty array. Place the file on the signing machine.\n 3. Sign the file using `hackage-root-tool sign KEY mirrors.json`, and place the resulting signature array into the `signatures` field of `mirrors.json`.\n 4. Commit the updated file to `https://github.com/haskell-infra/hackage-root-keys` and inform the Hackage admins so they can install it.\n \n\n### Root Data: Every Other Year\n\nThe holders of the root keys are, each year, signing the root information file `root.json` that directs clients to the operational and mirror keys.\nThe keyholders are attesting that they believe that the root and operational keys are not compromised, that Hackage is still under the control of trusted administrators, and that everything is working about the way it usually does.\n\nToday, there are five active root keys, because three of the original eight never completed the setup process.\n\nTo prepare the updated `roots.json` for signing by the root keyholders, a coordinator should perform the following edits:\n 1. If any new keys are to be admitted, collect their key IDs and add them to the `keys` field.\n 2. Modify the expiration date.\n 3. Increment the `version` field.\n 4. Delete the existing signatures.\n\nEach holder of root keys should do the following:\n 1. Verify that the contents of `root.json` are correct. Any changes to the set of keys and their roles or to the threshold policies should be trustworthy, and any new keyholders need to have their identity verified. Please be a stickler for details here.\n 2. Install `hackage-root-tool` on the signing machine and ensure that the key is present.\n 3. Place the updated `roots.json` file on the signing machine.\n 4. Sign the file using `hackage-root-tool sign KEY roots.json`, and send the resulting signature back to the person who is coordinating the signing.\n \nFinally, the coordinator should insert the provided signatures and commit the updated file to `https://github.com/haskell-infra/hackage-root-keys` and inform the Hackage admins so they can install it.\n\n\n### Operational Keys\n\nThe operational keys do not presently require regeneration, unless the private keys have been lost or compromised.\n\n\n## \u003ca name=\"paths\"\u003eFootnotes\u003c/a\u003e\n\n### Footnote: Paths\n\nThe situation with paths in cabal/hackage is a bit of a mess. In this footnote\nwe describe the situation before the work on the Hackage Security library.\n\n#### The index tarball\n\nThe index tarball contains paths of the form\n\n```\n\u003cpackage-name\u003e/\u003cpackage-version\u003e/\u003cpackage-name\u003e.cabal\n```\n\nFor example:\n\n```\nmtl/1.0/mtl.cabal\n```\n\nas well as a single top-level `preferred-versions` file.\n\n#### Package resources offered by Hackage\n\nHackage offers a number of resources for package tarballs: one\n\u0026ldquo;official\u0026rdquo; one and a few redirects.\n\n1.  The official location of package tarballs on a Hackage server is\n\n    ```\n    /package/\u003cpackage-id\u003e/\u003cpackage-id\u003e.tar.gz\n    ```\n\n    for example\n\n    ```\n    /package/mtl-2.2.1/mtl-2.2.1.tar.gz\n    ```\n\n    (This was the official location from [very early on][63a8c728]).\n\n2.  It [provides a redirect][3cfe4de] for\n\n    ```\n    /package/\u003cpackage-id\u003e.tar.gz\n    ```\n\n    for example\n\n    ```\n    /package/mtl-2.2.1.tar.gz\n    ```\n\n    (that is, a request for `/package/mtl-2.2.1.tar.gz` will get a 301 Moved\n    Permanently response, and is redirected to\n    `/package/mtl-2.2.1/mtl-2.2.1.tar.gz`).\n\n3.  It provides a redirect for Hackage-1 style URLs of the form\n\n    ```\n    /packages/archive/\u003cpackage-name\u003e/\u003cpackage-version\u003e/\u003cpackage-id\u003e.tar.gz\n    ```\n\n    for example\n\n    ```\n    /packages/archive/mtl/2.2.1/mtl-2.2.1.tar.gz\n    ```\n\n#### Locations used by cabal-install to find packages\n\nThere are two kinds of repositories supported by `cabal-install`: local and\nremote.\n\n1.  For a local repository `cabal-install` looks for packages at\n\n    ```\n    \u003clocal-dir\u003e/\u003cpackage-name\u003e/\u003cpackage-version\u003e/\u003cpackage-id\u003e.tar.gz\n    ```\n\n2.  For remote repositories however `cabal-install` looks for packages in one of\n    two locations.\n\n    a.  If the remote repository (`\u003crepo\u003e`) is\n        `http://hackage.haskell.org/packages/archive` (this value is hardcoded)\n        then it looks for the package at\n\n        \u003crepo\u003e/\u003cpackage-name\u003e/\u003cpackage-version\u003e/\u003cpackage-id\u003e.tar.gz\n\n    b.  For any other repository it looks for the package at\n\n        \u003crepo\u003e/package/\u003cpackage-id\u003e.tar.gz\n\nSome notes:\n\n1.  Files downloaded from a remote repository are cached locally as\n\n    ```\n    \u003ccache\u003e/\u003cpackage-name\u003e/\u003cpackage-version\u003e/\u003cpackage-id\u003e.tar.gz\n    ```\n\n    I.e., the layout of the local cache matches the layout of a local\n    repository (and matches the structure of the index tarball too).\n\n2.  Somewhat bizarrely, when `cabal-install` creates a new initial `config`\n    file it uses `http://hackage.haskell.org/packages/archive` as the repo base\n    URI (even in newer versions of `cabal-install`; this was [changed only very\n    recently][bfeb01f]).\n\n3.  However, notice that _even when we give `cabal` a \u0026ldquo;new-style\u0026rdquo;\n    URI_ the address used by `cabal` _still_ causes a redirect (from\n    `/package/\u003cpackage-id\u003e.tar.gz` to\n    `/package/\u003cpackage-id\u003e/\u003cpackage-id\u003e.tar.gz`).\n\nThe most important observation however is the following: **It is not possible to\nserve a local repository as a remote repository** (by pointing a web server at a\nlocal repository) because the layouts are completely different. (Note that the\nlocation of packages on Hackage-1 _did_ match the layout of local repositories,\nbut that doesn't help because the _only_ repository that `cabal-install` will\nregard as a Hackage-1 repository is one hosted on `hackage.haskell.org`).\n\n[TUF]: http://theupdateframework.com/\n[CabalHell1]: http://www.well-typed.com/blog/2014/09/how-we-might-abolish-cabal-hell-part-1/\n[ZuriHac]: http://www.well-typed.com/blog/2015/06/cabal-hackage-hacking-at-zurihac/\n[StackageNightly]: http://www.stackage.org/\n[DebianJessie]: https://wiki.debian.org/DebianJessie\n[Targetshs]: https://github.com/well-typed/hackage-security/blob/master/hackage-security/src/Hackage/Security/TUF/Targets.hs\n[Patternshs]: https://github.com/well-typed/hackage-security/blob/master/hackage-security/src/Hackage/Security/TUF/Patterns.hs\n[bfeb01f]: https://github.com/haskell/cabal/commit/bfeb01f\n[63a8c728]: https://github.com/haskell/hackage-server/commit/63a8c728\n[3cfe4de]: https://github.com/haskell/hackage-server/commit/3cfe4de\n[blogpost]: https://www.well-typed.com/blog/2015/07/hackage-security-alpha/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaskell%2Fhackage-security","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhaskell%2Fhackage-security","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaskell%2Fhackage-security/lists"}