From 8c9c212176606ffa7ee1bdcb186bfc0f40e6f283 Mon Sep 17 00:00:00 2001 From: Justin Cappos Date: Thu, 3 Oct 2019 16:09:18 -0400 Subject: [PATCH 001/122] Fixing link to [81] --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index a5067b51a52..a89de53f460 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -856,7 +856,7 @@ indicates an attack. As for attacks that serve different versions of metadata, or freeze a version of a package at a specific version, they can be handled by TUF with techniques -like implicit key revocation and metadata mismatch detection [81]. +like implicit key revocation and metadata mismatch detection [2]. Appendix A: Repository Attacks Prevented by TUF From 2fa11b31cc421dac606aed2b7a4e96ceec3ba781 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Fri, 4 Oct 2019 11:29:00 -0400 Subject: [PATCH 002/122] clarified that filenames can be relative or absolute --- pep-0458.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index a5067b51a52..63c6511885f 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -305,7 +305,9 @@ latest *snapshot* and can signify when a new snapshot of the repository is available. The *snapshot* role indicates the latest version of all the TUF metadata files (other than *timestamp*). The *targets* role lists the available target files (in our case, it will be all files on PyPI under the -/simple and /packages directories). Each top-level role will serve its +/simple and /packages directories). These target files do not need to be +URIs or relative files on the same repository as long as they can be accessed +by anyone performing an update. Each top-level role will serve its responsibilities without exception. Figure 1 provides a table of the roles used in TUF. From d656e3c9c1418f1c3606033f4549b8c39e2491f3 Mon Sep 17 00:00:00 2001 From: Justin Cappos Date: Fri, 4 Oct 2019 11:37:08 -0400 Subject: [PATCH 003/122] Addressing the frequency of updates issue... --- pep-0458.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index a89de53f460..dd7f87a595a 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -618,7 +618,11 @@ The project transaction and snapshot processes SHOULD work concurrently. Finally, project transaction processes SHOULD keep in memory the latest *bins* metadata so that they will be correctly updated in new consistent snapshots. -All project transactions MAY be placed in a single queue and processed +Signing updated snapshot, timestamp, and bin metadata needs to be done on each +update. Fortunately, the actual operation of signing is fast enough that this +may be done a thousand or more times per second. However, locking must be +used so that project transactions are handled sequentially. To achieve this, +all project transactions MAY be placed in a single queue and processed serially. Alternatively, the queue MAY be processed concurrently in order of appearance, provided that the following rules are observed: From 4296723f00802d1c19233969966517856270b4df Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Fri, 4 Oct 2019 15:45:52 -0400 Subject: [PATCH 004/122] talk about managing online keys --- pep-0458.txt | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index dd7f87a595a..7b428af5429 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -509,11 +509,43 @@ discarded and more offline keys will not help resist key recovery attacks [21]_ unless diversity of keys is maintained. -Online and Offline Keys Recommended for Each Role -------------------------------------------------- +Managing online keys +--------------------- -In order to support continuous delivery, the *timestamp*, *snapshot*, *bins* -role keys MUST be online. +In order to support continuous delivery, the keys for the *timestamp*, +*snapshot*, all *bin-n* roles MUST be online. There is little benefit in +requiring all of these roles to use different online keys, since attackers +would presumably be able to compromise all of them if they compromise PyPI. +Therefore, it is reasonable to use one online key for them all. + +This online key MAY be stored, encrypted or not, on the Python infrastructure. +For example, the kept MAY be kept on a self-hosted key management service +(e.g. Hashicorp Vault__), or a third-party one (e.g. AWS KMS__, Google +Cloud KMS__, or Azure Key Vault__). + +__ https://www.vaultproject.io/ +__ https://aws.amazon.com/kms/ +__ https://cloud.google.com/kms/ +__ https://docs.microsoft.com/en-us/azure/key-vault/basic-concepts + +Some of these key management services allow keys to be stored on Hardware +Security Modules (HSMs) (e.g., Hashicorp Vault__, AWS CloudHSM__, Google +Cloud HSM__, Azure Key Vault__). This prevents attackers from exfiltrating +the online private key (although not from using it, although their actions +may now be cryptographically auditable). However, this requires modifying +the reference TUF implementation to support HSMs (WIP__). + +__ https://www.vaultproject.io/docs/enterprise/hsm/index.html +__ https://aws.amazon.com/cloudhsm/ +__ https://cloud.google.com/hsm/ +__ https://docs.microsoft.com/en-us/azure/key-vault/key-vault-hsm-protected-keys +__ https://github.com/secure-systems-lab/securesystemslib/pull/170 + +Regardless of where and how this online key is kept, its use SHOULD be +carefully logged, monitored, and audited. + +Managing offline keys +---------------------- As explained in the previous section, the *root* and *targets* role keys MUST be offline for maximum security: these keys will be offline in the sense that From fb870186845bbd6d41b3e82240a174339d819ef8 Mon Sep 17 00:00:00 2001 From: Justin Cappos Date: Thu, 3 Oct 2019 16:09:18 -0400 Subject: [PATCH 005/122] Fixing link to [81] --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index a5067b51a52..a89de53f460 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -856,7 +856,7 @@ indicates an attack. As for attacks that serve different versions of metadata, or freeze a version of a package at a specific version, they can be handled by TUF with techniques -like implicit key revocation and metadata mismatch detection [81]. +like implicit key revocation and metadata mismatch detection [2]. Appendix A: Repository Attacks Prevented by TUF From e6ea13febbfe33bbe02de781e441ed816218c009 Mon Sep 17 00:00:00 2001 From: Justin Cappos Date: Fri, 4 Oct 2019 11:37:08 -0400 Subject: [PATCH 006/122] Addressing the frequency of updates issue... --- pep-0458.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index a89de53f460..dd7f87a595a 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -618,7 +618,11 @@ The project transaction and snapshot processes SHOULD work concurrently. Finally, project transaction processes SHOULD keep in memory the latest *bins* metadata so that they will be correctly updated in new consistent snapshots. -All project transactions MAY be placed in a single queue and processed +Signing updated snapshot, timestamp, and bin metadata needs to be done on each +update. Fortunately, the actual operation of signing is fast enough that this +may be done a thousand or more times per second. However, locking must be +used so that project transactions are handled sequentially. To achieve this, +all project transactions MAY be placed in a single queue and processed serially. Alternatively, the queue MAY be processed concurrently in order of appearance, provided that the following rules are observed: From 7426e7b48195380d71f9f6a1e84be20e49dd9473 Mon Sep 17 00:00:00 2001 From: Justin Cappos Date: Fri, 4 Oct 2019 11:47:01 -0400 Subject: [PATCH 007/122] First cut at revoking trust text --- pep-0458.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pep-0458.txt b/pep-0458.txt index dd7f87a595a..f51f9eb1fa2 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -682,6 +682,18 @@ server failure. __ https://en.wikipedia.org/wiki/Transaction_log + +Revoking Trust in Projects and Versions +======================================= + +From time to time either a project or a version of a package will need to be revoked. +To revoke trust in a version of a package, the bin role can simply remove the +delegation and re-sign the bin metadata. Similarly, an entire project may be removed +by removing the bin metadata references to the metadata and package versions. +All of these actions only require actions with the online bin key. + + + Key Compromise Analysis ======================= From 57abcb1406a439908cb931040ef41e7e973f554e Mon Sep 17 00:00:00 2001 From: Justin Cappos Date: Fri, 4 Oct 2019 12:12:40 -0400 Subject: [PATCH 008/122] more update related changes. --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index f51f9eb1fa2..b8cd633d145 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -642,7 +642,7 @@ Snapshot Process The snapshot process is fairly simple and SHOULD be automated. The snapshot process MUST keep in memory the latest working set of *root*, *targets*, and -delegated roles. Every minute or so, the snapshot process will sign for this +delegated roles. Upon an update, the snapshot process will sign for this latest working set. (Recall that project transaction processes continuously inform the snapshot process about the latest delegated metadata in a concurrency-safe manner. The snapshot process will actually sign for a copy of From 2be8b84fe2446d3a1a799e16fa0756993e321006 Mon Sep 17 00:00:00 2001 From: Justin Cappos Date: Fri, 4 Oct 2019 12:19:14 -0400 Subject: [PATCH 009/122] Garbage collection initial text. Needs more work. --- pep-0458.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pep-0458.txt b/pep-0458.txt index b8cd633d145..74e0c2d9fb2 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -682,6 +682,21 @@ server failure. __ https://en.wikipedia.org/wiki/Transaction_log +Cleaning up old metadata +------------------------ + +Prior versions of snapshot, targets, and timestamp metadata does not need to +be kept indefinitely. (Root files must be indefinitely retained.) +However, a client that performs an update MUST be able +to retrieve a consistent set of versions of the files on the repository. +For example, if a client downloads a snapshot file, it should retrieve the versions +of targets metadata that existed when that snapshot file was created, even if +the targets metadata has been updated concurrently with the client requests. +Fortunately, the use of hash / version delegations handle this case automatically +since clients request targets unambiguously. Once no clients could reasonably +be requesting outdated targets, snapshot or timestamp files, those files may be +removed to save space. Thus files that were obsoleted some reasonable time +in the past (such as 1 hour) may be safely discarded. Revoking Trust in Projects and Versions ======================================= From 7d1c235245d415449701b6c4df4c8ac9178488ed Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Mon, 7 Oct 2019 12:50:00 -0400 Subject: [PATCH 010/122] Update pep-0458.txt Co-Authored-By: lukpueh --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 7b428af5429..827a239dc2d 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -513,7 +513,7 @@ Managing online keys --------------------- In order to support continuous delivery, the keys for the *timestamp*, -*snapshot*, all *bin-n* roles MUST be online. There is little benefit in +*snapshot*, and all *bin-n* roles MUST be online. There is little benefit in requiring all of these roles to use different online keys, since attackers would presumably be able to compromise all of them if they compromise PyPI. Therefore, it is reasonable to use one online key for them all. From 3f3e8c39abf9ce78d1b58c453abfede1a3678f99 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Mon, 7 Oct 2019 12:50:43 -0400 Subject: [PATCH 011/122] Update pep-0458.txt Co-Authored-By: lukpueh --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 827a239dc2d..85fe0b6c8fe 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -519,7 +519,7 @@ would presumably be able to compromise all of them if they compromise PyPI. Therefore, it is reasonable to use one online key for them all. This online key MAY be stored, encrypted or not, on the Python infrastructure. -For example, the kept MAY be kept on a self-hosted key management service +For example, the key MAY be kept on a self-hosted key management service (e.g. Hashicorp Vault__), or a third-party one (e.g. AWS KMS__, Google Cloud KMS__, or Azure Key Vault__). From ddbf2aa9691c5a438d97dce7517c5e88d11f0905 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Mon, 7 Oct 2019 15:12:33 -0400 Subject: [PATCH 012/122] discuss offline key ceremony --- pep-0458.txt | 91 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 24 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 85fe0b6c8fe..5e3ac939ce2 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -468,21 +468,8 @@ order to be able to run on as many systems as Python supports; and third, TUF recommends diversity of keys for certain applications. -Number Of Keys Recommended --------------------------- - -The *timestamp*, *snapshot*, and *bins* roles require continuous delivery. -Even though their respective keys MUST be online, this PEP requires that the -keys be independent of each other. Different keys for online roles allow for -each of the keys to be placed on separate servers if need be, and prevents side -channel attacks that compromise one key from automatically compromising the -rest of the keys. Therefore, each of the *timestamp*, *snapshot*, and *bins* -roles MUST require (1, 1) keys. - -The *bins* role MAY delegate targets in an automated manner to a number of -roles called "bins", as discussed in the previous section. Each of the "bin" -roles SHOULD share the same key as the *bins* role, due to space efficiency, -and because there is no security advantage to requiring separate keys. +Number and Type Of Keys Recommended +----------------------------------- The *root* role key is critical for security and should very rarely be used. It is primarily used for key revocation, and it is the locus of trust for all @@ -504,13 +491,12 @@ MUST be offline and independent of other keys. For simplicity of key management, without sacrificing security, it is RECOMMENDED that the keys of the *targets* role be permanently discarded as soon as they have been created and used to sign for the role. Therefore, the *targets* role SHOULD require -(1, 1) keys. Again, this is because the keys are going to be permanently +(2, 3) keys. Again, this is because the keys are going to be permanently discarded and more offline keys will not help resist key recovery attacks [21]_ unless diversity of keys is maintained. - -Managing online keys ---------------------- +For similar reasons, the keys for the *bins* role SHOULD be set up similar to +the keys for the *targets* role. In order to support continuous delivery, the keys for the *timestamp*, *snapshot*, and all *bin-n* roles MUST be online. There is little benefit in @@ -518,10 +504,15 @@ requiring all of these roles to use different online keys, since attackers would presumably be able to compromise all of them if they compromise PyPI. Therefore, it is reasonable to use one online key for them all. -This online key MAY be stored, encrypted or not, on the Python infrastructure. -For example, the key MAY be kept on a self-hosted key management service -(e.g. Hashicorp Vault__), or a third-party one (e.g. AWS KMS__, Google -Cloud KMS__, or Azure Key Vault__). + +Managing online keys +--------------------- + +The online key shared by the *timestamp*, *snapshot*, and all *bin-n* roles +MAY be stored, encrypted or not, on the Python infrastructure. For example, +the key MAY be kept on a self-hosted key management service (e.g. Hashicorp +Vault__), or a third-party one (e.g. AWS KMS__, Google Cloud KMS__, or Azure +Key Vault__). __ https://www.vaultproject.io/ __ https://aws.amazon.com/kms/ @@ -531,7 +522,7 @@ __ https://docs.microsoft.com/en-us/azure/key-vault/basic-concepts Some of these key management services allow keys to be stored on Hardware Security Modules (HSMs) (e.g., Hashicorp Vault__, AWS CloudHSM__, Google Cloud HSM__, Azure Key Vault__). This prevents attackers from exfiltrating -the online private key (although not from using it, although their actions +the online private key (albeit not from using it, although their actions may now be cryptographically auditable). However, this requires modifying the reference TUF implementation to support HSMs (WIP__). @@ -552,6 +543,58 @@ be offline for maximum security: these keys will be offline in the sense that their private keys MUST NOT be stored on PyPI, though some of them MAY be online in the private infrastructure of the project. +There SHOULD be an offline key ceremony to generate, backup, and store these +keys in in such a manner that the private keys can be read only by the Python +administrators when necessary (e.g., such as rotating the keys for the +top-level TUF roles). Thus, keys SHOULD be generated—preferably in a physical +location where side-channel attacks__ are not a concern—using: + +1. A trusted, airgapped__ computer with a true random number generator +__, and with no **data** persisted after the ceremony +2. A trusted operating system +3. A trusted set of third-party packages (e.g., cryptographic libraries, +the TUF reference implementation) + +__ https://en.wikipedia.org/wiki/Side-channel_attack +__ https://en.wikipedia.org/wiki/Air_gap_(networking) +__ https://en.wikipedia.org/wiki/Hardware_random_number_generator + +In order to avoid persisting sensitive data (e.g., private keys) other than +on backup media after the ceremony, offline keys SHOULD be generated +encrypted using strong passwords, either on (in decreasing order of trust): +private HSMs (e.g., YubiHSM__), cloud-based HSMs (e.g., those listed above), +in volatile memory (e.g., RAM), or in nonvolatile memory +(e.g., SSD or microSD). If keys must be generated on nonvolatile memory, +then this memory MUST be irrecoverably destroyed after having securely +backed up the keys. + +__ https://www.yubico.com/products/yubihsm/ + +Passwords used to encrypt keys SHOULD be stored somewhere durable and +trustworthy where only Python admins have access. + +In order to minimize OPSEC__ errors during the ceremony, scripts SHOULD be +written to automate tedious parts such as: + +- Exporting to sneakernet__ all code and data (e.g., previous TUF metadata, +targets, and *root* keys) required to generate new keys and replace old ones +- Tighten the firewall, update the entire operating system in order to +fix security vulnerabilities, and airgap the computer +- Print and save cryptographic hashes of new TUF metadata +- Export *all* new TUF metadata, targets, and keys to encrypted backup media +- Export *only* new TUF metadata, targets, and online keys to encrypted backup +media + +__ https://en.wikipedia.org/wiki/Operations_security +__ https://en.wikipedia.org/wiki/Sneakernet + +Note the one-time keys for the *targets* and *bins* role MAY be safely +generated, used, and deleted during the offline key ceremony. Furthermore, +the *root* keys MAY not be generated during the offline key ceremony itself: +instead, a threshold t of n Python administrators, as discussed above, may sign +independently sign the *root* metadata **after** the offline key ceremony used +to generate all other keys. + How Should Metadata be Generated? ================================= From 9da22de2269c6d1d19b3f41b2839fcfd195988b5 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Mon, 7 Oct 2019 15:19:47 -0400 Subject: [PATCH 013/122] update metadata scalability --- pep-0458.txt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 74e0c2d9fb2..e10e6629a74 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -436,12 +436,15 @@ install or upgrade a PyPI project via TUF. __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/TUTORIAL.md#delegate-to-hashed-bins -Based on our findings as of the time of writing, PyPI SHOULD split all targets -in the *bins* role by delegating them to 1024 delegated roles, each of which -would sign for PyPI targets whose hashes fall into that "bin" or delegated role -(see Figure 2). It was found that 1024 bins would result in the *bins* -metadata, and each of its delegated roles, being about the same size (40-50KB) -for about 220K PyPI targets (simple indices and distributions). +Based on our findings as of the time of updating it for implementation +(Oct 7 2019), PyPI SHOULD split all targets in the *bins* role by delegating +them to 16,384 delegated roles, each of which would sign for PyPI targets whose +hashes fall into that "bin" or delegated role (see Figure 2). It was found__ +that this number of bins would result in a 12-17% metadata overhead for +returning users, and a 148% overhead for new users who are installing +pip for the first time. + +__ https://docs.google.com/spreadsheets/d/11_XkeHrf4GdhMYVqpYWsug6JNz5ZK6HvvmDZX0__K2I/edit?usp=sharing It is possible to make TUF metadata more compact by representing it in a binary format as opposed to the JSON text format. Nevertheless, a sufficiently large From 4c3503a739490ddc2f37b5bcef66ecf6d03a11c5 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Fri, 4 Oct 2019 10:58:34 -0400 Subject: [PATCH 014/122] changed minilock example to yubikey --- pep-0458.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index a5067b51a52..68e79d6591c 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -932,7 +932,7 @@ in this section: delegate trust of these wheels to an online role. 2. An easy-to-use key management solution is needed for developers. - `miniLock`__ is one likely candidate for management and generation of keys. + `YubiKey`__ is one likely candidate for management and generation of keys. Although developer signatures can remain optional, this approach may be inadequate due to the great number of potentially unsigned dependencies each distribution may have. If any one of these dependencies is unsigned, it @@ -942,7 +942,7 @@ in this section: distributions and manage keys is expected to render key signing an unused feature. - __ https://github.com/kaepora/miniLock + __ https://www.yubico.com/products/yubikey-hardware/ 3. A two-phase approach, where the minimum security model is implemented first followed by the maximum security model, can simplify matters and give PyPI From 5d2cadd6fbababbba2b4c26b3232fdd6c6c158fa Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Tue, 8 Oct 2019 16:01:59 +0200 Subject: [PATCH 015/122] Update section about consistent snapshots consistent snapshots used to require a hash digest prefix in filenames for metadata and target files, now only target files add a hash digest prefix and metadata uses a version number. --- pep-0458.txt | 73 ++++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 490aa7b02f7..969cce0c8eb 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -542,38 +542,49 @@ that disagrees with what is available on PyPI, which is indistinguishable from arbitrary metadata injected by an attacker. The problem would also occur for mirrors attempting to sync with PyPI. - Consistent Snapshots -------------------- -There are problems with consistency on PyPI with or without TUF. TUF requires -that its metadata be consistent with the repository files, but how would the -metadata be kept consistent with projects that change all the time? As a -result, this proposal MUST address the problem of producing a consistent -snapshot that captures the state of all known projects at a given time. Each -snapshot should safely coexist with any other snapshot, and be able to be -deleted independently, without affecting any other snapshot. +To keep TUF metadata on PyPI consistent with the highly volatile target files, +consistent snapshots SHOULD be used. Each consistent snapshot captures the +state of all known projects at a given time and MAY safely coexist with any +other snapshot, or be deleted independently, without affecting any other +snapshot. + +To maintain consistent snapshots, all TUF metadata MUST, when written to disk, +include a version number in their filename: -The solution presented in this PEP is that every metadata or data file managed -by PyPI and written to disk MUST include in its filename the `cryptographic -hash`__ of the file. How would this help clients that use the TUF protocol to -securely and consistently install or update a project from PyPI? + VERSION_NUMBER.ROLENAME.json, + where VERSION_NUMBER is an incrementing integer, and ROLENAME is one of the + top-level metadata roles -- *root*, *snapshot* or *targets* -- or one of + the delegated targets roles -- *bins* or *bin *. -__ https://en.wikipedia.org/wiki/Cryptographic_hash_function +The only exception is the *timestamp* metadata file, whose version is not known +in advance, when a client performs an update. -The first step in the TUF protocol requires the client to download the latest -*timestamp* metadata. However, the client would not know in advance the hash -of the *timestamp* associated with the latest snapshot. Therefore, PyPI MUST -redirect all HTTP GET requests for *timestamp* to the *timestamp* referenced in -the latest snapshot. The *timestamp* role is the root of a tree of -cryptographic hashes that points to every other metadata that is meant to exist -together (i.e., clients request metadata in timestamp -> snapshot -> root -> -targets order). Clients are able to retrieve any file from this snapshot -by deterministically including, in the request for the file, the hash of the -file in the filename. Assuming infinite disk space and no `hash collisions`__, -a client may safely read from one snapshot while PyPI produces another +Instead the *timestamp* metadata serves as version root. That is, it lists the +version of the *snapshot* metadata, which in turn lists the versions of *root*, +*targets* and delegated targets metadata, all part of a given consistent snapshot. +Eventually, *targets* or delegated targets metadata point to the actual target +files, including their `cryptographic hashes`__. Thus, to mark a target file as +part of a consistent snapshot it MUST, when written to disk, include its hash +in its filename: + + HASH.FILENAME + where HASH is the `hex digest`__ of the `SHA-256`__ hash of the file + contents and FILENAME is the original filename. + +__ https://en.wikipedia.org/wiki/Cryptographic_hash_function +__ https://docs.python.org/3.7/library/hashlib.html#hashlib.hash.hexdigest +__ https://en.wikipedia.org/wiki/SHA-2 + + +Assuming infinite disk space, strictly incrementing version numbers, and no +`hash collisions`__, a client may safely read from one snapshot while PyPI +produces another snapshot. + __ https://en.wikipedia.org/wiki/Collision_(computer_science) In this simple but effective manner, PyPI is able to capture a consistent @@ -598,14 +609,6 @@ called the "project transaction". How PyPI MAY validate the files in a project transaction is discussed in a later section. For now, the focus is on how PyPI will respond to a project transaction. -Every metadata and target file MUST include in its filename the `hex digest`__ -of its `SHA-256`__ hash. For this PEP, it is RECOMMENDED that PyPI adopt a -simple convention of the form: digest.filename, where filename is the original -filename without a copy of the hash, and digest is the hex digest of the hash. - -__ http://docs.python.org/2/library/hashlib.html#hashlib.hash.hexdigest -__ https://en.wikipedia.org/wiki/SHA-2 - When a project uploads a new transaction, the project transaction process MUST add all new targets and relevant delegated *bins* metadata. (It is shown later in this section why the *bins* role will delegate targets to a number of @@ -670,10 +673,8 @@ snapshot. All clients, such as pip using the TUF protocol, MUST be modified to download every metadata and target file (except for *timestamp* metadata) by including, -in the request for the file, the cryptographic hash of the file in the -filename. Following the filename convention recommended earlier, a request for -the file at filename.ext will be transformed to the equivalent request for the -file at digest.filename. +in the request for the file, the version of the file (for metadata), or the +cryptographic hash of the file (for target files) in the filename. Finally, PyPI SHOULD use a `transaction log`__ to record project transaction processes and queues so that it will be easier to recover from errors after a From 4e684a91e3f55ef8e461c8abba13eab2d7974fa7 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Tue, 8 Oct 2019 16:12:28 +0200 Subject: [PATCH 016/122] Update link to client updater workflow The link used to point to outdated documentation in the reference implementation. This change updates the link to point to the up-to-date client workflow in the specification. --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 490aa7b02f7..2e59817b8d9 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -373,7 +373,7 @@ root metadata and downloads the rest of the roles, including updating "root.json" if it has changed. An `outline of the update process`__ is available. -__ https://github.com/theupdateframework/tuf/tree/develop/tuf/client#overview-of-the-update-process. +__ https://github.com/theupdateframework/specification/blob/master/tuf-spec.md#5-detailed-workflows. Minimum Security Model From 0980fc41d8d9bb9aba589254ecc28353eccdb432 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Wed, 9 Oct 2019 16:47:42 -0400 Subject: [PATCH 017/122] clarify --- pep-0458.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 5e3ac939ce2..699c80dcad8 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -491,9 +491,9 @@ MUST be offline and independent of other keys. For simplicity of key management, without sacrificing security, it is RECOMMENDED that the keys of the *targets* role be permanently discarded as soon as they have been created and used to sign for the role. Therefore, the *targets* role SHOULD require -(2, 3) keys. Again, this is because the keys are going to be permanently -discarded and more offline keys will not help resist key recovery attacks [21]_ -unless diversity of keys is maintained. +(2, 2) keys. Again, this is because the keys are going to be permanently +discarded, and more offline keys will not help resist key recovery +attacks [21]_ unless diversity of cryptographic algorithms is maintained. For similar reasons, the keys for the *bins* role SHOULD be set up similar to the keys for the *targets* role. @@ -533,7 +533,9 @@ __ https://docs.microsoft.com/en-us/azure/key-vault/key-vault-hsm-protected-keys __ https://github.com/secure-systems-lab/securesystemslib/pull/170 Regardless of where and how this online key is kept, its use SHOULD be -carefully logged, monitored, and audited. +carefully logged, monitored, and audited, ideally in a such a manner that +attackers who compromise PyPI are unable to immediately turn off this logging, +monitoring, and auditing. Managing offline keys ---------------------- @@ -588,7 +590,7 @@ media __ https://en.wikipedia.org/wiki/Operations_security __ https://en.wikipedia.org/wiki/Sneakernet -Note the one-time keys for the *targets* and *bins* role MAY be safely +Note the one-time keys for the *targets* and *bins* roles MAY be safely generated, used, and deleted during the offline key ceremony. Furthermore, the *root* keys MAY not be generated during the offline key ceremony itself: instead, a threshold t of n Python administrators, as discussed above, may sign From 0858bceebb50ab68451f45ee0d73df0e27adaaa2 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Fri, 4 Oct 2019 10:49:07 -0400 Subject: [PATCH 018/122] updated image --- pep-0458-2.png | Bin 18497 -> 23361 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/pep-0458-2.png b/pep-0458-2.png index cb535f631bcb8277ef9c3e4e4bc4f9a0765b5519..fe3e762142c84d53c29042309ed5106703f52c5d 100644 GIT binary patch literal 23361 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 VD!X*X?^mb==0|OgP)t29{S))($v6N2 literal 18497 zcmc({cUV)~_Aa~v2)LEvMtT$a*M08sJ)0EICvHCU43{oD-zZqxpydegvna(%|qj>jiOeBUxphSRy)s<&c-xxDe7 zxXtEE@$U;~P!(bo7b7V*T`1_~i&Z*eVPD&o_SjRs_#UqFKcgRyg;8|zv;TR!Zy8R< zeS19lFY&rxFR$-^WwjYNn-bb*MJ=I${UN1`yUgU3g~;5Azh5$31l=Ruacv+5TJShE z9N%{xDZ-lGZz_xjHT(bSuWs2`7dt{ZB2j)00GzG{UM(nzXQTjtRe9dt?Z;)z=wL_5 z+@&**vTz^zxXW{w*C9X#EDoba*O}$55DyK#YumuQU=2xv!a>3gj4!uM;=R~ z;zR>+SnCw5eUlLY0jy#%ukbTF**lq81cNEdu?T|o>Jx(bSg3mca)VOyz*3+ zbvV&5V@Zdlay5f*%R5k;0vrrNR&O}ThjWHWO^X#%c9`@sYxc5(ymD&hr>1DsT~N@6 zge{KU(nQYQfvggHB5BJ*Ksoc^`k~zNF$)Y-x=0T9iosNgNRqriami7>;Q}0-8d=g% z%pmXJz2b*H%f~$+t6dx$zkcK*eA48rI{iC7v*@vE+f$wR}{ zE{eD5sgJ!jyH4`~K*hHK%+i)FU66=)zy?nW{sTY(z&O($xQOXEE}1rP8s5>NC0A>? z_hdNP{Ft&qjGZq%6s1p7!2ZlTfgybLDpi~KR(rwP;Zr4Z+3Ng99U5Ryc1-dfP z#wP4@5>4vAE;~w6w5dDjkr$S8k9jxK6l_;`pvXR^B|Gh-bKsKsEyzYeaV`hc=vW*P zjr)1O!Rjpp_z@h%D&tdKd(dRU1#{`}%6vLr_qOAu8IDmecY!D(O2;QY$HaJ~ubY|2 z>jEJNDvh(mZhxf#0EP<;nik>ePlYRk&UEUX<$@ZpyIHt!vqd0yA`>9k$ja6Y`VQFM z7IxDRY)9Ic3yV3f%omCv*6_ax%v{qx1vIlL=-aE$iLHp!T!2r9w{C4jA}C_+R|Ur> z6-fv#!p<7=#=t#tJTi^Y?qR}|LsREqN0Jm8MP=13+>*v#hG^$wcbFgkyh_FkO`EYO~xXl~hjlt($FG;yVmMf^u z0QPC%-MZL{iHXv!(T9b`W^1;t~16^CNom_#(5TKdzMv`I7^BtQy z{N>veoqcEd@WG?K8lB*`q}A2Ch_v?-#TU>iYXcM4As0PgTm=9gW0wn1ogxx}cjlY* zsn~R8m?XLSkH#o#p1=zWS2@)*7SjI7(e?K=qFzv)>%MaE zH+ZXAH*nl=?!~uHaSy$H@ISr0nJZ(1(;9WkIf#blOB=6ZUS_=tkQZ0D#R4?L17s0A zQ9tFBT*S(IdP=?OY56CKvh=Xyz%xsKk+o&JH&WT#G@CWXbyvi3AZajwgJfvt!n`|l z;abUeA(|>XWp{FSkas?gglo=oM|d;$ELM}i1%x`Qt#_GI54kmZPa@XZ7y&nOAL2FhAbriP_} z$HqqweF?s1bt2c=FB9*9aGhnHEfJ#V)W(v9D{ZYVUPDsQYLU0WhsZ6%=($fL0>_Gu z?`Zj?#e~T1e!;zS0>;*qAAv71*}eGW6Ayc<5Fv|KOu^7SQPI?x}3GqXbMJAu2O8$R8q9XHS6l zJVrjl;Sig3WUlL~A^#nf2^G zcmj?kpz7;4uE)^=KVQ~QwcW}gbn-7Y-4zL2yPEpeT5zaYp;~=ES=Ntw_SkTkS0V7I zT9XI(z-kd`gC^ZU>z2^RcYqWPR zx6ib>d(u@80?cg5*&oV$xc)+ZYvc!sVbd=pX)U;NXiZB5`{I4+Rs4uMKQ2`jfXOLl zD7X?X`?Pdxbd+)S^@06uc+UB4xjZ0zo~m}-{#xDj`ZNSCwjbg^r%pIl6kL>{>xhnn z;ye)5Go{{D^=(qTgbj5}KnuAVjR)VM+o}dnY)Z!qdBS+#eR}n3(tSJzBOFuPiJg^a zrVHX+DP2I$XFh6_pBwBRI+#d43LESm}N8;%nT5xG9zN&L1XT!(akAQZ9WOv0DjW=8Kq#L+98R9K5`KuNd}e%M^~e# zZ@3+8$riUrha^K&4t0suJ*CLJeAAS}#zsx>&_-@|Uk}*>#$%8+t7d&<|L~WvvXqv0oj&$#YBt0^^6lE4NHr$}KbbUK)a+H7; za{oD5Ee%3suG$+hW;;TR@by@Rk(!F8d^I{V$I+**^BkJnVTA0s0i-FEMS2%eH(hfh zW_k~-D2jKx6Oww&Xg*L=Stv|8u#^tTcTRyui#D~NBZFupj;Hy@ho%>RQ_hzI8oQ** zDFl~q{VB(5y}U~?m1(BIQARS`hEXgg9Me&JY;Ff&^~eK z@hP(D6rV|lY7_Knn+^Rh9Yg%@mydRLW1s!pJW&rk#)m-wZrasHBG)i+;S;kPHF-dI z{8Y&z)c)vN2rYOd0e06$?wH?Ja^?HZyTot;ujz9CgvA33pdY~pzYI}5%_nwJO(uDX zVLR-<%PGPKtx}-hc|l}A0dQzu0$Y#)Jq*yL2>GxYXkO#-`pXGJF4=%6lDm!kJ zvU+KO;FT_ydg$Qc5nDa5<$sWI__Pa$6##*ARdGwb?yzk_rchQDl0Q{+g_|xH*a8yr zIyB6EGaS{l{@|N&{sh7lDtJ04tla@cP#^Y|ixDDTq&*G7p6U$S4w~5Nh)L!qtg)`q z0}_0ME!=IqETb8 zPrZ8W-O*>=q^{%Rbbta543-0BhBNjxE=S5d`b|OZagfIkDNCnzW?AHZ)%Zakt5h;p z?!bpUHMO51}<$Swr%r8FNO zQD-pxNSB1JTiqPbL)Qu}xJRDj*=u8qS-vB1tTt^c8612)LkMakJ&QFM);|}qbczw6 zilr&p%z6b5@fbCx#GP;)a)Y8fPi;Mt`pO(WE-RR|8nf!nx~sNcI~TsXy)K}Zj*x-- zRhKptX^jnj9Oo_$WFSbiZgNzkiFXsmzE|drvMr6tn-BMFj&J#Gqjd~R0*VLOEMu_s zdVWm-{{3$BcYxXl!!6ympR23R%nfZ$G#S62r4i0_t__%Zz5k{M+qFGs;5-{BpxksG zwfo4KhsdW~bbfm?yApd_Li~-<#CXBF~?vPI;q!lLjm|%CXid1XR zJYa{*43wAmW2Zeyf;qHK%u1N>t|WVV6z^#;8mAHatArshL9{ zN~a(N2F`AXuQ=#dXZ^*6kkJv#}$;l4NEF z%($LzlcN4ddI z0GHl~-tl*?&e0Jb3cRpXuwpINhAuQM3^qcddMZ1j+H8#)P^O!NKBJ{P(}bzKM|TZ( z8ybxRqBR7j6HV{wy@*>0%6JWPU@aD<|Gi8Al_CA{UJm*y7lKZEa_P6asFNKohtD|FYx4QoXGy3H#(Yfo1c z9*=w&#PV^0V7I>tS!|$xwrxU{F_0W4c;TXs%!?d`(PZ9?QYSCd!u81nVbf8nSRU$F zR%>8?-2^ZehsX2hG=gVA0(c5hg};3D<+2nvMdvN2s0YOdftM%-Il*&~qBHSSY#05* z?B%+xV;%K!qf*BN8LVp?$FfIpv+5HoPbGP?$XA{hy$Sw}Wi}0*kWU5tTr9BQdy!vu z=L^O!39B(d%U=T?{LfqMoT7ZMw?_|C-fmdVED4(R{32shFZl|+e235B5Bd?pysGoq zXCf*69IP&+_I#7=#1AM7Wm{1aUX~GNV{Jw3>h@|XqPx}ffb(vd0egM$XBOCO#PM7{ zot|Yxtkj=4bMi5~+yv;AJ&j++cD^$%RI$si zI=C5O!Jkj9*sM$Kn^RSmbDrhXr$g?Fwy~7?0|j%~NZ&Ps7crcbgw>InLn1Y0XV1AT z7EbyP*9-_sr((q{l#93~_lEKhw%_`?>a>6Kz@PW>j{?fh$8blGcq0VvuNP6p=5(K{ zWTZzC~%@`$Ha`m-Czy|9(*L=@stTd4pB2b2`$}59Ny@Z zl2b`cm5?^1aOijuX~jnbo`#qXzF~m_KOXlp-!v-kl?5XcBs0iyz)IxSa=nBsNME|WR7~Am(zuymTbMXs-nax&Rw1;VG4D4Q zowx)9P1W!MixRb1`E#JZ91GlWYw&B&m_Y^#V zOYYa68U83n9lJWvjEiiYQ#QQ*NOwXr+UQnWmmRD_#4)|6a)L%6KkbeBFG9__6~+&` zd<$TYvUe}@=o~o(S)|zw#JE-ES@#V86!dNp?l^v`*l#8L4>BU-oZoN> zPObm9Y_X`Mkc7r40{RVz;J_})Xy~r1xW-`McixVJD^wsn;w&7x5;TN9K~)_l(IBGJ zPazmu1ZOlx6VS0I%^XyAee0%RTv8{XZ%-6@oNv?Um9&gB?O2N7*veysBP!~tx3Bb? zh^%4@uzzDnrL?1m1ep?t6;m(56Qas@P8?ARgny`es{miWf9!Kn}-_;NI7V1n642D41E9Q5U}EmQj9kowXztj zuY5F?0@Yr&ta78vCA1!Vng38{hB_}(G>=Q|G1C>#!40aYAp+GcMO|wt;LcUx06VS( z9cKuZxUQ<}LhnwMovgxmIP0ULo&ZNIG#IGb$>Vik|j1rccGBifnPc9@aQ|FKW3w(<$LdUalMq| z$aogX5R#HVlCZ zxJ>fs2^#+PS{`i)Se-fhmE(Jr5w!I04F!e{$>Cr|82E!6t%iZn_vFX_P4T}ZkbnGd zB>Z0$!&`Rlyof3j6o6|QmfUaw{dZu-?K(w=$rDE~^xe-KCdUY)PtgQBs{USlPT3_@ z5&S8aZYlt;cijRiGNLZou#cnh6ESOPL5f`_fC9L7d+RmPrl%dRqenb^8^08i zFj&nRkvU}?GCPP5lFV_X&)eDEh(D_RQ+4B0Ld!0wo4=n?5k@Nf_`#jInafA!dO^B` z^2^VER44Gg)2Wbulz0mGfS^yDM?awp`Rzw?sEVaJLjb$%dET5IpV|VYqAnaMrB3d} zI5!wb9rcryDjrJr<$h(060q|rjDCe z1y^`j`BTMC*;oU>N4T5pI&9R0KjBEVY<1o@V)C3%=Qaa!0&7ES#sJ% z$9>@jT@Xn)v-lcC=aq6vFtr%IVsCEDP2({O);Kxm$ouPeg5`G_20Y+?7m+sx`OpFY zJ~%msPWJBpqv-d;C#+1>Gki)&X3XzqRTtoCmse5%3b3o&a+ICvB;SJrj%v|atmhw~ zjBCx1#rIkCYG@_1VbEJC#Ml0L98#2@hn}0+Ojb&m^6X{VwbNBen3^M}eqaE53Q|w0a?mpzFj=mT4766N`5kOL(?0$pQJ~fBfDAxPp^nL3#!m4nH^l zeA20-{I?AQ(R)s^@I~T<_QW^s&e`5A;D{g>X=3FZ#)4S1_IHA)V%ykAx=O^6D7Nrm za+(Q%JhpsN-|^_IF!Xaf-%ce61<&e1O_8W)uf7P9Gec|rK$%>0U_$8El5vMGPcxBL z<`J3OEHOiBQ5u(P`QZT2+(*x6^vI~_0Jpu_Xv-OGb$=t;tNh{j&cIgPl9D#VSf+Eg zZRl=+iIv|ALpBxUj(>N5vR3~pA}`#(9)8#QU3B7lgB#&ba5n79A3`2$P{lqh2e;pp zvyLtp{}Y0reF}6PTZlUhUu6KAz5V$)MmW{zPV-T+!@QYg%t5C!tQY*4ovP7lQNtxn zFcY&peWD;m=lgxd?ZlXD8!rGLCIEvA{)#!J0&p%V5lWz++OMc{kZiF=QAU)V{N$}T zbtwivUfn`kB8f&hESVY&6``R^gAHi^6h*yZ5f<=7s9S$+cgJ?9`@^D_QraMNvBPX+ zxVkX-{UGl`d9Y7gojp%kpjIHk0*huXB$LSLc=>Tn<4qg`-wR zD_%PciUeov&}#8x{E>uQ#ov}GMxU5O%UAk7i2R`j&W7jk=ItDkEfIoK?eJ=js{m+? zMtMo#6r0E4*Ljy0w$mfpy_er-*~lCO>3ga*#1Hx~Ff(Tk7?~5-w*6G-jlt~r_-(X& zf?<2WQ%_M}<>!VxMxWzTXDmNSAG2N&wFvionU6qN=IpFz=I-bu{CJDAn-sZA$ zVV1ERz8QHQ035r*Q0Y;OWyl_slHo8$GCaRA|K+~sp3kOdAO8}Xr&k34&VC5o$u?IC zJUW;roSm{A>}DUDGL9Q))~VV##M)=ZSjh~cQS9KnJb!JsD5MDz4B$ucJ}3?eu*^Fb z8=+t;`T6D6UuQ}$A#EYRn!I0{hVqs!Psb0p0;iXOFDj?(F+YvNz=IZ^F60Wi{zUR~ z8~@_DIB+JwKjN^e$9XWHWEUT!u=r+pCZV-M@1Q62MuuUZ7MSks!DQ3TXo^sV(8=O5 zUOzr_yZTMe2<3(343ORM!vox6>k?Of>Jy6Bnq)z3s8y5+Q!al?wnOm8!69Yhe}L(Q zr^Wf4_LzL5e(w>fJe=Hhu-_gx6L!0dn2S4UT?3v>UAmvE9%b~=+^Z2ZpagdE0^^HV zHD8I&EAp9IUw%&h;Ov;XSipk;&drZ|hk>5~0MqfnhR2ehRWLs`pXkPQjWdGjhqtNX zXom5E_K1|INkH`0-59 z<K#k+4{OU*!nJXVwwkHTaFifR5 za*eD@OKWBVtR5(jbr^L`PRP3R#CmtNSdW7Wen1Gm6XgTtI^CKj+Erf#tUj!d{Q^V# z)+#RNcb^>a0#-k`L>(IQ8MOWw_oj(R5p@v0PQeXa`_L``U(B|c_=94~)zae^4Pe#0 zE^`_bH2&uG;$>q{pa7PQ&8bxN$7PXB=`uyX$9^;JarRb=GZ!;g-BM?SAaLg-pQ5_= zhB2SwIO79HG1h{;%{5%ltPvi1Ut8QRE2CcSw6-i~e<|jypa*u;JpHc+E3-gR%#}Th z?6iNw1xf+eL-|QY<=mHAfE6-oETta|zIk>Hcv1I$=a@%VbxHMm?C(!?W78St9INjF zR;Fg9GFc(FMI3wB6|tkVe7ICaN^a$&+^RR&kW49i0FLl3R*@oldDP6@bXH~Mq_920 zyT{pDLAML&dkCU&G{E>BKYZl!uwRs~i;22s?z16jwjllqZri+&JJ-YJDXl?^djAe75C;)2k^-kDV<%fZf_@`4lLhMg8S2S z{LL`U1+2M@#qD;;+WG8+AAYTi;}TnmsQOjR`sCKJR@oo~AnGaK@KtVgQ}!4xsPMQ@ zDm!{?YOi<{qe6BZwtCZTh6(!f4!zF}(G9 z4%=SejVwhLp?0|3gqgTR1IyvcES}|1n{B(u)}I{`qQ_o$Q+v?`(7x#nQVU+Cs5OjT zy&(O;t=l1`1K-j7EZwNpklV|6jR@hoOq>`PyGI=1v-+xj1!0NMh~=ZEPdk>u9=OI; z*A8sO{VlPptV0Yw4|S)EJU8FJAqLkR6SuRDvU29=f_4u36|gjqx0Cc7&@%S;u*O7G z$mSD5*qW;DkeX(aLme~=!gHu9Qq!=x`*)#gE~0s|AEna8p@xDrD4Y?4=Y{s7{J?)Wy+yBcxstAOZr#tk*wI2 zEo6I1RV6IDLnC}16Zp`{EF4V~tyL}d8HUM2^hiDjhrqb#px-CuEJ%?Qyh6B|f5qq# zk>{;#HE~C-@*@0;X*ik@^zTM_B3mg#^)a>3S82xY zxd{^d*^Rsg3{AY!7vPu-nnXM&n6#6_9t^n59L@UqahR}QoRwk7+o>k_l_s|%3cHhI zBf$oxjlUbXbW%no@m%3J8&WZ46ks^!;k`P|Xnkdmzj`-2ozB+ex>*^hPUJu8^ev(m zE1;v!bG-|%#d;Zfus{6=E1aL)rymID$A_-8;6v4^K2HxdpcbD)M-jzCMRe^2V7<~s zY`_RukK2O%>2&X_A$;g>ff|5rk=Mh@7J1j;dxXa?&0XYs8Gb9qLk2P32Op>y$&<$l zi+`p6o;M!IItRImx4S0IwnVcXN@zKNt5aRYCk8-N?d1~)H&63%oNM+FhhL`41K(K% zUxV7I0zFD#UcpUxJM>R*$>A6aK$k=NNiax1P=DNyi8~WyJiMVC=!!%)Tt^H3r=+)# zR2S=@{n_@wT;q*Xj5+fLOEj0@cB-;VFMY+cgOq1H;-I?2pMmEX9=i6>F(=(K9A~#f zUwLZuGWfmEG)3>-Q0pz-=JMpMi2>D~_({65)U^|=ORC;%IIAHAt?25Aa*~9tPc%Ee zalWuNtpl*X#t+aT>-NpSNJFRW!51!5tg?`-TV^jv5^7`Z82t1~9}G}bIR|%^ZVo8) z*spAP5H$lchf6bsSNd0;n)z4cPgRlv|D0371ks=O3W^$2V8=6ogLZ7lbO?GdJTDr^ zK#Cw1=C}DCw)rvyh~Te-{3FD`EN6_!9R8_p467F+KQe*T`wfj^mN7bQ#;d`Y|LVD7 zqIKg|+L+um(G-f#+XwUnr6{A4%y8`0OlVW4r4hxzk#AF}SF$z+_in+b;elTXf%dHc zi&T$NC^Jm?@`_WEg}59C$-jTx4qT#-AbU*g>sJF-3&$^i(rQajCpIy)qbpt1*{(BI zV@PI^9gSwgPp5HRS?U)qk67Xya!SvGBj7yEH)nJNIs;feSD%1xl=+J_Y0grab2lF< zr>uXRBymM%`gB+?p^s|WL*%~p+@g9ts5g!Gp2pYSar0~PXzEG827Jo5MJrt${o3yj zd3^C!e_(y-;Jk0ivhT*h_SEM1oWX`1bIYg3gr&+F+$^ndI-c9gg(|$fF#)61+DpA1fM5 z9DQxsvAYYD-J9~4JU_pny&lXfSKm~hF#np4I@V#It#rm!;d#GZ7~bqi46AV9QjM-H z!WZBq@$OE3H-V2&AQz8saLplU9FekVQI&BRl=VPOJW_K}@J9ii^u@L$)L?+pWTi@L z!FPsK20n4as@qF57c5J`C}`sDY69-r&==;cgOYJpQ=w7+bgaPBMQkb^A%?|=)S zCmO^AdnGJ#YBn-+ZuxUsMmXl^cA!zbsjRwO&9t}|EniIA?rukvM(od>dfYb>^1+n( zQynLeb>#$^_|6i`r4Ve16vNT)1T%2Xj0u-kvS}T>pG*Z5{<1kwD^ORHq3Jixj2j)-r|ksqvZQORthZz!S%v z;kcnpdAs%$rgmJxBk#|vvSnus=^|$w z6YKA<*oexX=1&YTIS*@BC=LpAhyu1+1XE6dF622FQ3`H`FvshfL8tiPT=_F0Wp^Qq z1N8Wy;(4NuK%k;Idq@?V$W3k8Kr!;sioln1f6Sh2HsV}*+fDLEssQh!)tg+TWpK|c z-vVVfW&(0|Khjb}4*V!(g>?%UDg6lzv8Y9Ttl1c&i{ZTkRcu*s3*@m>+kX&&MJVSg zUke7AA&9kPwO;Uhr09I+b+j*cDi#bZKvXN0;loKSDB)*W;9TwS!4=Iptqn(?u|f43 zHg@SvUAjdjblSEv^UvObo@+`1&*B1q`_x++yI|~jpmdUTtO*=YJQQjXFx^Hv%SSV` zA|G8hF<$uD0=Pc+YZ_%p(duLnl-A=}3F!OZsG`Q)b4Pklxh_cTei7~vHq{2r-8-NR zIZyo3J*f&#m2N1=n?!)I25N6{mxq9#Kzo`D-L;S+kD2B1t<9TZ=4IN$cYuNLo$~s& z1#Ri2+6O4K=$4BnrdCo=fhsm|tD(TIf9{Q-vK(fc;q$3Qf2Z|MOI4Fez^LL@t*gUpS)g_y)492E2FJ*S$q^RVonF>LgAmR2YuJr25lJdZV;TVl@j9)v`^F>NPo?jK$DEymN4Bo_dkHq{{T8X zbg!sePe4#I??LZKI2<$k8yfu<3IbRV)U;fKCi8x0SUQ2IAR7Az8i6404Bs8Hi6C!P zATdCijf`$iP!;2U<0!B)N%KD{^phJ<{Tnd>JqRS`xmvg)=w1|JKaUvs44#4oc%PjAj;r*lf zR}n;ztaDkp^(}}^JGLc>@yk=)M1liweUa?gfUTV%x_2fhZjA-nBYa3J4YO3lAi{MG zEiin$vvP3FXgC%}Gsj@KKf%iNc^Y3{?-#s0AK#d{7lvf6yf-{MLXCdy(s#Bk@D~G! zx>NcPg_b47%YECeNb*>AR(|^r%egVzw%gF$otZS~k047Sqtr-KrDRrZ43-CsitCRV zV#BiVngK!3{otSTe*J&Cvgw8Y>!8a@Up2i{u1Jf@q^H%v6{#J)sM!C$k~ub z8NR(B!VcpJ`Hg?gWlUt*{rZ<*vUUE9@jsSMiOJn@zN!+b#=SmY?o{QDRD84BFF#m>qv~??9JyIKlR!u&UAG&T=LL=+h0Zr1ajf zcidZ5zTbF~BH02{B!)|oI38hPpy8jF*eGZI{!?}52g}YC5!vYTK{N&PRz?3;s4?B}GdWMfs8z#&4 zT$b24=HIvb9CoCnKI#|sQL8?)w|+yt$|LFv1vp3UXULDv`y=;4BzonPc1!akH zVmGsRAOK&B=zo{`6TBUB(ZW&80$Im$yzRS2+C0E6H1v*Tln07is$K5JA9~f}YAfpQ zF9zoLDO;Xa#01oH#q^@w1$Zo-%Us(66qz$o?|sER3$|Ha=8lO{-X(C9wyi32Hnr+W zALMk=(m%O}v>5i)B`xgs;CED~mFIr>HE@mv@!)T;pvuo5>$o32HkmQ;ER+oD#{0?7 zVxOA%kayPShNw>en7Ore$5XL52dKCzTIp#f?y%A~gE&w!i)hH3*)$pBuvqP>ajsAS zYT2M;V;Q`~GP6vZ+ikDlRR#fl(`hK_`Xo7YaG3Deh|f!f_!OTQj@N=DZ<85}!!c8(37?7cLVn-4+ zEaSTV)9`10N{mglKBf7buoP#MN2QG;9zhf9akp1fMbuz}bbm$Bx53`5>!V$TxCUj@ zz4wOT{CdchPSN3POIRwjb4F$cwwnCT+?$q->ZGwlU0e zBT<3DiFe}{d-3iKWu!Xbr#N%tNPy;D{S${K3=d%CMtQsRmd40eLCQ|#=5cO{u>kyg z@n+379=w<#bKsNpHbjbJMT(P_&+>k7wYJxX(f!QGWrJ5~(`l~Wti|;*XhSn7cgvNg z(Ro_gT|jc^C9t(hcMzQ({XAg~=eenjGg#`vg69knIuc$#jPBP>;9SU^$@U(}?JC$O z*o7_m;5i|7Z?8DX=yUmO7rbnCtj7Oj@_vtpT^7rc1B2YFIL7B_1) z)?dx#a0R>#nL`)&MIGNZJD#GWibZigpO~FI?1rGh!({7vO0?>I-WL|y<+)N{l8_$~ zkoxIGTICG|+lh&(1>3$n;MRkyKQ_la^u?)WH$cC3+-jBfLk{}ts4^X_R@BKn$_II- zkysQ`6yy0|E%LKuLa;AYNsLGo$#e5yxAj+aJ1I3vJ>crs9-{=({mk-u-1|FdmH_J++PcxLhU>cbc~zqW05ONSI}jki_Q6^*vsF>bJ{r2+cG6~m{`CL!e$(8AHO znsHl~^+#H|YX9oBrYc0PO4G5;Wo{_>SzLz^jn{)Qj`5-8d098bP?5R{DI45LpBY;| zPBSm}PGU{zFG88dqP`unI-uMoW{^{+L6h-l4~y~Ur$GfO@f`n}zLeRtgH(U6X6$0lp~kJZBX31)tr2XUT16Eur+qW$2K((>K-W-Q$- zusZE3Ptn#=K~Nc5vZ+@5Xfe~tf%1_$bX&U_%u6G?pP`|*Js+g8ut_sGX`950sK@n6 z|G=yo^2UX9sBq`D_$zP+6-@`N8-(HA_#6<+6+(x<`rDDR@u^Co4b>81jG$fOr=8TbtiSFae6h0B}41A5g=&I_sMJ&8%ov zFX1KLO0R@GLb=2t1DM+YhjP{*V2^0W=2JhN;^?0ajPC4e^|skcPPP&r=hJeBc%fE#{-1pMA$$UAX(me@#1vMH z6rE_b-o&WB|4FGYLKMYa3G$TBDfP+QK_#?rYwKeDuf?2YO$w<#;1C!9P|TP?Xwre7 znXrSe-`(38tYT;Q0N51eZ7?ccTT3X9Z$#=IAVJQ=maQ&T%Ziw8ErDx!l50z2c}@(9 z%gA!E-y;zv?+@$X`p#fMMLr-Pi{!xg=r6ppx#&t>(alAVq@|eG2LC~#f0PYo!TYNK z$pa8%ip^n*1)Fe=E3=GSKakdk7iWxvOa39zGnk!+0r09*v4Q!hoI^EHhhL`Umo6fj zr~?Jm^=o&nZ|ct#_Cz7Ko1H;Mg57<`WEOf*1J0qu1DMbH1(SZ z!ULyhKzhIjQOM(~b5%W~F^2S-I*z4~xp2=O)MlYgV?t8v@@g_o1|zvos#uK74;_b} zrXYWQhbQoZANo1DM$$&`Q671mqL9Uez^NiIlWnN1b9<$pd}VKkOq*}%BhR2IMdy|? ztMoRaII!&NupeE`NyrKSnHF_Mtb-|Z`Tu(U%@+e5t(_Cj*$KVk?OB&X?BJ4bxiS+nOG&!Lsnvu|d;W_zi_DZw6S`egEcShp7N}4FFj@;`kw=wr=YDJc$S4+#t`n z>!I7~o*7hWUR(%S^wtIamQtmokt}ru#m2Me0~Yq?eH#f{RIw9T^vt`Omu}Cz!5bdA z&_veho<1Ld_A5jM2+STE)F0_5%mw%`R7pEK0_-t`78~Ce(MHBlu!NTi@0w(L_?ZXGXn&6eu70uWv>sT$3;u&hb5{q`ea3 zat;2y{jfvi<0U8xoDvQ6@zu8Nh5f7r43K# zbU^P_%FooijQ^LwbZS$i*s}*3NAwz;~XmmE8|16%w|7=&g2-ywxF( zw|iY@SAO}2S#23#f~}R#DA#s@Jo9$6D>wmf=02Jbh}GZ?Hq3@~lOtP_^Jef{bXYUqK+@78J`M7nL^ynJikSg{D_w#$R>&v4Yrd-`X z85>HU+{$ z7W;pJCoc%MZ@?F4qO@hrn#8F+UDbeErsF09B9L|?)uGhq?$TNZ$fEpWZ$O~WP^kIdJeKkpyeMC zB$eBRmpbVJ4&&*}6i>zR-b6CTd5DBuDV zGC)@U^5nT{?RFgA!ugEG?lf8au;%GcQ;>=I*AM_-M(o^lXRf zi^PrvtoR_k0uxcYrhd}xlb9_?0;7tZO#@Y~VyS1-+I!r}7G6}jY9j!|n_ew3j5d_v zt?o7C33-yESIa zL-x&mFB;>-dB1JgsL+|}gN6pY6R`fi-^Zh265|efijfqZsu)G`O)$_v^bkLkO-%^> zW59OGlwWUhyE>r*{{C+bAP4&70@#0;vJ=iBnL+)pcVvtIX(Qhqga2dp{$J`A zm>=CI$|@gA{bLz(s*?olm6eaJPYUug#+Kt7|4JW&yMEimbU<_7yJEmgva9PU^}0=0}_TCqH`fP>dWRVS(utLwHZm+ ziGL9zJ~V0CNJb*Al%1U_WXYF$gG9U=i0JsEOJs;tuAL(}uBbm;{Hn=SjLcDMybD6o zE%u;r|9fcv zZ{Ne)K|qrI*ys*jFnB)@AL{{N1HEq?BTUO8i7a+}w0RR91 From 00aab9637ef17e1f8e1329d85a7f69579f9e4255 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Fri, 4 Oct 2019 10:55:14 -0400 Subject: [PATCH 019/122] updated description to explain that bins uses an offline key --- pep-0458.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index d710fc88882..4890b4e8b7a 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -335,11 +335,12 @@ details of signing repository files and the types of keys used for each role. Figure 2: An overview of the role metadata available on PyPI. -The roles that change most frequently are *timestamp*, *snapshot* and delegated -roles (*bins* and its delegated roles). The *timestamp* and *snapshot* +The roles that change most frequently are *timestamp*, *snapshot* and roles +delegated to by *bins*. The *timestamp* and *snapshot* metadata MUST be updated whenever *root*, *targets* or delegated metadata are updated. Observe, though, that *root* and *targets* metadata are much less -likely to be updated as often as delegated metadata. Therefore, *timestamp* +likely to be updated as often as delegated metadata. Similarly, the *bins* role +will only be updated when new bins are added. Therefore, *timestamp* and *snapshot* metadata will most likely be updated frequently (possibly every minute) due to delegated metadata being updated frequently in order to support continuous delivery of projects. Continuous delivery is a set of processes @@ -399,8 +400,8 @@ not have any of the keys required to sign for projects. However, it does not protect projects from attackers who have compromised PyPI, since attackers can manipulate TUF metadata using the keys stored online. -This PEP proposes that the *bins* role (and its delegated roles) sign for all -PyPI projects with an online key. The *targets* role, which only signs with an +This PEP proposes that the *bins* role's delegated roles sign for all +PyPI projects with online keys. The *targets* role, which only signs with an offline key, MUST delegate all PyPI projects to the *bins* role. This means that when a package manager such as pip (i.e., using TUF) downloads a distribution from a project on PyPI, it will consult the *bins* role about the From b46c4c43150fa7a8dce427c9d2703210cea09cff Mon Sep 17 00:00:00 2001 From: marinamoore Date: Tue, 8 Oct 2019 11:30:56 -0400 Subject: [PATCH 020/122] bins role should be offline, bin-n roles online --- pep-0458.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 4890b4e8b7a..8ea184fa161 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -416,7 +416,7 @@ Metadata Expiry Times The *root* and *targets* role metadata SHOULD expire in one year, because these two metadata files are expected to change very rarely. -The *timestamp*, *snapshot*, and *bins* metadata SHOULD expire in one day +The *timestamp*, *snapshot*, and *bin-n* metadata SHOULD expire in one day because a CDN or mirror SHOULD synchronize itself with PyPI every day. Furthermore, this generous time frame also takes into account client clocks that are highly skewed or adrift. @@ -546,10 +546,10 @@ monitoring, and auditing. Managing offline keys ---------------------- -As explained in the previous section, the *root* and *targets* role keys MUST -be offline for maximum security: these keys will be offline in the sense that -their private keys MUST NOT be stored on PyPI, though some of them MAY be -online in the private infrastructure of the project. +As explained in the previous section, the *root*, *targets*, and *bins* role +keys MUST be offline for maximum security: these keys will be offline in the +sense that their private keys MUST NOT be stored on PyPI, though some of them +MAY be online in the private infrastructure of the project. There SHOULD be an offline key ceremony to generate, backup, and store these keys in in such a manner that the private keys can be read only by the Python From f06d90890c5dc5de769dd0459af130f2ba0b08e0 Mon Sep 17 00:00:00 2001 From: mnm678 Date: Wed, 9 Oct 2019 17:08:36 -0400 Subject: [PATCH 021/122] Update pep-0458.txt Co-Authored-By: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 8ea184fa161..7700c5301b8 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -336,7 +336,7 @@ details of signing repository files and the types of keys used for each role. Figure 2: An overview of the role metadata available on PyPI. The roles that change most frequently are *timestamp*, *snapshot* and roles -delegated to by *bins*. The *timestamp* and *snapshot* +delegated to by *bins* (i.e., *bin-n*). The *timestamp* and *snapshot* metadata MUST be updated whenever *root*, *targets* or delegated metadata are updated. Observe, though, that *root* and *targets* metadata are much less likely to be updated as often as delegated metadata. Similarly, the *bins* role From dfb86e6064409fe7e9da8ab1aa9caf86b88038fd Mon Sep 17 00:00:00 2001 From: mnm678 Date: Wed, 9 Oct 2019 17:11:07 -0400 Subject: [PATCH 022/122] Apply suggestions from code review Co-Authored-By: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> --- pep-0458.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 7700c5301b8..2bb10ab2091 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -340,8 +340,8 @@ delegated to by *bins* (i.e., *bin-n*). The *timestamp* and *snapshot* metadata MUST be updated whenever *root*, *targets* or delegated metadata are updated. Observe, though, that *root* and *targets* metadata are much less likely to be updated as often as delegated metadata. Similarly, the *bins* role -will only be updated when new bins are added. Therefore, *timestamp* -and *snapshot* metadata will most likely be updated frequently (possibly every +will only be updated when new bins are added. Therefore, *timestamp*, +*snapshot*, and *bin-n* metadata will most likely be updated frequently (possibly every minute) due to delegated metadata being updated frequently in order to support continuous delivery of projects. Continuous delivery is a set of processes that PyPI uses produce snapshots that can safely coexist and be deleted @@ -413,10 +413,10 @@ PyPI. Metadata Expiry Times --------------------- -The *root* and *targets* role metadata SHOULD expire in one year, because these +The metadata for the *root*, *targets*, and *bins* roles SHOULD each expire in one year, because these two metadata files are expected to change very rarely. -The *timestamp*, *snapshot*, and *bin-n* metadata SHOULD expire in one day +The *timestamp*, *snapshot*, and *bin-n* metadata SHOULD each expire in one day because a CDN or mirror SHOULD synchronize itself with PyPI every day. Furthermore, this generous time frame also takes into account client clocks that are highly skewed or adrift. From 8e307970f75e2f4da945f55036246b629195c7f6 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Wed, 9 Oct 2019 17:12:40 -0400 Subject: [PATCH 023/122] refer to bins roles as bin-n --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 2bb10ab2091..aa3c165328d 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -400,7 +400,7 @@ not have any of the keys required to sign for projects. However, it does not protect projects from attackers who have compromised PyPI, since attackers can manipulate TUF metadata using the keys stored online. -This PEP proposes that the *bins* role's delegated roles sign for all +This PEP proposes that the *bin-n* roles sign for all PyPI projects with online keys. The *targets* role, which only signs with an offline key, MUST delegate all PyPI projects to the *bins* role. This means that when a package manager such as pip (i.e., using TUF) downloads a From b3d7d90ccda8bc8105022d4bcaaad8b810b119f4 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 10 Oct 2019 11:08:23 +0200 Subject: [PATCH 024/122] Add newline and indentation to fix pep2html errors Fixes: - Enumerated list ends without a blank line - Anonymous hyperlink mismatch --- pep-0458.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index aa3c165328d..5c299e56886 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -557,11 +557,11 @@ administrators when necessary (e.g., such as rotating the keys for the top-level TUF roles). Thus, keys SHOULD be generated—preferably in a physical location where side-channel attacks__ are not a concern—using: -1. A trusted, airgapped__ computer with a true random number generator -__, and with no **data** persisted after the ceremony +1. A trusted, airgapped__ computer with a true random number generator__, and + with no **data** persisted after the ceremony 2. A trusted operating system -3. A trusted set of third-party packages (e.g., cryptographic libraries, -the TUF reference implementation) +3. A trusted set of third-party packages (e.g., cryptographic libraries, the + TUF reference implementation) __ https://en.wikipedia.org/wiki/Side-channel_attack __ https://en.wikipedia.org/wiki/Air_gap_(networking) @@ -585,13 +585,13 @@ In order to minimize OPSEC__ errors during the ceremony, scripts SHOULD be written to automate tedious parts such as: - Exporting to sneakernet__ all code and data (e.g., previous TUF metadata, -targets, and *root* keys) required to generate new keys and replace old ones + targets, and *root* keys) required to generate new keys and replace old ones - Tighten the firewall, update the entire operating system in order to -fix security vulnerabilities, and airgap the computer + fix security vulnerabilities, and airgap the computer - Print and save cryptographic hashes of new TUF metadata - Export *all* new TUF metadata, targets, and keys to encrypted backup media - Export *only* new TUF metadata, targets, and online keys to encrypted backup -media + media __ https://en.wikipedia.org/wiki/Operations_security __ https://en.wikipedia.org/wiki/Sneakernet From 4c57d8b446123b03a1b463b3473b810f73c72fea Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 10 Oct 2019 11:16:53 +0200 Subject: [PATCH 025/122] Disambiguate delegated target roles This commit aims at clarifying the targets delgations hierarchy targets->bins->bin-n and when which role is used. --- pep-0458.txt | 68 +++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 5c299e56886..7571de3ac0b 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -322,11 +322,12 @@ Signing Metadata and Repository Management The top-level *root* role signs for the keys of the top-level *timestamp*, *snapshot*, *targets*, and *root* roles. The *timestamp* role signs for every new snapshot of the repository metadata. The *snapshot* role signs for *root*, -*targets*, and all delegated roles. The *bins* roles (delegated roles) sign -for all distributions belonging to registered PyPI projects. +*targets*, and all delegated targets roles. The delegated targets role *bins* +further delegates to the *bin-n* roles, which sign for all distributions +belonging to registered PyPI projects. Figure 2 provides an overview of the roles available within PyPI, which -includes the top-level roles and the roles delegated by *targets*. The figure +includes the top-level roles and the roles delegated to by *targets*. The figure also indicates the types of keys used to sign each role and which roles are trusted to sign for files available on PyPI. The next two sections cover the details of signing repository files and the types of keys used for each role. @@ -340,7 +341,7 @@ delegated to by *bins* (i.e., *bin-n*). The *timestamp* and *snapshot* metadata MUST be updated whenever *root*, *targets* or delegated metadata are updated. Observe, though, that *root* and *targets* metadata are much less likely to be updated as often as delegated metadata. Similarly, the *bins* role -will only be updated when new bins are added. Therefore, *timestamp*, +will only be updated when a new *bin-n* role is added. Therefore, *timestamp*, *snapshot*, and *bin-n* metadata will most likely be updated frequently (possibly every minute) due to delegated metadata being updated frequently in order to support continuous delivery of projects. Continuous delivery is a set of processes @@ -405,7 +406,7 @@ PyPI projects with online keys. The *targets* role, which only signs with an offline key, MUST delegate all PyPI projects to the *bins* role. This means that when a package manager such as pip (i.e., using TUF) downloads a distribution from a project on PyPI, it will consult the *bins* role about the -TUF metadata for the project. If no bin roles delegated by *bins* specify the +TUF metadata for the project. If no *bin-n* roles delegated by *bins* specify the project's distribution, then the project is considered to be non-existent on PyPI. @@ -441,8 +442,8 @@ __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/TUTORIAL.md#deleg Based on our findings as of the time of updating it for implementation (Oct 7 2019), PyPI SHOULD split all targets in the *bins* role by delegating -them to 16,384 delegated roles, each of which would sign for PyPI targets whose -hashes fall into that "bin" or delegated role (see Figure 2). It was found__ +them to 16,384 *bin-n* roles, each of which would sign for PyPI targets whose +hashes fall into that bin (see Figure 2). It was found__ that this number of bins would result in a 12-17% metadata overhead for returning users, and a 148% overhead for new users who are installing pip for the first time. @@ -640,7 +641,7 @@ include a version number in their filename: VERSION_NUMBER.ROLENAME.json, where VERSION_NUMBER is an incrementing integer, and ROLENAME is one of the top-level metadata roles -- *root*, *snapshot* or *targets* -- or one of - the delegated targets roles -- *bins* or *bin *. + the delegated targets roles -- *bins* or *bin-n*. The only exception is the *timestamp* metadata file, whose version is not known in advance, when a client performs an update. @@ -685,26 +686,24 @@ efficiently transfer consistent snapshots from PyPI. Producing Consistent Snapshots ------------------------------ -Given a project, PyPI is responsible for updating the *bins* metadata (roles -delegated by the *bins* role and signed with an online key). Every project -MUST upload its release in a single transaction. The uploaded set of files is -called the "project transaction". How PyPI MAY validate the files in a project -transaction is discussed in a later section. For now, the focus is on how PyPI -will respond to a project transaction. +Given a project, PyPI is responsible for updating the *bin-n* metadata. Every +project MUST upload its release in a single transaction. The uploaded set of +files is called the "project transaction". How PyPI MAY validate the files in +a project transaction is discussed in a later section. For now, the focus is +on how PyPI will respond to a project transaction. When a project uploads a new transaction, the project transaction process MUST -add all new targets and relevant delegated *bins* metadata. (It is shown later -in this section why the *bins* role will delegate targets to a number of -delegated *bins* roles.) Finally, the project transaction process MUST inform -the snapshot process about new delegated *bins* metadata. +add all new targets and relevant delegated *bin-n* metadata. Finally, the +project transaction process MUST inform the snapshot process about new +delegated *bin-n* metadata. Project transaction processes SHOULD be automated and MUST also be applied atomically: either all metadata and targets -- or none of them -- are added. The project transaction and snapshot processes SHOULD work concurrently. -Finally, project transaction processes SHOULD keep in memory the latest *bins* +Finally, project transaction processes SHOULD keep in memory the latest *bin-n* metadata so that they will be correctly updated in new consistent snapshots. -Signing updated snapshot, timestamp, and bin metadata needs to be done on each +Signing updated *snapshot*, *timestamp*, and *bin-n* metadata needs to be done on each update. Fortunately, the actual operation of signing is fast enough that this may be done a thousand or more times per second. However, locking must be used so that project transactions are handled sequentially. To achieve this, @@ -716,8 +715,7 @@ appearance, provided that the following rules are observed: project. 2. No pair of project transaction processes must concurrently work on - *bins* projects that belong to the same delegated *bins* targets - role. + projects that belong to the same delegated *bin-n* role. These rules MUST be observed so that metadata is not read from or written to inconsistently. @@ -728,7 +726,8 @@ Snapshot Process The snapshot process is fairly simple and SHOULD be automated. The snapshot process MUST keep in memory the latest working set of *root*, *targets*, and -delegated roles. Upon an update, the snapshot process will sign for this +delegated targets roles (i.e. *bins* and *bin-n* roles). Upon an update, the +snapshot process will sign for this latest working set. (Recall that project transaction processes continuously inform the snapshot process about the latest delegated metadata in a concurrency-safe manner. The snapshot process will actually sign for a copy of @@ -877,8 +876,8 @@ A key compromise means that a threshold of keys (belonging to the metadata roles on PyPI), as well as the PyPI infrastructure, have been compromised and used to sign new metadata on PyPI. -If a threshold number of *timestamp*, *snapshot*, or *bins* keys have -been compromised, then PyPI MUST take the following steps: +If a threshold number of *timestamp*, *snapshot*, *targets*, *bins* or *bin-n* +keys have been compromised, then PyPI MUST take the following steps: 1. Revoke the *timestamp*, *snapshot* and *targets* role keys from the *root* role. This is done by replacing the compromised *timestamp*, @@ -889,17 +888,17 @@ been compromised, then PyPI MUST take the following steps: keys (because, as explained earlier, this increases the security of *targets* metadata). -3. All targets of the *bins* roles SHOULD be compared with the last known - good consistent snapshot where none of the *timestamp*, *snapshot*, or - *bins* keys +3. All targets of the *bin-n* roles SHOULD be compared with the last known + good consistent snapshot where none of the *timestamp*, *snapshot*, + *bins* or *bin-n* keys were known to have been compromised. Added, updated or deleted targets in the compromised consistent snapshot that do not match the last known good consistent snapshot MAY be restored to their previous versions. After - ensuring the integrity of all *bins* targets, the *bins* metadata - MUST be regenerated. + ensuring the integrity of all *bin-n* targets, their keys should be renewed + in the *bins* metadata. -4. The *bins* metadata MUST have their version numbers incremented, expiry - times suitably extended, and signatures renewed. +4. The *bins* and *bin-n* metadata MUST have their version numbers incremented, + expiry times suitably extended, and signatures renewed. 5. A new timestamped consistent snapshot MUST be issued. @@ -907,8 +906,7 @@ Following these steps would preemptively protect all of these roles even though only one of them may have been compromised. If a threshold number of *root* keys have been compromised, then PyPI MUST take -the steps taken when the *targets* role has been compromised. All of the -*root* keys must also be replaced. +above steps and in addition replace all *root* keys in the *root* role. It is also RECOMMENDED that PyPI sufficiently document compromises with security bulletins. These security bulletins will be most informative when @@ -933,7 +931,7 @@ Auditing Snapshots ------------------ If a malicious party compromises PyPI, they can sign arbitrary files with any -of the online keys. The roles with offline keys (i.e., *root* and *targets*) +of the online keys. The roles with offline keys (i.e., *root*, *targets* and *bins*) are still protected. To safely recover from a repository compromise, snapshots should be audited to ensure files are only restored to trusted versions. From 9b1dcef3c215dbf03e31d8ea4743de6af19d5786 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 10 Oct 2019 11:24:08 +0200 Subject: [PATCH 026/122] Clarify roles in Definitions section Add information about top-level roles and remove obsolete explanation about how *snapshot* differs from *release*. --- pep-0458.txt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 7571de3ac0b..3d8877c6827 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -166,8 +166,9 @@ Terms used in this PEP are defined as follows: * Roles: There is one *root* role in PyPI. There are multiple roles whose responsibilities are delegated to them directly or indirectly by the *root* role. The term top-level role refers to the *root* role and any role - delegated by the *root* role. Each role has a single metadata file that it is - trusted to provide. + delegated directly by the *root* role, i.e. *timestamp*, *snapshot* and + *targets* roles. Each role has a single metadata file that it is trusted to + provide. * Metadata: Metadata are signed files that describe roles, other metadata, and target files. @@ -179,10 +180,6 @@ Terms used in this PEP are defined as follows: complete state of all projects on PyPI as they existed at some fixed point in time. -* The *snapshot* (*release*) role: In order to prevent confusion due to the - different meanings of the term "release" used in PEP 426 [17]_ and the TUF - specification [16]_, the *release* role is renamed as the *snapshot* role. - * Developer: Either the owner or maintainer of a project who is allowed to update the TUF metadata as well as distribution metadata and files for the project. From 797306ac5ddcdc9f0a7be871bb633461f8715710 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 10 Oct 2019 14:36:49 +0200 Subject: [PATCH 027/122] Fix misc spelling --- pep-0458.txt | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 3d8877c6827..82c267053c5 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -342,11 +342,11 @@ will only be updated when a new *bin-n* role is added. Therefore, *timestamp*, *snapshot*, and *bin-n* metadata will most likely be updated frequently (possibly every minute) due to delegated metadata being updated frequently in order to support continuous delivery of projects. Continuous delivery is a set of processes -that PyPI uses produce snapshots that can safely coexist and be deleted +that PyPI uses to produce snapshots that can safely coexist and be deleted independent of other snapshots [18]_. Every year, PyPI administrators SHOULD sign for *root* and *targets* role keys. -Automation will continuously sign for a timestamped, snapshot of all projects. +Automation will continuously sign for a timestamped snapshot of all projects. A `repository management`__ tool is available that can sign metadata files, generate cryptographic keys, and manage a TUF repository. @@ -428,7 +428,7 @@ grow correspondingly. For example, consider the *bins* role. In August 2013, it was found that the size of the *bins* metadata was about 42MB if the *bins* role itself signed for about 220K PyPI targets (which are simple indices and distributions). This PEP does not delve into the details, but TUF features a -so-called "`lazy bin walk`__" scheme that splits a large targets' metadata file +so-called "`lazy bin walk`__" scheme that splits a large targets metadata file into many small ones. This allows a TUF client updater to intelligently download only a small number of TUF metadata files in order to update any project signed for by the *bins* role. For example, applying this scheme to @@ -537,7 +537,7 @@ __ https://docs.microsoft.com/en-us/azure/key-vault/key-vault-hsm-protected-keys __ https://github.com/secure-systems-lab/securesystemslib/pull/170 Regardless of where and how this online key is kept, its use SHOULD be -carefully logged, monitored, and audited, ideally in a such a manner that +carefully logged, monitored, and audited, ideally in such a manner that attackers who compromise PyPI are unable to immediately turn off this logging, monitoring, and auditing. @@ -550,7 +550,7 @@ sense that their private keys MUST NOT be stored on PyPI, though some of them MAY be online in the private infrastructure of the project. There SHOULD be an offline key ceremony to generate, backup, and store these -keys in in such a manner that the private keys can be read only by the Python +keys in such a manner that the private keys can be read only by the Python administrators when necessary (e.g., such as rotating the keys for the top-level TUF roles). Thus, keys SHOULD be generated—preferably in a physical location where side-channel attacks__ are not a concern—using: @@ -597,7 +597,7 @@ __ https://en.wikipedia.org/wiki/Sneakernet Note the one-time keys for the *targets* and *bins* roles MAY be safely generated, used, and deleted during the offline key ceremony. Furthermore, the *root* keys MAY not be generated during the offline key ceremony itself: -instead, a threshold t of n Python administrators, as discussed above, may sign +instead, a threshold t of n Python administrators, as discussed above, may independently sign the *root* metadata **after** the offline key ceremony used to generate all other keys. @@ -641,9 +641,8 @@ include a version number in their filename: the delegated targets roles -- *bins* or *bin-n*. The only exception is the *timestamp* metadata file, whose version is not known -in advance, when a client performs an update. - -Instead the *timestamp* metadata serves as version root. That is, it lists the +in advance, when a client performs an update. Instead the *timestamp* metadata +serves as version root. That is, it lists the version of the *snapshot* metadata, which in turn lists the versions of *root*, *targets* and delegated targets metadata, all part of a given consistent snapshot. From 50c7219a017464ee5afdeea768b6c38fe2e08d6e Mon Sep 17 00:00:00 2001 From: marinamoore Date: Thu, 10 Oct 2019 14:02:54 -0400 Subject: [PATCH 028/122] replace reference to RSA with ED25519 and fix surrounding text --- pep-0458.txt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 5c299e56886..fa2c386e3c5 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -465,12 +465,11 @@ PyPI and Key Requirements In this section, the kinds of keys required to sign for TUF roles on PyPI are examined. TUF is agnostic with respect to choices of digital signature algorithms. For the purpose of discussion, it is assumed that most digital -signatures will be produced with the well-tested and tried RSA algorithm [20]_. +signatures will be produced the ed25519 algorithm [25]_ as this algorithm has +native and well-tested Python support. Nevertheless, we do NOT recommend any particular digital signature algorithm in this PEP because there are a few important constraints: first, cryptography -changes over time; second, package managers such as pip may wish to perform -signature verification in Python, without resorting to a compiled C library, in -order to be able to run on as many systems as Python supports; and third, TUF +changes over time; and second, TUF recommends diversity of keys for certain applications. @@ -704,7 +703,7 @@ The project transaction and snapshot processes SHOULD work concurrently. Finally, project transaction processes SHOULD keep in memory the latest *bins* metadata so that they will be correctly updated in new consistent snapshots. -Signing updated snapshot, timestamp, and bin metadata needs to be done on each +Signing updated snapshot, timestamp, and bin metadata needs to be done on each update. Fortunately, the actual operation of signing is fast enough that this may be done a thousand or more times per second. However, locking must be used so that project transactions are handled sequentially. To achieve this, @@ -769,7 +768,7 @@ __ https://en.wikipedia.org/wiki/Transaction_log Cleaning up old metadata ------------------------ -Prior versions of snapshot, targets, and timestamp metadata does not need to +Prior versions of snapshot, targets, and timestamp metadata does not need to be kept indefinitely. (Root files must be indefinitely retained.) However, a client that performs an update MUST be able to retrieve a consistent set of versions of the files on the repository. @@ -788,7 +787,7 @@ Revoking Trust in Projects and Versions From time to time either a project or a version of a package will need to be revoked. To revoke trust in a version of a package, the bin role can simply remove the delegation and re-sign the bin metadata. Similarly, an entire project may be removed -by removing the bin metadata references to the metadata and package versions. +by removing the bin metadata references to the metadata and package versions. All of these actions only require actions with the online bin key. From d2698b2e9fd174f384bd6e27fba752b3ebfbe116 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Thu, 10 Oct 2019 14:16:11 -0400 Subject: [PATCH 029/122] update image to show bin N --- pep-0458-2.png | Bin 23361 -> 23036 bytes pep-0458-3.png | Bin 32146 -> 35954 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/pep-0458-2.png b/pep-0458-2.png index fe3e762142c84d53c29042309ed5106703f52c5d..52bea1b29481d5e9c982eedd8cc4a585095b89b5 100644 GIT binary patch delta 19223 zcma)kc{r5c8@Cyw6h#V^ZIY$3l{MKWDaP7nU&}`lzpi$k!&F})>IhT zip*rM3}P@@#$+K$@*FAU(}pP{9Zz4hYZ1A7(r26WId%?Z2Y8kP z>$@wg%EIKKn}(??jy3V#H+@EL9iXW91&z{GZ{A4>YOiMWdAIj1)U?ySXSyV@fak51 zLoBBR8w(2-%Yr{^C4lGP6!_oYwlLp)-G--GiR;DD*)*3`ag@Ct_gQ!C`}MnK3k=2r zmp?S~+2J(ufHn$+VqrmG?hEGr2+)^mFj6pG`jxQEw&rJ!WC;~iZ)gSa(G{jCAdm)>?AJGoKpK~NF``-25=q^ny97X`PYn=dC%(=EQ zvBHrH3tXnbmseDuP>eT+g^Om8pdZt2nomURrGou4n^;|Dbdm%clfb`y=@Pri_lZ(; z)m_}{eBtKaHy$au@XhL3&>^F?>>|#$@^}e*6TA_SnLUH*RiK2B)iVrWv)iT57+mWL@y zutc;x8m03k32+h?bLr!KF(YB~uUuVT5Sl0ya334-Lg^%m1uptNc3Mnfp8>x4!QwLk zu2{tdAKBg9yGkY4*-j5jQMt-ANH}k&lr`?!dhCfQ}iJT|8Nn zgZZIPy;0&aW0>St;M>qixte51UeTqN8PWIi>*}NYBvoTD^AecbfL`!GfKRL(dM3hA z{PCR>f+FB0_^F+gB7Og>>5tEuP2}+#o?Ryfjqy#2gz_n^cnSaPrncCm#Ap1tBC#cW zN`Y_Bzl7qha1hB>TJ5i`6ndKdUHfU7P0x8A^Q%#-tf?tNIkFbJyXwcImm;>p)@2K9 z1XMB7{cI0T#Y!tZzt?!)vYdS0tC+O#!h6kUbm5*kDy?<-chuPF4;hUYx7B+ki2h-` z<$X7kne0=Ck)EZYX)k-T7V|2f5A>>)wuP~!(dq$lYmXPEg>vgNLa(f!K_t?vq#I9} zDg7~19&Uk>CArh2KUGY7K}jigsPk=Ile8T-j3)PN7pebA9O ze*4KYUhd}eoxg-zma%i5U88b;h{hW_t!n$kE2#WdM_@A9#0a6Ww-kH@|46r)QZL@- zh3H)HiXgoB03wsIke<6FC(-@gTzgZZbDwCS3ocFv1jZ1WdScP`tLiB z0}$R+M;R?UjPIP_;f8m}IT3j_3EGjZ(K;31|!O2@642(()Q0&B+XjTQxHs%pSp)&E=YGFU}?}33rFT4I3iMXZRA{HWB z+g=g2el{wOL}Uv6{X%RAtw~&V_`YQJi?EG&R8F^W8!?7gU7`gi&a?n5uEoHy@1FZ>|5l(LY4W)#BJZw{ zm0;Z^+Z*rgD~A5`Jmf#J=;EhR*g-WEOKh54I35oaXQ@q%gopxKIsZq|)xTs%j6Q2= zKQq^ADl3a8Gm1qJ}c+X$yHgeL>>%hC@#2Z z77SrV_X|mGb_8}X@@zwFRU)10xpp;HqI`6y>cv};pTjmu%THWqrWf~%{OJmMqJXp} zEbI%Pvdo@59-L3VVv>|{af0g$FiCRRP0n6QvyOL2j_P1to69+;d#PcSqVq4$3m$cdgG1{{;z;-gmX?6D=&@zlqQlS4TNLb*(wrUGT|ocP(x zX}NKRaIp6GgwP~g=SSvioGZsFFPl34;U+}Hj&!&9TLzG(kjR63{tCdBL;WW!xq8JB z7{s5xuoxKe=18OHp^xmuGkEvSuIv$cm&!0T)*qke^_%8~VL3n6sBFkSI8^(uI2WWl z`9HMPMk!lt$_^4)e8$D3MD6)6QKZ0eXmdT>+?K=YJtwG3em)upkbguP7GwHx_E@Vd zEGBgGK!HKGJ4t)<^G>ub&*H$inKat{O?D%6JdW*7RjBmu5l866(FvOjMWHBLN|eYZ zCApqzu$bZcO|HO2RT69yp>!ukMnZo$miVaQ?~CRZjYQCX%Gq;UM4!*Ubn6H=2a{t| zGS*H2h4AR_tb9a{L=pAe7vjibufJsKNhQaoT;I>`eU&0S1P+}K?dO4+TV|I|E_ zL^CBPBD8+oxp6y^-3g)C3BSs_SYpzLKa>=IFIEF*EX|Z<7q{i?-}NXPPBkwpR*LBG zbHhccynl9rNMx!E>M`~>EHSY0&lvK>r`rq?({GKsFvmuPrv2eK?pgayLkK7f%-+BJ z-nVGzR1>}Mq@}e6DEloc|MoQsic95K--=7i{kGTcN!ylgO6yVQb8@N=Pn-`p-1J?H zaq-ro8c@?EDf@~g9QV6#D6_nCd}>sJ7FbG+$O=(cy!wW^`>JA7t^N$8-*jKrFM|aHq}Fck_I_oiU}ds=N73tR4(0G=Q)UE3q8uQ;} z_$$wOMZ&(a!%qnG7b=fCLj~O^j-@c3yei;d!)T$~+@oBawPdwes%WiC4KB zJT5-JpwfJtHSma2O%MCR&|+2L8Ak4-IQPU#i54F!qeerP;#(QP#QI32R7?<`%fe6J zxU{?=gOK_>WbNQw>-nLwm@oV zX^}!4A(7Vhx`y8-0k<(``Qy|OE3LLG9)=MQquu-Fwq$^QGu@8~$N0A@_=hbxo_eR1 zPrIj;JM`l#&g*#7snRunb5|}HjGg`HFSxuJ^k$yR681T|L}vPThS*xL-u8$)Xulo5 zEQg>=TABlG%e=JkqPKz{h8&$W@laxKq3e|oiwenq0|}cXWxOZ!YYoINJ9y7zn0>!4?tLelEOpR7 zcsb*{M#0vx-(MdaEtsQ|BF$mJs*kQ<s(1EP0XlI0E(xs;n&vp}be zCt{|^yQyu7nFPk-tyxLZch0P#_tT%XpaW~7XuOQeOw48IX}q_~Ot)*}M?3-nArNl^ zw-XEOZY(^$?ct!BsFW(+nx-$kg znHzV`Hp<>rTy(4l)0S^OX?HjQ#sXf@2HZk;3%0-H=Z@mSM~O@C z4a2f9eJAF_2RtIMLO7gF0(Jv$ATA3rNn>(|ow!|A!DXfo%ge~_r0juh;w(5GdtCF3 z2`Vj@p{i%lL6O-QATJS{f===*4;`@1^FQf#*rn##TQOugDt08F_h#A!w8CkWSs6}( z>6d}^2$-YF{L*Hr^@04+;aha>va`c1b4I&=!Dj&SH~g~Hmn1>!nw0FskV_d_{0bf?m=wdBSRViTDmmL4~_C01UpuZW{CFSE_Q-f~={&$$`t3S0ytaT)F!FZHVYgaDm(tsbin$5E8 z*Ws2dSCQ1;C6rgbV+dW6V>fiUfAum|39o2H`&%Q)ZWf zE7E4GN%J0-VYj_6A}DSh6W2nv^q#Q&Eu7cB>YBe&T|)1kuOJJ(Y=6U0hk{~9Y(~E> z2Zn^^j4oP@?hVY)%>8mB*nCv=eB-+`(g`k~%%9pFefA9c(nN3LZ((@xjO{Dq?52aJ zzx^vuhg6Fr-4D2QPp$uRYD|hQBAodq*vH1k(|G@$?T&cEbKb?J4bil|;qp*B_iNbf{loKo{%# z%gkbe{CNv5_-3u7_Qa8`qQ`in;;r%}hDskfkNc{0&5OKjtg?;{C=gcYI-+i3dSlV1 z-!B#$eB0ghL%KoIEIl!*RP2Ul$d>SN@06FKq2^T8slriJ)ATEzP-=DRJ19>Klco9a zek?9av-!*MXB;xedOCOEig?JgzUon3H^{EM-Byj=V}WTyseI8nal{k}WUk5TTYvrl-S^R}-@D3om1@93iO^K?{FupsdXC{uyK*97Etue*!fIHlb|F5AKa++p9qxwjv#mLiyS6FPzMb{QL>L^1W zS7VG~DmfI2WQfY*!#xTur>{Z~ZxCc_T-ugWYvY_5${`*DVl%cL+Y78ln z{sj1nY~Ll|GuxbU>ECaS^K=Pp_4>XALAiPVJ*M=Gx~ZdX1-wIMvE5&j*4(wfBwOj; zP+c51eRuf$^z|2=N_myl{OrV!a&n}l3EtoSWYqNhOpiQ*a(;&)ROQ?2uLj-u)b7_1 ztC(L$tvmtr;mVq!GcNE#tb$$dABwk_paPw92B``H9o;;UQmH4K6nx7n`*~kV?NrlO zcUr8P(q|_wXh!#xJ>~zWvbJNs$V_dpWoX+`qpz?;0!8BF(A9l z@nx6S^v%|L^fSIeN!LoXQ|u|)xA@0v{9l@4Jkf?hs-~hhURa%y^_|kP?=MYrEsd?R z?)7<50rvkGdn(gLAu#nneQ)(t--dftZ2vu}v@+SBzwcpSJtZyfqPiX1&oq+d=s@>` zt@x2>^8>&2`pa|aRkS=T){s z*9ip~SfX@dNYw|&f4atRJg2MM=T7$YB_4M;48g|k-x$AP^19)l*U_}pE;`KvE`Pjv ztm68&dSmmPrnb|Zx>uOC*bjbGAMTaYHl=CqoNQ9&l$}%npE~cnQhtJb-r{E&f9^N6 zo-x@@DV@O=KW1FTca@|r)t;l3lQqUutr+)7`{o!{&Q&J#u|?mP?LHNf){f_T?u^^# zLE7{AS8QH;9JG)4rAFVUem?#4leQX-?(AVoep~(4TD!&uZ{M|Ht5L6P*Ka}e6YaiH z@6+xEXGGm^R4aKw|1i2N9~FMVH&XL_BZl$gKL0p9{UGvA$sESf{L#LE{EACPep?bY z^o7GO1SX@i@O0yoPvrD`fe8yqq)LGP6?$WCZ`Z)$+M$mCpbG9`0muWO25xQ+@E@i? z)fwF=sDRr_AX)1_R~WN{Rz5iV>ox#~!2@EwOcch=edwqVUP$l;G-tbdDfE0}<7P+- z>@3#8%%7mr=^HEvh690I_PF}pP0{8H8vxK^r-aK4_HyW<`1EG3@!wyK*8L)G>4!mH z`Pkzq)T^D1jq_qg61=mg%2R4dC;U{K_A)1CqPe5!SuB8P3xvGI4dgl92gPWiJ1Cj| zguaL?9Le@|t4i9z1I?a9Ve59`Cecag`kqfWG+n*6bWVT&ZE++Vuh^aOLL_PUOR1xy zM3eG5pv-3z^T$kaj-XHXo}KQ>Yuy<7e&lFbFbJgc^D<6-rjO5(ooWM>1rDu_+`+Q2 ziXp2N5`y-)pL!4F#hot`-mx7jEwAi_4|&3rWDJ3rMd|c1IjZZgUt& z3tmnV1xOvQnBHEIQs)zVTr&=@n+tV4Z*nIjX)IidBKc~+nip*AT0WCy{o`0N<@qDh z%t^pqY#lF-+U5`{?jn2m=azfT=vfOddRZQIgKX9AB+3V+U3JK#7t))e+`j!z=7PgH zv#>V)TV~5B;(N&pyicWAgK^(*cF<( zzhhD>7PEF{!s9e^;_p1%&PP-0eD3v5AMjQ+TVf>GG6`bIE5Jr}>}$E?jN63K8%Y3% zr($MzH{$aw%#^}9at4Wnf7><_5dUezzdFRd8;B;zAZVl3vF}Fa0RQw5lC`GrB!1Un zBpijp!wzden*nY;X3W!>Qh(^%e5IRt&T;LJbpXJ|9ArMLsg%8f9>`j<~Q9ugZ* zSB8TV!~zyU)E{X&jQ|ezzYR|EO>VmCp;l`Hj*LGGupFBch}6Vm@V2LztOcyWJn!^8 zy13-W!8|sAkuhB#YbSkb1EH_7>jxaLs@Q65=b6FF@qY)&H!K5=_E(|5)TzsfzKMLfLe+KAEK$`WNHEzBeK2MLYd@7>l&-&eG~IaCwOwjv zYe@H8233rL-rQ7qw}3JaRgBQMX zbr#R&l4Xm3!#DZ7FV4-VV|f%heNrouwKJEOBKr4*pk7DtGz6J`;u@NXFL%naI_bA5 z88MfLo#-UF+PnmjhT-^CkI;+o4eu3f+`fB)1|zb;&LG6y+^As9%*$yzQ>^d{s_Gdfc+UwX0ux>p$x4txl&hGz97rec!B($m10rNn%Z*$}BV)GVf z!i1W?l1#D}k|nqON%#5loB%j2;RomAqo?vWkce)H^C?N`9$jmxi4qjcp^whk6#6Uh(ow zlG98dsa=I4VSkPnA`^jG`a&=7Kc--QmvQ&U88$Jar1UkYb9n}=p*G0)Oz6m{?*@Kj zfkPnoshecKIzlgB>FZW~_9kS01VJg$db3({zu6_!$s{RtZPPXb@fEN`t&aDT2j|Hm z=#t4=+ts}|6Lw;I*1``dp&Wu%YvHm6*!~Soz)tLUskv^RBv*qQ8KvpZrmwUrrZ26w zau5qwO9&q3YQiS7_3;-wmLCO;IFTx*RR3;s_f7cB!@*?gHTpxq=V9mzRJk_Nv@m4> zjhQU9U+t=PpNZ~V?wR#AKeyHeIdPxZ6m=$6_`y6a_@7de(6IWyEcvi0lT0PN85Chk zEfZbdNT22ha<$7{EswE+N7;c&1dSLAtJ6vL3qcPSF4?Splp78gM^^sP zj)75(uQe^cAcO#Rm3Q`iS)XRIM7lAa0(m*xnAG+qQd~BbOK}IGur0#=<;f<4ktmAE z%$TCJ*7+|$kxx5>Xdxar|V`e;HWz)gA9LzReHCPf3?BPx!5tW z%0G(#a(5abv7pV=fT-`}8oOKREujvzj#GHkUVWQ1J#lZdW28gbmBb5^0~iT;|FFq} ziItZZkII`uY|CG89&0fztqw{xAJa=@EjA>r7~X9iwrp2<`S;MKVvmS)YA_>oZB(;> zgj3slblPhH%#PW>*b8?^8qEwPV2FU^thgBz*fK6<_aAV@HHq;nsHl-VHuqMbgpKm5 zAL%N6z<>NU8{jn(35gb8`ZELZsk3LxZ6+#Wn&?F*^;2G_#}9lgYI&5d>SUg$x6^5= zM{Lvj-c#R=ZgcYZMl-HF#X)3oavhP>v4~$8+axy6%~(t|9`n%l0Bh0;I*q(ZKt@cF zSn}kSbsP?+(7B7XS>G7q*>b;#Nc}SY&C$0U-5Q(H)R$*2`aW&`NvHp&(b$*g_OTB& zE8%T^Jie=SAc4(QCTeUq+Bm=L;?hRNk*G?%3|hEUfFxGHTC9w>mKT5?UQw4i{{1%1 zb-jS6*k_ua(2o>&V@=wM&MMY+JQZvY&@woW^|L1F3zja(sZ$?>=6-s+?I^Z>LyXqVzc~c z$4y!yz5JxVaM|jR6o=4lVhc}K<*dS(l81|FUKQ&^4**(78fthOeJ@$3rw5TBiVvy= zBAiUN5e=+P>TD71b)4A>*Jds}@@82#nMXRE3U*6o-;^LqDB>P3sB*Qm*gGmK+~trOytF?H(t@5Nocm6J;x1Ff-Rj zobst38y6mlCL=zG+yIB;PO?U-=-gPvN>hTZ^B1p6yB6MDWkmW&;FnKQXKNnsqWPgw z%e%IMLbo@n{fBiRE9d4bb2AiC_)JB=LdVRs=ZV@O$cRt);Gb1l?UX(nxr)r{73$LV zDT}_YjGihAIy0}JRsNQ?po_SR1xku&<#b_W=?-JaW@PL0=AW5^4s(eKZ0;FNjg52~ zvCNN$8~OmlVma-iFk>g1B)?og7$17)^I*C48QI+riMq*LOj`t4Q+mX>K>GR_=(Ycu zwOM1keaBUPg?U>Tp^O~w#o~levm2|U^*}s0< z0N;^5WnHvoAF9IIY~*hsnwxwvjr7?nnr<+2HInt~p+vX)qx&iy-(y6*ZROnu9;mr@oq*tT~^vcOP1^`KeqIJ zWMM0M*80?9|MiSPE(FF774=AOmLEH;@OxB9-*y+8Tvq5aVQTCPRLy7ri>qkzHE(EKr0aZ$WqcWwE=zWg>SWtI|=cDP<>cQ(w$a9W@3Vzycue7>YzPUD)5%BCjX(Hp-VKU8-v@0 zxVD~ecU{{ll7BeRe<2jLv~&v~?p^R7jL>hI_@Pw`E^(6<{=MPmKX2Y-X4yj29#13- z_*BJek1t))sSyE(3i#onv~BEWcZX)#8jdxx3Y&yhz0)4Qxi^N!RA|7v84P0DxkgrI zXJ%;N={FyB=(w=p`i87K;B1nDTufdwLFKL6yQ8)OCqfiB?tAubH*UN$R5$(Y=KAa9 zf!*APj(@+&419@dqk<*2bFyXsea+Vrf*vp|4qz#F_jd0M(Uf39(e1{^g~5&xukv2E z-g>>aY|)VEY#o9&M`>=(D3!sp+DW?^6{|E!04xh8EpL0CnRxE{31Rk#A;1ruuXLnS zY*iYj@q|Zn){&7$k6$;2R1TfBj+@?Z?TPa9ID=|%RWlRaE%n<5s5Ih<<)Xfd4=2Lo zirOAsKj0z}>!e?o^GysH#pDO6=;*cCld({U>zf_Dpf(6oW(Sp(U9xsfeUFy)**@t` ze>32hZsKJUdjV~CHQ)c4@ERtzS>}6SrOs0sHcyE}4~@~WD-#bx;qrF0@4>Me`MnI1 z_k7x#SL~>@_Unu?MmO911zL6H0JWRsZ4|E)l`FF?A~FtJ6{DzY{*&G&-a5ZHfP|3S zjp09-T*YuBC_|(*E&ZA6X63z|Sz`Qno0sGs>wk{g*3VlwtCf@5lc~o#OIJ0}b}|8p zhhu&|&+(NbRdAJm7^af~C9T^1onynV2{@_AzkZC3j7J!e{piJoY1Cj1MvYkE7suLe zE1J!HborB!ZYzK5`ybt!T5h#L-l=D;dARr4!4hlg8cifyC*;X1fgO>Ot+Mcc0IyE@ zt1q9cZr8HHq;93&t#7SqShFjfUJ997TFG2sgBM?7!+2`=Jx>_#gm(-NI@25OPvnqq z=6NqUn%a0;qfmA)(kt_Kx05GrVTog}(;}bP*o@^d{L&}YlIxw{T;qWBtTpF-NNVxp!RaX82}%1)wkD2?`uacm{O0xBy|{}84b4E)y40NG znd##xRO;N%$~3TmwiN`W?uS3{5G~*H;d0TTUkQ_tm#?W+zBZ$q9z-kR@+EcKm)qyW#mn|HW<#6Knh@;@ z-32^LzrAj(H6%L8_5M-~y4z)m@-~R6f}DRYf^1$HSCk8>CmB&KS78PE8Md#=(FywAN#Z4CZjmpSl?R zSe|qpF>x& zwy*e=ktYjfk;v+F+L-!OSn?j6ZK6oy=ho-fOfB?LQS^YRaI1IMbACp72fR6Ww7Tki z{>j0QeJpc+HI~t1QKpdUr)#RafMYI-OkV1%DeY=~0C;g+m~TJpOr?y=Pf4WK1VE{; zIbmaHT~E{rAVnFEn_7I$D=yYAMz#1lj@wE((%^9U7{bY)3W)+=SFZC8cb0zw86hjR zw3fc7)BT_*xaK4*@de|{)PpX8;{(a2uDaX4`s7yY_ic~wP9C~X z^<7Kyxw?ycpD+yFQPo>9sU~z1C?`Zzcs+trWXgl{<6s6-GUEEX-hC3j<5lF%S1X0o z2neX&KGi*5@OqXHfsyyV@U^VmvEsp6k-#1tCR9+jQi@a;=s8ISVA&$xBhXQ8(Kej& zj3<8H8%oVzIgHS`r-zbw$xD$E7jQ(+eh@X+mISh!)9(LLiP~+r68y~j@kp?sPj9 zxuSvYYl%%f@YlMzTVXNZrH{5rv<8_9YMS;#1vQ-A=rde@w92>^`mVAMBhJsL^DPpwO?;fAamR=9rrl ziHUm_dSP)pY!_R$tO^2iE+-_{_tM9->uHmElh#q;pjv%iv5L!Y%kx4o@Rm%bR70OT zlU>Gb(2W_1$ysIoLNS+Wjt+irdE`81tMK7*#YW70-6&&FVwfYdtnKYxp+X`v4Bt$h zaYH^~feq`-`Tw-sOf&@Cv;)Yq*WXREzyTm&CaexA{A8E zfDR^(xH|+x08q!&asL@U0`7QM^@P2H;0<*w*5TP-7_xNoBR)s#;;I9eNcadxWcZ$x zo1zQT+c|P*;ky<9qMC)zI88pzS$xNObkVSjwvs%)daLP{SZg&C233MDKwTfi5Yu`w z^c_pv`npzxomhpP+rEoMAo{TH+FOiwY5yz0C4I3yF4R!Rz1P2d@Og(dua4Bo$-^<9S)Lqw|kwhoq=Vw6@jG)kO6l>0$ z*ijU7BuddC`u2%cL7Xi80*fQ&Ie4l z3F)>T1CH$c15W1?TjQMzOr8UNiOd762jw9`Ec1_!(mn@LsL4^xXG6|VP zfxWWrLj2~Q1_dPNxl3jq5{iRd2Y>$yt2w(bIA3&j?D=`}(upQ&V)B09{F$pG6<&(v z;^xr7#MOL^+4pkIWv3Z@?UQ9o19c~S)$5NkA~hmszWs}b6N**6`46fD$!Epb$?bG5 zP^-`KJ+Y-sLiO|RU7QF7dg8>dm2XSWR#sk})49Wx$z9!h6JK1Ojrhm2|M+?QzSH^; z`HFX}3ck;OcXY@bVYRC?XASvdV(E;m13hMNZ4?V|t))_QU9Ba^Hw4NC-f0Gu2&)|m z2ui*CvrDXy>*S+ysip<7pl#qZv9Ks-Vdu)6v$li04~29s!IuX|5A09Y7+|Kyz}q+2 z*9*v|8l|82N&*t$@(SkO###zVoA! z9j$0^vU$>~>}!yXKB`Rm*CJC7u&^9Ff)puVT{}0@V3CJ8`6eHc&DigZ7qoCl7c}%L zD4h!HNqdgK6nmt*luiZbnw-;0qlAfdmJayDW>d>JQhi18-Q8)q7a+7Bd8Lwita_C2 zslrpb36GZb8(`FE^33Iv{Mt!1Jp|(F1fycfTJ+bo*yen4YC&8<88W!!W}cmQ)_`YW z^Ou}9KZf1xb!Y3Sxq#@&8ya$Ag!2sBm!p#*I}6J+!v`E`OufLuVgbRrt)u%ZwHXXo z*w_rMM1Dv|*&O<=YRk5;$`jVDxkM>4i-DeQ?@oK8ULAooeEhmaEI_#_m2MSJE$wxl z9xQCEv1?z$xcB;uss{DWTta^iL8lc?*7RNDRV5usi!QyJUH(XNyl5*vC8x3c;Y9Q` z0WR{WcV^k}jHt}^6;PEXT}{4OX2*CI$YsR4Ty6N|_#C7&=@YAgE+bPJWn(oJP(}MF z{aXKgXuki`wCtR)J7r6fU*$@BrgsoY6*`|?S#jn&x0EXSu+0@p6Xy-H_i4S5F~ zRS1*qq*)*5%D~bRqhxBSF+CukYrLk34M7Q8Bm~U|n`i$Tu-2;mr28qU&D$uuZfq$qECAAiL7{I;)2x@ytI zoEIekp1sJQm1PxX{Ka)rs%UIGF^rcRHyS@J%%8;OD(bzPGljT}qD!Qw_%O3AFct?Q z8vsagQGS|MPlLYbIHwC1q%-8sE_>MN@Kp>6^d!3ZqQ`yfdTMka{yg_Y5l*cd)Hy-| zwyJ_iHT0>_F!Bo>c)S4nHCS~LV7<=on$$D^IU{96Bl~47>nXtKtW~K6LNekvq6=ZMtH`8nA(}6wwW8$<;tDpYNqxaZf`P z&iQVA#+bLPl`3LXJf&A$(CF@V?&WiFQB4kOoMqw(77)x6?@YeO(C6(@)7NxboWPq~ zg)s(>+)no_JrfCXE7sJ7Yz2Rrhm+z`qcctW-arE$yi;7{S^w%x`5+|r@T8l&$^`-805mO~6CdLMQz&SI(?9P?MotIL+Y zx0u{fujYhx_bgVW<*zK9Tr_##n>OI@@BThHg!03|HC>AoIN_F?ODc%Vt6^p$Sy)gI z%0s-@+BPL{=*T>yTe8|RtLF~hSwZED!{r!Ri^D}O_UeTys6|w|S)^m4NJ>b-c1liL zB4dRRYV*$U{;5K#4ue$f5*6ICUM}wt-A3zrNY(>t(dl_3Pn))k<_UdrQ2)TiCeQiq ztz4sv-Q|3{adsT__ZJ17thB$bR0Z-wyC-TI%}>y7c#kw@%{dJu<&l3HFD!TL_u<%3 zGziQcj4SWkA%nc~oV%CJJ%X?Gv?kU*wOjOYxB9uzHiCELQU{i|iRkGXO=%*?k0_7C~>o<^**3WW(sBrgBHH>#kLKQ-Gm>~bPqu&R33 z>{X#$=a;2JMP}wXo;@0yeJKsa`BoNBLya2t(6ja1cp;Ik=?qWC6tFv88-Cy902Cp<~ZmBtiG9FxXQz zo#kDuEPGi+MH}$bh63=2LGZN(g!$~IUo6>FCl*fqUDtDC;|_ylBGYSO!JY$`O^*c6 zOQFd5EE)+I@_w?8+HB%-b6;Bemm7EL9F&`u(DS>P>f-H-ZA6_|{#cufb2~WWsS1fU z^;TD<%SY4g7!{Qrl(R|dF%peMS?cGOR_hi8r-+UD?IT@cLkMsW4yfPla1bdnsMa3+ z?d)d@%X^Oc9~#X*HC+pl58IGB}eHYm?7K3w7}ow7mk zC4SsWS8K-Rm4vaPDNwW?|kaM9BhsnCe zd;1F9M*#8D=ifvuj3ru~M^95eZ0|w^6_y64T=>RS?=@4wSA+(fXJOf^4C|iDp>W|9 zb$J(9+X+;03aQdDM8Y{%G2qWNgj?Vm0#mVlMNpoJwMJzonttOlG9b#gVdW(8;IgjM z9{Q=$6o}x*X(P7I>}NbUwOB2^j;iceB1fzowe8-7EsmmMB^XD3f@=lA?zeB-BJN@V zx1_`6n)M5Ek{E zzZ~Y>*l?qM`K)ZT?b0$AvqF#M?JJ0#S^mNNxT!`ua7zj{a?xwqO6uz|xHbx`lMkjE zSMe+LcIfY$>I>7N&ToPL&WuL_3B3Je=q8^G>fZ)%C1eA)AaqWefI5zK(8WwEFyl8Y zEF1s3iv?o5J_gf)g2N$TXx5AR4j|X1di|X?iislkio;ZsC7e4j6`?hcB`&yl#pp1< zSpUE>=Zdn?yiIR|>ex*cqlWR(rA~}zlHg0a+-s6`>u`Rc5VMAgN#!mcTxV%W;S-yd zxa$+f_*oY(!a-y_b!~KgCKd?jU(?H;9fB{{>|fjty!OO?mick&zo$!iouQTW*2!&hC3R+)j+eM15>pM zRUAa}TbHZ2c)dPu7mZGGGr?a7xqI25zxJ?^u*)MA%P6ePOUNv-@S>EFXWvKL{3*vu zS3!fq=T5FkRhA2MRW)LGfAZ^VpC8g}GkZwHB=y;@)fRu)i#sJQ%!qrOa^!pB1o4AK zQkBIFq+lBB#Q>GV=ksf`#bx+AG5^>x+^TB_%I2)Rr(U{y%9(tbR}w$jd#%>yg~!$E zkdy0HfSuUN`<4cRunVGZ2)Ur1<@L=YAjo*4SIssL0E}ZUWfHApZA0nkq-i$c!QBsJ zeAlu9YsNsmuK~J5&84P;C#R&;y_!)5Nx8JWzI)kP!(=I-{mXoME*W}CuTd$~Ht4L_ zPE7l!+9pR~wY?9+pBIL}WR$LFWadX3C@V#hkEr)cU@0v+z zIlm<37;>^PeM&vFOnf(pqg>Wly@>v33+ko~N>|h3Ufbv;-4E=K);}bi<4!nB?X!(` z(E`JjWUQ{~p+6=Dwg}LZXi*QaKpwm~BAWW84k6X}_L+MXDJ7Gp`P#os%E=u-_VOqA zx_Y{gPVels&KFh?*M+J*Ob2s9ymCTAGrCqLViW^B?|WQA+{FS*i{f)ONrx}%^NzLq zApYqO8|y!kBqx0Vr3`M9$(T-*mk;^mdtciI8*6GDFn-wd`C8G{+9|hf^}9LT8r?KT z>Pbr#xt&Ur)v=}E+Iwb`%3@zlxoKwMT`UvH1yToXseTL4JP&*ZrLi_2CFoW-bzvoK z_Nr^OuGQ^12jA{-59%|LMZ$d^-u1(TLU~mE7BWa0<>SUzTEmMnyw|<}Xh=FO3>AUO z`s#3yB#V#d#@R87X!HBxi%Z+RJv`B5!_ruF^0i(2T}xNz>>P^+8r7QjzL&oUVva__ zG;e5Wet%0fAKdE-Y*&&IKAO+v#?Ng;H;wkJwje^e38Ofx!o+r5q4ie|sB*Dlq|)+& z$}m^J-?jjX0Etu)M}j*^(nntedm13;3;qGOOde^dehK#ktmb+w?LJAYYr8i2laZop zO%2J<%tR+`&22VY@-;T4wvYm=@AEu?0BdyhwR=v_`=4OVH@8Lw-l_BV!3>pNiGq#E z$obhZN;Pdu0$DrLe;%|^TZ-J*-QgVgeQCpeg%Mm}-6@LE3_?iIjuAT!7~%XHR8-RD z_NY1m2&WS@ph1l`Jh1=qKuDi%K3|lFd7)8P{Y(ErLR-bCzMEg<11w}y@FS@B!OSV8 zEkEk4-|Qr|vJ(kC_uRWQ#&7jRWvCq8L36ubN8(BBjtw&^-C^>@)vb`xZwK7EF83XQ z*g)}^(;58A`_w-n@H^- zaq^txW+oXwonrn{z|4ftShlEtl&ZSe?^mNRL>i*6+EHI$SIl>XGJPN6rb2XGE7L5) zq^dTo%o!eBvuUtrL4h6@X>Y`-d|#3_s*Qa7p-k5U=mzB8_*o#~o`0E@dRbY4OGmOc zLyBINY~WKtp1WC;Ms-c?9n7EnYTe=Gzp|Wqj)%KB#bV;RxX=2EizUdg#2nJ?Lem(H zE@#dUO>OS+`YeGme3I5BrbgtDc&lj0^L``4X>jpO>BqU=DA~~^oA*Q7OtQOv?FKhO z1}whazU$2A;;w3~a9ikQ-T0`k3-{s~`+*lzPL3f~7dD_C%(7qVnI|UA^7{=T1SSH3 z@uZ#rnZEVn25wvz<&jx=i9fTvk!d7=l`2lEx-g_xQxnw-B8Nz`exL+T?X;z|L-#3aZ^%G_K?6?f37NNA&T0@lr>!|=!mi1}I0 zaQ^o4K90s~fWHi5X>7YR;T-D*{>lo11$&7H*B3)j_$J9qKUHsDLfQ9g*h#&7cnO_U z_2P}lg97vPTIHp#5(DF-8_T#OfvkoUz6AlC^gc(L%K7^d#(v`u6=V@bz7Dte&P>~p*G2(=wjTxnpkf=AnO0yj`56!XsgZ!vxs7NwQ|dsF8N2}1iy+p$ zE*(1H2ar-wm9w6;W&yxihglvVi3c$r1+%lLtnoFLGwFxn+9(i%l7j`jUJnlI{`D-< z?jD!obdx56+Z!wnlWYj^N1_DqmR6tkO02E1oWy4|oD8^a$pRkqX=9xNZHL?c2L$W1 AUjP6A delta 19649 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ⅆtId6&(h%zPA%;aUtWlt7h=$ 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 L>WYQ(kDvcP9!^_T From 3fd1a1575e492dd66a3b4b8372af95eb3d0a5439 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Thu, 10 Oct 2019 15:59:39 -0400 Subject: [PATCH 030/122] add brief description of tuf version management --- pep-0458.txt | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 5c299e56886..ad97c7bbaec 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -704,7 +704,7 @@ The project transaction and snapshot processes SHOULD work concurrently. Finally, project transaction processes SHOULD keep in memory the latest *bins* metadata so that they will be correctly updated in new consistent snapshots. -Signing updated snapshot, timestamp, and bin metadata needs to be done on each +Signing updated snapshot, timestamp, and bin metadata needs to be done on each update. Fortunately, the actual operation of signing is fast enough that this may be done a thousand or more times per second. However, locking must be used so that project transactions are handled sequentially. To achieve this, @@ -769,7 +769,7 @@ __ https://en.wikipedia.org/wiki/Transaction_log Cleaning up old metadata ------------------------ -Prior versions of snapshot, targets, and timestamp metadata does not need to +Prior versions of snapshot, targets, and timestamp metadata does not need to be kept indefinitely. (Root files must be indefinitely retained.) However, a client that performs an update MUST be able to retrieve a consistent set of versions of the files on the repository. @@ -788,7 +788,7 @@ Revoking Trust in Projects and Versions From time to time either a project or a version of a package will need to be revoked. To revoke trust in a version of a package, the bin role can simply remove the delegation and re-sign the bin metadata. Similarly, an entire project may be removed -by removing the bin metadata references to the metadata and package versions. +by removing the bin metadata references to the metadata and package versions. All of these actions only require actions with the online bin key. @@ -974,6 +974,18 @@ of a package at a specific version, they can be handled by TUF with techniques like implicit key revocation and metadata mismatch detection [2]. +Managing Future Changes to the Update Process +============================================= + +If breaking changes are made to the update process, PyPI should implement these +changes without disrupting existing clients. To do so, PyPI should maintain +previous versions of metadata so that clients can access the version of metadata +that corresponds with their TUF version. More details about how breaking changes +can be managed with TUF are discussed in the TAPs repository__. + +__ https://github.com/theupdateframework/taps/pull/107 + + Appendix A: Repository Attacks Prevented by TUF =============================================== From 648d50319b22def2fec913573097d6086e15738a Mon Sep 17 00:00:00 2001 From: marinamoore Date: Thu, 10 Oct 2019 16:43:50 -0400 Subject: [PATCH 031/122] remove details about version management --- pep-0458.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index d564fd2b5e0..b5fcfe2af14 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -465,7 +465,7 @@ PyPI and Key Requirements In this section, the kinds of keys required to sign for TUF roles on PyPI are examined. TUF is agnostic with respect to choices of digital signature algorithms. For the purpose of discussion, it is assumed that most digital -signatures will be produced the ed25519 algorithm [25]_ as this algorithm has +signatures will be produced the ed25519 algorithm [25]_ as this algorithm has native and well-tested Python support. Nevertheless, we do NOT recommend any particular digital signature algorithm in this PEP because there are a few important constraints: first, cryptography @@ -977,10 +977,9 @@ Managing Future Changes to the Update Process ============================================= If breaking changes are made to the update process, PyPI should implement these -changes without disrupting existing clients. To do so, PyPI should maintain -previous versions of metadata so that clients can access the version of metadata -that corresponds with their TUF version. More details about how breaking changes -can be managed with TUF are discussed in the TAPs repository__. +changes without disrupting existing clients. For guidance on how to do so, +see ongoing discussion in the TAP repository__. Note that the changes to PyPI +from this PEP will be backwards compatible. __ https://github.com/theupdateframework/taps/pull/107 From a0d89b341b6c3afec0dccf998ee527159d0ed3d2 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Thu, 10 Oct 2019 16:45:27 -0400 Subject: [PATCH 032/122] fix whitespace --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index b5fcfe2af14..5ea999f9a7b 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -465,7 +465,7 @@ PyPI and Key Requirements In this section, the kinds of keys required to sign for TUF roles on PyPI are examined. TUF is agnostic with respect to choices of digital signature algorithms. For the purpose of discussion, it is assumed that most digital -signatures will be produced the ed25519 algorithm [25]_ as this algorithm has +signatures will be produced the ed25519 algorithm [25]_ as this algorithm has native and well-tested Python support. Nevertheless, we do NOT recommend any particular digital signature algorithm in this PEP because there are a few important constraints: first, cryptography From c5e6ee98cb9842e7d4fe09f4bc58ac8562d8224f Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Thu, 10 Oct 2019 17:12:44 -0400 Subject: [PATCH 033/122] Add new coauthors to PEP 458 --- pep-0458.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index fa2c386e3c5..1f312cbe3f9 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -4,7 +4,10 @@ Version: $Revision$ Last-Modified: $Date$ Author: Trishank Karthik Kuppusamy , Vladimir Diaz , - Donald Stufft , Justin Cappos + Marina Moore , + Lukas Puehringer , + Donald Stufft , + Justin Cappos BDFL-Delegate: Donald Stufft Discussions-To: DistUtils mailing list Status: Draft From 8db57452e272a648dd2a66fc2d15c3f535549b54 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Thu, 10 Oct 2019 17:32:47 -0400 Subject: [PATCH 034/122] Fix #13 --- pep-0458.txt | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 1f312cbe3f9..542188cf4bd 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -361,23 +361,25 @@ __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/TUTORIAL.md#how-t How to Establish Initial Trust in the PyPI Root Keys ---------------------------------------------------- -Package managers like pip need to ship a file called "root.json" with the +Package managers like pip MUST ship the *root* metadata file with the installation files that users initially download. This includes information -about the keys trusted for certain roles, as well as the root keys themselves. -Any new version of "root.json" that clients may download are verified against -the root keys that client's initially trust. If a root key is compromised, but -a threshold of keys are still secured, the PyPI administrator MUST push a new -release that revokes trust in the compromised keys. If a threshold of root keys -are compromised, then "root.json" should be updated out-of-band, however the -threshold should be chosen so that this is extremely unlikely. The TUF client -library does not require manual intervention if root keys are revoked or added: -the update process handles the cases where "root.json" has changed. - -To bundle the software, "root.json" MUST be included in the version of pip -shipped with CPython (via ensurepip). The TUF client library then loads the -root metadata and downloads the rest of the roles, including updating -"root.json" if it has changed. An `outline of the update process`__ is -available. +about the keys trusted for all top-level roles (including the root keys themselves). +Any new version of *root* metadata that package managers may download are verified +against the root keys that the package managers initially trust. If a root key is +compromised, but a threshold of keys are still secured, the PyPI administrator MUST +push new *root* metadata that revokes trust in the compromised keys. If a threshold +of root keys are compromised, then the *root* metadata MUST be updated out-of-band. +(However, the threshold of root keys should be chosen so that this event is extremely +unlikely.) Package managers do not necessarily need to be updated immediately if root +keys are revoked or added between new releases of the package manager: the update process +automatically handles the cases where a threshold of previous *root* keys sign for new +*root* keys (assuming no backwards-incompatibility in the TUF specification used). + +Thus, to repeat, the latest good copy of *root* metadata MUST be included +in any new version of pip shipped with CPython (via ensurepip). The TUF client library +inside the package manager then loads the *root* metadata and downloads the rest of +the roles, including updating the *root* metadata if it has changed. +An `outline of the update process`__ is available. __ https://github.com/theupdateframework/specification/blob/master/tuf-spec.md#5-detailed-workflows. From b65878084657af33c377b6a13fc2d824fbc1f770 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Thu, 10 Oct 2019 17:42:31 -0400 Subject: [PATCH 035/122] clarifications --- pep-0458.txt | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 542188cf4bd..904276c8b4a 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -364,21 +364,27 @@ How to Establish Initial Trust in the PyPI Root Keys Package managers like pip MUST ship the *root* metadata file with the installation files that users initially download. This includes information about the keys trusted for all top-level roles (including the root keys themselves). -Any new version of *root* metadata that package managers may download are verified -against the root keys that the package managers initially trust. If a root key is -compromised, but a threshold of keys are still secured, the PyPI administrator MUST -push new *root* metadata that revokes trust in the compromised keys. If a threshold -of root keys are compromised, then the *root* metadata MUST be updated out-of-band. +Package managers must also bundle a TUF client library. Any new version of *root* +metadata that the TUF client library may download are verified against the root keys +that the package manager was initially bundled with. If a root key is compromised, +but a threshold of keys are still secured, then PyPI administrators MUST push new +*root* metadata that revokes trust in the compromised keys. If a threshold of root +keys are compromised, then the *root* metadata MUST be updated out-of-band. (However, the threshold of root keys should be chosen so that this event is extremely unlikely.) Package managers do not necessarily need to be updated immediately if root -keys are revoked or added between new releases of the package manager: the update process -automatically handles the cases where a threshold of previous *root* keys sign for new -*root* keys (assuming no backwards-incompatibility in the TUF specification used). - -Thus, to repeat, the latest good copy of *root* metadata MUST be included -in any new version of pip shipped with CPython (via ensurepip). The TUF client library -inside the package manager then loads the *root* metadata and downloads the rest of -the roles, including updating the *root* metadata if it has changed. +keys are revoked or added between new releases of the package manager: the TUF update +process automatically handles the cases where a threshold of previous *root* keys sign +for new *root* keys (assuming no backwards-incompatibility in the TUF specification +used). So, for example, if a package manager was initially shipped with version 1 of +the *root* metadata, and a threshold of *root* keys in version 1 signed version 2 of +the *root metadata*, and a threshold of *root* keys in version 2 signed version 3 of +the *root metadata, then the package manager should be able to transparently update +its copy of the *root* metadata from version 1 to 3 using its TUF client library. + +Thus, to repeat, the latest good copy of *root* metadata and a TUF client library MUST +be included in any new version of pip shipped with CPython (via ensurepip). The TUF +client library inside the package manager then loads the *root* metadata and downloads +the rest of the roles, including updating the *root* metadata if it has changed. An `outline of the update process`__ is available. __ https://github.com/theupdateframework/specification/blob/master/tuf-spec.md#5-detailed-workflows. From c8b0657301026f5845675005a410235f0ef8a212 Mon Sep 17 00:00:00 2001 From: lukpueh Date: Fri, 11 Oct 2019 13:29:46 +0200 Subject: [PATCH 036/122] Refine roles disambiguation Co-Authored-By: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> --- pep-0458.txt | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index c27aaf14d68..0f4e6f342b8 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -166,7 +166,7 @@ Terms used in this PEP are defined as follows: * Roles: There is one *root* role in PyPI. There are multiple roles whose responsibilities are delegated to them directly or indirectly by the *root* role. The term top-level role refers to the *root* role and any role - delegated directly by the *root* role, i.e. *timestamp*, *snapshot* and + specified directly by the *root* role, i.e. *timestamp*, *snapshot* and *targets* roles. Each role has a single metadata file that it is trusted to provide. @@ -338,7 +338,7 @@ delegated to by *bins* (i.e., *bin-n*). The *timestamp* and *snapshot* metadata MUST be updated whenever *root*, *targets* or delegated metadata are updated. Observe, though, that *root* and *targets* metadata are much less likely to be updated as often as delegated metadata. Similarly, the *bins* role -will only be updated when a new *bin-n* role is added. Therefore, *timestamp*, +will only be updated when a *bin-n* role is added, updated, or removed. Therefore, *timestamp*, *snapshot*, and *bin-n* metadata will most likely be updated frequently (possibly every minute) due to delegated metadata being updated frequently in order to support continuous delivery of projects. Continuous delivery is a set of processes @@ -640,9 +640,9 @@ include a version number in their filename: the delegated targets roles -- *bins* or *bin-n*. The only exception is the *timestamp* metadata file, whose version is not known -in advance, when a client performs an update. Instead the *timestamp* metadata -serves as version root. That is, it lists the -version of the *snapshot* metadata, which in turn lists the versions of *root*, +in advance, when a client performs an update. The *timestamp* metadata +lists the +version of the *snapshot* metadata, which in turn lists the versions of the *targets* and delegated targets metadata, all part of a given consistent snapshot. @@ -689,16 +689,16 @@ on how PyPI will respond to a project transaction. When a project uploads a new transaction, the project transaction process MUST add all new targets and relevant delegated *bin-n* metadata. Finally, the -project transaction process MUST inform the snapshot process about new -delegated *bin-n* metadata. +project transaction process MUST inform the snapshot process about any new +*bin-n* metadata. Project transaction processes SHOULD be automated and MUST also be applied atomically: either all metadata and targets -- or none of them -- are added. The project transaction and snapshot processes SHOULD work concurrently. -Finally, project transaction processes SHOULD keep in memory the latest *bin-n* +Finally, project transaction processes SHOULD use the latest *bin-n* metadata so that they will be correctly updated in new consistent snapshots. -Signing updated *snapshot*, *timestamp*, and *bin-n* metadata needs to be done on each +Signing updated *timestamp*, *snapshot*, and *bin-n* metadata needs to be done on each update. Fortunately, the actual operation of signing is fast enough that this may be done a thousand or more times per second. However, locking must be used so that project transactions are handled sequentially. To achieve this, @@ -720,8 +720,8 @@ Snapshot Process ---------------- The snapshot process is fairly simple and SHOULD be automated. The snapshot -process MUST keep in memory the latest working set of *root*, *targets*, and -delegated targets roles (i.e. *bins* and *bin-n* roles). Upon an update, the +process SHOULD use the latest working set of the *targets* and all +delegated targets roles' (i.e. *bins* and *bin-n* roles) metadata. Upon an update, the snapshot process will sign for this latest working set. (Recall that project transaction processes continuously inform the snapshot process about the latest delegated metadata in a From 7b71fabefbe481bc03d0d85d32ce9c6e57700590 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Fri, 11 Oct 2019 14:34:31 +0100 Subject: [PATCH 037/122] Fix link for detailed workflows --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 59d3e580d5b..b5128da842c 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -385,7 +385,7 @@ client library inside the package manager then loads the *root* metadata and dow the rest of the roles, including updating the *root* metadata if it has changed. An `outline of the update process`__ is available. -__ https://github.com/theupdateframework/specification/blob/master/tuf-spec.md#5-detailed-workflows. +__ https://github.com/theupdateframework/specification/blob/master/tuf-spec.md#5-detailed-workflows Minimum Security Model From 7c21271676cfb23a08f3050373e85a0fc28a5063 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Fri, 11 Oct 2019 14:35:18 +0100 Subject: [PATCH 038/122] simple clarification --- pep-0458.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index b5128da842c..43e9f893466 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -474,8 +474,8 @@ PyPI and Key Requirements In this section, the kinds of keys required to sign for TUF roles on PyPI are examined. TUF is agnostic with respect to choices of digital signature algorithms. For the purpose of discussion, it is assumed that most digital -signatures will be produced the ed25519 algorithm [25]_ as this algorithm has -native and well-tested Python support. +signatures will be produced with the ed25519 algorithm [25]_ as this algorithm +has native and well-tested Python support. Nevertheless, we do NOT recommend any particular digital signature algorithm in this PEP because there are a few important constraints: first, cryptography changes over time; and second, TUF From 95157d3fa83642e0e26a2cd3f6ed50cbb2eb02d9 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Fri, 11 Oct 2019 14:37:08 +0100 Subject: [PATCH 039/122] Emphasise the need to be familiar with the TUF spec Even by the definitions section a reader will be confused without a passing familiarity with the spec, change the wording to be more explicit to those who aren't intimately familiar with RFC 2119 --- pep-0458.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 43e9f893466..f750f666081 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -147,8 +147,8 @@ interpreted as described in RFC 2119__. __ http://www.ietf.org/rfc/rfc2119.txt This PEP focuses on integrating TUF with PyPI; however, the reader is -encouraged to read about TUF's design principles [2]_. It is also RECOMMENDED -that the reader be familiar with the TUF specification [16]_. +encouraged to read about TUF's design principles [2]_ and SHOULD be +familiar with the TUF specification [16]_. Terms used in this PEP are defined as follows: From 68a782666333e257bf7a681c1fe6f6a373e475c6 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Fri, 11 Oct 2019 16:46:12 +0200 Subject: [PATCH 040/122] Clarify that targets file paths must be relative Clarify that file paths of target files must be listed relative to a root URL in the targets metadata, to allow serving target files from anywhere. This change also removes outdated information about the PyPI API, see https://github.com/pypa/warehouse/blob/master/docs/api-reference/legacy.rst. --- pep-0458.txt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 59d3e580d5b..90fe11b826a 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -303,13 +303,12 @@ and *targets*. The *root* role specifies the public cryptographic keys of the top-level roles (including its own). The *timestamp* role references the latest *snapshot* and can signify when a new snapshot of the repository is available. The *snapshot* role indicates the latest version of all the TUF -metadata files (other than *timestamp*). The *targets* role lists the -available target files (in our case, it will be all files on PyPI under the -/simple and /packages directories). These target files do not need to be -URIs or relative files on the same repository as long as they can be accessed -by anyone performing an update. Each top-level role will serve its -responsibilities without exception. Figure 1 provides a table of the roles -used in TUF. +metadata files (other than *timestamp*). The *targets* role lists the file +paths of available target file together with their hashes. The file paths must +be specified relative to a base URL. This allows the actual target files to be +served from anywhere, as long as the base URL can be accessed by the client. +Each top-level role will serve its responsibilities without exception. Figure +1 provides a table of the roles used in TUF. .. image:: pep-0458-1.png From 7c670d898aaf086e0664990228efdc6d1d5f4942 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Fri, 11 Oct 2019 16:14:16 +0100 Subject: [PATCH 041/122] Apply suggestions from code review Co-Authored-By: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> --- pep-0458.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index f750f666081..318fdbb0ee3 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -473,8 +473,8 @@ PyPI and Key Requirements In this section, the kinds of keys required to sign for TUF roles on PyPI are examined. TUF is agnostic with respect to choices of digital signature -algorithms. For the purpose of discussion, it is assumed that most digital -signatures will be produced with the ed25519 algorithm [25]_ as this algorithm +algorithms. For the purpose of discussion, it is assumed that all digital +signatures will be produced with the Ed25519 algorithm [25]_ as this algorithm has native and well-tested Python support. Nevertheless, we do NOT recommend any particular digital signature algorithm in this PEP because there are a few important constraints: first, cryptography From 73e9d8e078e0268b3fd5a7dd512cb4ce91ba73ca Mon Sep 17 00:00:00 2001 From: lukpueh Date: Fri, 11 Oct 2019 17:29:57 +0200 Subject: [PATCH 042/122] Update pep-0458.txt Co-Authored-By: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 90fe11b826a..34d4c5f3703 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -304,7 +304,7 @@ top-level roles (including its own). The *timestamp* role references the latest *snapshot* and can signify when a new snapshot of the repository is available. The *snapshot* role indicates the latest version of all the TUF metadata files (other than *timestamp*). The *targets* role lists the file -paths of available target file together with their hashes. The file paths must +paths of available target files together with their hashes. The file paths must be specified relative to a base URL. This allows the actual target files to be served from anywhere, as long as the base URL can be accessed by the client. Each top-level role will serve its responsibilities without exception. Figure From 61f94d51bc7fb0c25f098d9c9ae9cde2bb080b32 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Fri, 11 Oct 2019 11:51:40 -0400 Subject: [PATCH 043/122] explain why this pep is backwards compatible --- pep-0458.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 5ea999f9a7b..e402942e246 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -978,11 +978,16 @@ Managing Future Changes to the Update Process If breaking changes are made to the update process, PyPI should implement these changes without disrupting existing clients. For guidance on how to do so, -see ongoing discussion in the TAP repository__. Note that the changes to PyPI -from this PEP will be backwards compatible. +see ongoing discussion in the TAP repository__. __ https://github.com/theupdateframework/taps/pull/107 +Note that the changes to PyPI from this PEP will be backwards compatible. The +location of targets files and simple indices are not changed in this PEP, so any +existing PyPI clients will still be able to perform updates using these files. +This PEP adds the ability for clients to use TUF metadata to improve the +security of the update process. + Appendix A: Repository Attacks Prevented by TUF =============================================== From bb868674bf5c57a683779868499e4b31a7905181 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Tue, 15 Oct 2019 16:55:41 -0400 Subject: [PATCH 044/122] talk about when to double number of bins --- pep-0458.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pep-0458.txt b/pep-0458.txt index e6cb0c9be40..99d3b0c2361 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -457,6 +457,17 @@ pip for the first time. __ https://docs.google.com/spreadsheets/d/11_XkeHrf4GdhMYVqpYWsug6JNz5ZK6HvvmDZX0__K2I/edit?usp=sharing +This number of bins SHOULD double when the metadata overhead for returning +users exceeds 50% (relative to the average size of downloaded packages). +Presently, this SHOULD happen when the number of targets (simple indices +and packages) increase at least 4x from over 2M to nearly 9M, at which +point the metadata overhead for returning and new users would be around +49-54% and 185% respectively, assuming that the number of bins stay fixed. +If the number of bins is increased, then the cost for all users would +effectively be the cost for new users. If the cost for new users should +prove to be too much, then this subject SHOULD be revisited before that +happens. + It is possible to make TUF metadata more compact by representing it in a binary format as opposed to the JSON text format. Nevertheless, a sufficiently large number of projects and distributions will introduce scalability challenges at From fbc326b572b0a6b1aecbb0f00b01dcfa5f624655 Mon Sep 17 00:00:00 2001 From: Justin Cappos Date: Tue, 15 Oct 2019 17:59:01 -0400 Subject: [PATCH 045/122] Fixing #24 This clarifies that package and project revocations are the same --- pep-0458.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index e6cb0c9be40..42227df8915 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -790,10 +790,9 @@ Revoking Trust in Projects and Versions ======================================= From time to time either a project or a version of a package will need to be revoked. -To revoke trust in a version of a package, the bin role can simply remove the -delegation and re-sign the bin metadata. Similarly, an entire project may be removed -by removing the bin metadata references to the metadata and package versions. -All of these actions only require actions with the online bin key. +To revoke trust in either a project or a version of a package, the bin role can +simply remove the delegation and re-sign the bin metadata. This action only +requires actions with the online bin key. From 03d63347a16667f10884f3ad54905073618e1606 Mon Sep 17 00:00:00 2001 From: jhdalek55 Date: Tue, 15 Oct 2019 18:05:16 -0400 Subject: [PATCH 046/122] LD line edits 1015 --- pep-0458.txt | 309 +++++++++++++++++++++++++-------------------------- 1 file changed, 153 insertions(+), 156 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index e6cb0c9be40..1d800bf9588 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -19,39 +19,39 @@ Created: 27-Sep-2013 Abstract ======== -This PEP proposes how the Python Package Index (PyPI [1]_) should be integrated -with The Update Framework [2]_ (TUF). TUF was designed to be a flexible +This PEP proposes how The Update Framework [2]_ (TUF) should be integrated with the +Python Package Index (PyPI [1]_). TUF was designed to be a flexible security add-on to a software updater or package manager. The framework -integrates best security practices such as separating role responsibilities, +integrates best security practices, such as separating role responsibilities, adopting the many-man rule for signing packages, keeping signing keys offline, -and revocation of expired or compromised signing keys. For example, attackers -would have to steal multiple signing keys stored independently to compromise -a role responsible for specifying a repository's available files. Another role -responsible for indicating the latest snapshot of the repository may have to be -similarly compromised, and independent of the first compromised role. - -The proposed integration will allow modern package managers such as pip [3]_ to -be more secure against various types of security attacks on PyPI and protect +and revocation of expired or compromised signing keys. As a result, attackers +would need to steal multiple signing keys, which are stored independently, +in order to compromise the role responsible for specifying a repository's available +files. Or, alternatively, a role +responsible for indicating the latest snapshot of the repository may also have to be +compromised. + +The proposed integration will allow modern package managers, such as pip [3]_ to +be more secure against security attacks on PyPI, and to better protect users from such attacks. Specifically, this PEP describes how PyPI processes should be adapted to generate and incorporate TUF metadata (i.e., the minimum security model). The minimum security model supports verification of PyPI -distributions that are signed with keys stored on PyPI: distributions uploaded -by developers are signed by PyPI, require no action from developers (other than +distributions that are signed with keys stored on PyPI. Distributions that are +uploaded by developers are signed by PyPI, requiring no action from developers (other than uploading the distribution), and are immediately available for download. The minimum security model also minimizes PyPI administrative responsibilities by automating much of the signing process. -This PEP does not prescribe how package managers such as pip should be adapted +This PEP does not prescribe how package managers, such as pip, should be adapted to install or update projects from PyPI with TUF metadata. Package managers -interested in adopting TUF on the client side may consult TUF's `library -documentation`__, which exists for this purpose. Support for project -distributions that are signed by developers (maximum security model) is also -not discussed in this PEP, but is outlined in the appendix as a possible future -extension and covered in detail in PEP 480 [26]_. The PEP 480 extension -focuses on the maximum security model, which requires more PyPI administrative -work (none by clients), but it also proposes an easy-to-use key management -solution for developers, how to interface with a potential future build farm on -PyPI infrastructure, and discusses the feasibility of end-to-end signing. +interested in adopting TUF on the client side may consult its `library +documentation`__, which was created for this purpose. There is also no discussion in this PEP of support for project +distributions that are signed by developers (maximum security model). But, this +alternative model is outlined in the appendix as a possible future +extension, and is covered in detail in PEP 480 [26]_. This model requires more PyPI administrative +work (though no added work for clients), but it also proposes an easy-to-use key management +solution for developers, ideas on how to interface with a potential future build farm on +PyPI infrastructure, and the feasibility of end-to-end signing. __ https://github.com/theupdateframework/tuf/tree/v0.11.1/tuf/client#updaterpy @@ -68,11 +68,11 @@ implement the PEP. The Python Software Foundation secured this funding Motivation ========== -In January 2013, the Python Software Foundation (PSF) announced [4]_ that the -python.org wikis for Python, Jython, and the PSF were subjected to a security -breach that caused all of the wiki data to be destroyed on January 5, 2013. -Fortunately, the PyPI infrastructure was not affected by this security breach. -However, the incident is a reminder that PyPI should take defensive steps to +On January 5, 2013, the Python Software Foundation (PSF) announced that [4]_a security +breach had occurred on the +python.org wikis for Python, Jython, and as a result all of the wiki data was destroyed. +Fortunately, the PyPI infrastructure was not affected by this breach. +However, the incident is a reminder that PyPI needed to take defensive steps to protect users as much as possible in the event of a compromise. Attacks on software repositories happen all the time [5]_. The PSF must accept the possibility of security breaches and prepare PyPI accordingly because it is a @@ -82,23 +82,25 @@ Before the wiki attack, PyPI used MD5 hashes to tell package managers, such as pip, whether or not a package was corrupted in transit. However, the absence of SSL made it hard for package managers to verify transport integrity to PyPI. It was therefore easy to launch a man-in-the-middle attack between pip and -PyPI, and change package content arbitrarily. Users could be tricked into -installing malicious packages with man-in-the-middle attacks. After the wiki +PyPI, and change package content arbitrarily. As a result, users could be tricked into +installing malicious packages. After the wiki attack, several steps were proposed (some of which were implemented) to deliver -a much higher level of security than was previously the case: requiring SSL to +a much higher level of security than was previously the case. These steps included +requiring SSL to communicate with PyPI [6]_, restricting project names [7]_, and migrating from MD5 to SHA-2 hashes [8]_. -These steps, though necessary, are insufficient because attacks are still +Though necessary, these steps are insufficient to protect package because attacks are still possible through other avenues. For example, a public mirror is trusted to -honestly mirror PyPI, but some mirrors may misbehave due to malice or accident. +honestly mirror PyPI, but some mirrors may misbehave, whether by accident or through +malicious intervention. Package managers such as pip are supposed to use signatures from PyPI to verify packages downloaded from a public mirror [9]_, but none are known to actually do so [10]_. Therefore, it would be wise to add more security measures to detect attacks from public mirrors or content delivery networks [11]_ (CDNs). -Even though official mirrors are being deprecated on PyPI [12]_, there remain a -wide variety of other attack vectors on package managers [13]_. These attacks +Even though official mirrors are being deprecated on PyPI [12]_, a +wide variety of other attack vectors on package managers remain [13]_. These attacks can crash client systems, cause obsolete packages to be installed, or even allow an attacker to execute arbitrary code. In `September 2013`__, a post was made to the Distutils mailing list showing that the latest version of pip (at @@ -113,9 +115,8 @@ __ https://mail.python.org/pipermail/distutils-sig/2013-September/022755.html With the intent to protect PyPI against infrastructure compromises, this PEP proposes integrating PyPI with The Update Framework [2]_ (TUF). TUF helps -secure new or existing software update systems. Software update systems are -vulnerable to many known attacks, including those that can result in clients -being compromised or crashed. TUF solves these problems by providing a flexible +secure new or existing software update systems that can result in clients +being compromised or crashed. It solves these problems by providing a flexible security framework that can be added to software updaters. @@ -131,10 +132,10 @@ The threat model assumes the following: * Attackers can respond to client requests. -An attacker is considered successful if they can cause a client to install (or -leave installed) something other than the most up-to-date version of the -software the client is updating. If the attacker is preventing the installation -of updates, they want clients to not realize there is anything wrong. +An attacker is considered successful if it can cause a client to install (or +leave installed) something other than the most up-to-date version of a +software package or file. If the attacker is preventing the installation +of updates, they do not want clients to realize there is anything wrong. Definitions @@ -146,8 +147,8 @@ interpreted as described in RFC 2119__. __ http://www.ietf.org/rfc/rfc2119.txt -This PEP focuses on integrating TUF with PyPI; however, the reader is -encouraged to read about TUF's design principles [2]_ and SHOULD be +This PEP focuses only on integrating TUF into PyPI. However, the reader is +encouraged to review TUF's design principles [2]_ and SHOULD be familiar with the TUF specification [16]_. Terms used in this PEP are defined as follows: @@ -166,9 +167,8 @@ Terms used in this PEP are defined as follows: * Simple index: The HTML page that contains internal links to the distributions of a project [17]_. -* Roles: There is one *root* role in PyPI. There are multiple roles whose - responsibilities are delegated to them directly or indirectly by the *root* - role. The term top-level role refers to the *root* role and any role +* Roles: There is one *root* role in PyPI. There are multiple other roles for which the *root* role delegates + responsibilities, directly or indirectly. The term top-level role refers to the *root* role and any role specified directly by the *root* role, i.e. *timestamp*, *snapshot* and *targets* roles. Each role has a single metadata file that it is trusted to provide. @@ -176,7 +176,7 @@ Terms used in this PEP are defined as follows: * Metadata: Metadata are signed files that describe roles, other metadata, and target files. -* Repository: A repository is a resource comprised of named metadata and target +* Repository: A repository is a source for named metadata and target files. Clients request metadata and target files stored on a repository. * Consistent snapshot: A set of TUF metadata and PyPI targets that capture the @@ -184,7 +184,7 @@ Terms used in this PEP are defined as follows: time. * Developer: Either the owner or maintainer of a project who is allowed to - update the TUF metadata as well as distribution metadata and files for the + update the TUF metadata, as well as distribution metadata and files for the project. * Online key: A private cryptographic key that MUST be stored on the PyPI @@ -207,13 +207,13 @@ Terms used in this PEP are defined as follows: Overview of TUF =============== -At its highest level, TUF provides applications with a secure method of -obtaining files and knowing when new versions of files are available. On the +At its highest level, TUF provides applications with a secure method for +knowing about and obtaining new versions of files. On the surface, this all sounds simple. The basic steps for updating applications are: -* Knowing when an update exists. +* Knowing an update exists. -* Downloading a correct copy of the latest version of an updated file. +* Downloading a correct copy of the latest version of the updated file. The problem is that updating applications is only simple when there are no malicious activities in the picture. If an attacker is trying to interfere with @@ -224,7 +224,7 @@ that try to be secure). It downloads both the file it wants and a cryptographic signature of the file. The software updater already knows which key it trusts to make the signature. It checks that the signature is correct and was made by this trusted key. Unfortunately, the software updater is still at risk in many -ways, including: +ways, including the following scenarios: * An attacker keeps giving the software updater the same update file, so it never realizes there is an update. @@ -233,8 +233,8 @@ ways, including: that it already has, so it downloads that one and blindly uses it thinking it is newer. -* An attacker gives the software updater a newer version of a file it has but - it is not the newest one. The file is newer to the software updater, but it +* An attacker gives the software updater a newer version of a file, but + not the newest one. The file is newer to the software updater, but it may be insecure and exploitable by the attacker. * An attacker compromises the key used to sign these files and now the software @@ -246,12 +246,12 @@ referencing the metadata files during the update procedure. Repository files are verified against the information included in the metadata before they are handed off to the software update system. The framework also provides multi-signature trust, explicit and implicit revocation of cryptographic keys, -responsibility separation of the metadata, and minimizes key risk. For a full +responsibility separation of the metadata, and minimized key risk. For a full list and outline of the repository attacks and software updater weaknesses addressed by TUF, see Appendix A. -Integrating TUF with PyPI +Integrating PyPI with TUF ========================= A software update system must complete two main tasks to integrate with TUF. @@ -259,16 +259,15 @@ First, it must add the framework to the client side of the update system. For example, TUF MAY be integrated with the pip package manager. Second, the repository on the server side MUST be modified to provide signed TUF metadata. This PEP is concerned with the second part of the integration, and the changes -required on PyPI to support software updates with TUF. +on PyPI required to support software updates with TUF. What Additional Repository Files are Required on PyPI? ------------------------------------------------------ In order for package managers like pip to download and verify packages with -TUF, a few extra files MUST exist on PyPI. These extra repository files are -called TUF metadata. TUF metadata contains information such as which keys are -trustable, the cryptographic hashes of files, signatures to the metadata, +TUF, a few extra files MUST be added to PyPI. These extra repository files are +called TUF metadata, and they contain such information as which keys can be trusted, the cryptographic hashes of files, signatures to the metadata, metadata version numbers, and the date after which the metadata should be considered expired. @@ -280,8 +279,8 @@ from PyPI. TUF downloads them and checks them against the TUF metadata that it also downloads from the repository. If the downloaded target files are trustworthy, TUF then hands them over to the package manager. -The `Metadata`__ document provides information about each of the required -metadata and their expected content. The next section covers the different +The `Metadata`__ document provides information about each type of required +metadata and its expected content. The next section covers the different kinds of metadata RECOMMENDED for PyPI. __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/METADATA.md @@ -292,11 +291,10 @@ PyPI and TUF Metadata TUF metadata provides information that clients can use to make update decisions. For example, a *targets* metadata lists the available distributions -on PyPI and includes the distribution's signatures, cryptographic hashes, and -file sizes. Different metadata files provide different information. The -various metadata files are signed by different roles, which are indicated by -the *root* role. The concept of roles allows TUF to delegate responsibilities -to multiple roles and minimizes the impact of a compromised role. +on PyPI and includes for each the required signatures, cryptographic hashes, and +file sizes. Different metadata files provide different information, which are signed by separate roles. +The *root* role indicates what metadata belongs to each role. The concept of roles allows TUF to delegate responsibilities +to multiple roles, thus minimizing the impact of any one compromised role. TUF requires four top-level roles. These are *root*, *timestamp*, *snapshot*, and *targets*. The *root* role specifies the public cryptographic keys of the @@ -322,12 +320,12 @@ The top-level *root* role signs for the keys of the top-level *timestamp*, *snapshot*, *targets*, and *root* roles. The *timestamp* role signs for every new snapshot of the repository metadata. The *snapshot* role signs for *root*, *targets*, and all delegated targets roles. The delegated targets role *bins* -further delegates to the *bin-n* roles, which sign for all distributions +further delegatees to the *bin-n* roles, which sign for all distributions belonging to registered PyPI projects. Figure 2 provides an overview of the roles available within PyPI, which includes the top-level roles and the roles delegated to by *targets*. The figure -also indicates the types of keys used to sign each role and which roles are +also indicates the types of keys used to sign each role, and which roles are trusted to sign for files available on PyPI. The next two sections cover the details of signing repository files and the types of keys used for each role. @@ -362,15 +360,15 @@ Package managers like pip MUST ship the *root* metadata file with the installation files that users initially download. This includes information about the keys trusted for all top-level roles (including the root keys themselves). Package managers must also bundle a TUF client library. Any new version of *root* -metadata that the TUF client library may download are verified against the root keys -that the package manager was initially bundled with. If a root key is compromised, +metadata that the TUF client library may download is verified against the root keys +initially bundled with the package manager. If a root key is compromised, but a threshold of keys are still secured, then PyPI administrators MUST push new *root* metadata that revokes trust in the compromised keys. If a threshold of root keys are compromised, then the *root* metadata MUST be updated out-of-band. (However, the threshold of root keys should be chosen so that this event is extremely unlikely.) Package managers do not necessarily need to be updated immediately if root -keys are revoked or added between new releases of the package manager: the TUF update -process automatically handles the cases where a threshold of previous *root* keys sign +keys are revoked or added between new releases of the package manager, as the TUF update +process automatically handles cases where a threshold of previous *root* keys sign for new *root* keys (assuming no backwards-incompatibility in the TUF specification used). So, for example, if a package manager was initially shipped with version 1 of the *root* metadata, and a threshold of *root* keys in version 1 signed version 2 of @@ -390,9 +388,9 @@ __ https://github.com/theupdateframework/specification/blob/master/tuf-spec.md#5 Minimum Security Model ---------------------- -There are two security models to consider when integrating TUF with PyPI. The +There are two security models to consider when integrating TUF into PyPI. The one proposed in this PEP is the minimum security model, which supports -verification of PyPI distributions that are signed with private cryptographic +verification of PyPI distributions signed with private cryptographic keys stored on PyPI. Distributions uploaded by developers are signed by PyPI and immediately available for download. A possible future extension to this PEP, discussed in Appendix B, proposes the maximum security model and allows a @@ -403,10 +401,10 @@ The minimum security model requires no action from a developer and protects against malicious CDNs [19]_ and public mirrors. To support continuous delivery of uploaded packages, PyPI signs for projects with an online key. This level of security prevents projects from being accidentally or -deliberately tampered with by a mirror or a CDN because the mirror or CDN will -not have any of the keys required to sign for projects. However, it does not -protect projects from attackers who have compromised PyPI, since attackers can -manipulate TUF metadata using the keys stored online. +deliberately tampered with by a mirror or a CDN because neither will +have any of the keys required to sign for projects. However, it does not +protect projects from attackers who have compromised PyPI, since they can +then manipulate TUF metadata using the keys stored online. This PEP proposes that the *bin-n* roles sign for all PyPI projects with online keys. The *targets* role, which only signs with an @@ -433,7 +431,7 @@ that are highly skewed or adrift. Metadata Scalability -------------------- -Due to the growing number of projects and distributions, TUF metadata will also +As the number of projects and distributions on a repository grows, TUF metadata will need to grow correspondingly. For example, consider the *bins* role. In August 2013, it was found that the size of the *bins* metadata was about 42MB if the *bins* role itself signed for about 220K PyPI targets (which are simple indices and @@ -447,9 +445,9 @@ install or upgrade a PyPI project via TUF. __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/TUTORIAL.md#delegate-to-hashed-bins -Based on our findings as of the time of updating it for implementation +Based on our findings as of the time of the time this document was updated for implementation (Oct 7 2019), PyPI SHOULD split all targets in the *bins* role by delegating -them to 16,384 *bin-n* roles, each of which would sign for PyPI targets whose +them to 16,384 *bin-n* roles. Each *bin-n* role would sign for the PyPI targets whose hashes fall into that bin (see Figure 2). It was found__ that this number of bins would result in a 12-17% metadata overhead for returning users, and a 148% overhead for new users who are installing @@ -458,7 +456,7 @@ pip for the first time. __ https://docs.google.com/spreadsheets/d/11_XkeHrf4GdhMYVqpYWsug6JNz5ZK6HvvmDZX0__K2I/edit?usp=sharing It is possible to make TUF metadata more compact by representing it in a binary -format as opposed to the JSON text format. Nevertheless, a sufficiently large +format, as opposed to the JSON text format. Nevertheless, a sufficiently large number of projects and distributions will introduce scalability challenges at some point, and therefore the *bins* role will still need delegations (as outlined in figure 2) in order to address the problem. Furthermore, the JSON @@ -473,7 +471,7 @@ PyPI and Key Requirements In this section, the kinds of keys required to sign for TUF roles on PyPI are examined. TUF is agnostic with respect to choices of digital signature algorithms. For the purpose of discussion, it is assumed that all digital -signatures will be produced with the Ed25519 algorithm [25]_ as this algorithm +signatures will be produced with the Ed25519 algorithm [25]_ as it has native and well-tested Python support. Nevertheless, we do NOT recommend any particular digital signature algorithm in this PEP because there are a few important constraints: first, cryptography @@ -506,7 +504,7 @@ the *targets* role be permanently discarded as soon as they have been created and used to sign for the role. Therefore, the *targets* role SHOULD require (2, 2) keys. Again, this is because the keys are going to be permanently discarded, and more offline keys will not help resist key recovery -attacks [21]_ unless diversity of cryptographic algorithms is maintained. +attacks [21]_ unless the diversity of cryptographic algorithms is maintained. For similar reasons, the keys for the *bins* role SHOULD be set up similar to the keys for the *targets* role. @@ -515,7 +513,7 @@ In order to support continuous delivery, the keys for the *timestamp*, *snapshot*, and all *bin-n* roles MUST be online. There is little benefit in requiring all of these roles to use different online keys, since attackers would presumably be able to compromise all of them if they compromise PyPI. -Therefore, it is reasonable to use one online key for them all. +Therefore, it is reasonable to use one online key for all of them. Managing online keys @@ -554,7 +552,7 @@ Managing offline keys ---------------------- As explained in the previous section, the *root*, *targets*, and *bins* role -keys MUST be offline for maximum security: these keys will be offline in the +keys MUST be offline for maximum security. These keys will be offline in the sense that their private keys MUST NOT be stored on PyPI, though some of them MAY be online in the private infrastructure of the project. @@ -565,7 +563,7 @@ top-level TUF roles). Thus, keys SHOULD be generated—preferably in a physical location where side-channel attacks__ are not a concern—using: 1. A trusted, airgapped__ computer with a true random number generator__, and - with no **data** persisted after the ceremony + with no **data** persisting after the ceremony 2. A trusted operating system 3. A trusted set of third-party packages (e.g., cryptographic libraries, the TUF reference implementation) @@ -574,7 +572,7 @@ __ https://en.wikipedia.org/wiki/Side-channel_attack __ https://en.wikipedia.org/wiki/Air_gap_(networking) __ https://en.wikipedia.org/wiki/Hardware_random_number_generator -In order to avoid persisting sensitive data (e.g., private keys) other than +In order to avoid the persistence of sensitive data (e.g., private keys) other than on backup media after the ceremony, offline keys SHOULD be generated encrypted using strong passwords, either on (in decreasing order of trust): private HSMs (e.g., YubiHSM__), cloud-based HSMs (e.g., those listed above), @@ -586,18 +584,18 @@ backed up the keys. __ https://www.yubico.com/products/yubihsm/ Passwords used to encrypt keys SHOULD be stored somewhere durable and -trustworthy where only Python admins have access. +trustworthy to which only Python admins have access. In order to minimize OPSEC__ errors during the ceremony, scripts SHOULD be -written to automate tedious parts such as: +written to automate tedious parts, such as: - Exporting to sneakernet__ all code and data (e.g., previous TUF metadata, targets, and *root* keys) required to generate new keys and replace old ones -- Tighten the firewall, update the entire operating system in order to - fix security vulnerabilities, and airgap the computer -- Print and save cryptographic hashes of new TUF metadata -- Export *all* new TUF metadata, targets, and keys to encrypted backup media -- Export *only* new TUF metadata, targets, and online keys to encrypted backup +- Tightening the firewall, updating the entire operating system in order to + fix security vulnerabilities, and airgapping the computer +- Printing and saving cryptographic hashes of new TUF metadata +- Exporting *all* new TUF metadata, targets, and keys to encrypted backup media +- Exporting *only* new TUF metadata, targets, and online keys to encrypted backup media __ https://en.wikipedia.org/wiki/Operations_security @@ -605,7 +603,7 @@ __ https://en.wikipedia.org/wiki/Sneakernet Note the one-time keys for the *targets* and *bins* roles MAY be safely generated, used, and deleted during the offline key ceremony. Furthermore, -the *root* keys MAY not be generated during the offline key ceremony itself: +the *root* keys MAY not be generated during the offline key ceremony itself. instead, a threshold t of n Python administrators, as discussed above, may independently sign the *root* metadata **after** the offline key ceremony used to generate all other keys. @@ -623,13 +621,13 @@ same metadata or distributions. There are also issues with consistency on PyPI without TUF, but the problem is more severe with signed metadata that MUST keep track of the files available on PyPI in real-time. -Suppose that PyPI generates a *snapshot*, which indicates the latest version of -every metadata except *timestamp*, at version 1 and a client requests this +Suppose that PyPI generates a *snapshot* that indicates the latest version of +every metadata, except *timestamp*, at version 1 and a client requests this *snapshot* from PyPI. While the client is busy downloading this *snapshot*, PyPI then timestamps a new snapshot at, say, version 2. Without ensuring consistency of metadata, the client would find itself with a copy of *snapshot* -that disagrees with what is available on PyPI, which is indistinguishable from -arbitrary metadata injected by an attacker. The problem would also occur for +that disagrees with what is available on PyPI. The result would be indistinguishable from +arbitrary metadata injected by an attacker. The problem would also occur with mirrors attempting to sync with PyPI. Consistent Snapshots @@ -649,11 +647,11 @@ include a version number in their filename: top-level metadata roles -- *root*, *snapshot* or *targets* -- or one of the delegated targets roles -- *bins* or *bin-n*. -The only exception is the *timestamp* metadata file, whose version is not known -in advance, when a client performs an update. The *timestamp* metadata +The only exception is the *timestamp* metadata file, whose version would not be known +in advance when a client performs an update. The *timestamp* metadata lists the version of the *snapshot* metadata, which in turn lists the versions of the -*targets* and delegated targets metadata, all part of a given consistent +*targets* and delegated targets metadata, all as part of a given consistent snapshot. Eventually, *targets* or delegated targets metadata point to the actual target @@ -681,29 +679,28 @@ snapshot of all projects and the associated metadata at a given time. The next subsection provides implementation details of this idea. Note: This PEP does not prohibit using advanced file systems or tools to -produce consistent snapshots. There are two important reasons for why this PEP -proposes the simple solution. First, the solution does not mandate that PyPI +produce consistent snapshots. There are two important reasons for proposing a simple solution in this PEP. +First, the solution does not mandate that PyPI use any particular file system or tool. Second, the generic file-system based -approach allows mirrors to use extant file transfer tools such as rsync to +approach allows mirrors to use extant file transfer tools, such as rsync, to efficiently transfer consistent snapshots from PyPI. Producing Consistent Snapshots ------------------------------ -Given a project, PyPI is responsible for updating the *bin-n* metadata. Every +When given a project, PyPI is responsible for updating the *bin-n* metadata. Every project MUST upload its release in a single transaction. The uploaded set of -files is called the "project transaction". How PyPI MAY validate the files in +files is called the "project transaction." How PyPI MAY validate the files in a project transaction is discussed in a later section. For now, the focus is -on how PyPI will respond to a project transaction. +on how PyPI responds to a project transaction. When a project uploads a new transaction, the project transaction process MUST -add all new targets and relevant delegated *bin-n* metadata. Finally, the -project transaction process MUST inform the snapshot process about any new +add all new targets and relevant delegated *bin-n* metadata. The process then MUST inform the snapshot process about any new *bin-n* metadata. Project transaction processes SHOULD be automated and MUST also be applied -atomically: either all metadata and targets -- or none of them -- are added. +atomically. This means either all metadata and targets -- or none of them -- are added. The project transaction and snapshot processes SHOULD work concurrently. Finally, project transaction processes SHOULD use the latest *bin-n* metadata so that they will be correctly updated in new consistent snapshots. @@ -731,7 +728,7 @@ Snapshot Process The snapshot process is fairly simple and SHOULD be automated. The snapshot process SHOULD use the latest working set of the *targets* and all -delegated targets roles' (i.e. *bins* and *bin-n* roles) metadata. Upon an update, the +delegated targets roles (i.e. *bins* and *bin-n* roles) metadata. Upon an update, the snapshot process will sign for this latest working set. (Recall that project transaction processes continuously inform the snapshot process about the latest delegated metadata in a @@ -741,26 +738,26 @@ with information that is continuously communicated by the project transaction processes.) The snapshot process MUST generate and sign new *timestamp* metadata that will vouch for the metadata (*root*, *targets*, and delegated roles) generated in the previous step. Finally, the snapshot process MUST make -available to clients the new *timestamp* and *snapshot* metadata representing -the latest snapshot. +the new *timestamp* and *snapshot* metadata representing +the latest snapshot available to clients. -A few implementation notes are now in order. So far, we have seen only that +At this stage, its important to acknowledge a few implementation notes. So far, we have seen only that new metadata and targets are added, but not that old metadata and targets are removed. Practical constraints are such that eventually PyPI will run out of disk space to produce a new consistent snapshot. In that case, PyPI MAY then use something like a "mark-and-sweep" algorithm to delete sufficiently old -consistent snapshots: in order to preserve the latest consistent snapshot, PyPI -would walk objects beginning from the root (*timestamp*) of the latest +consistent snapshots. In order to preserve the latest consistent snapshot, PyPI +should walk objects beginning from the root (*timestamp*) of the latest consistent snapshot, mark all visited objects, and delete all unmarked objects. The last few consistent snapshots may be preserved in a similar fashion. Deleting a consistent snapshot will cause clients to see nothing except HTTP 404 responses to any request for a file within that consistent snapshot. -Clients SHOULD then retry (as before) their requests with the latest consistent +Clients SHOULD then retry their requests (as before) with the latest consistent snapshot. -All clients, such as pip using the TUF protocol, MUST be modified to download +All clients, such as pip, using the TUF protocol MUST be modified to download every metadata and target file (except for *timestamp* metadata) by including, -in the request for the file, the version of the file (for metadata), or the +in the file request, the version of the file (for metadata), or the cryptographic hash of the file (for target files) in the filename. Finally, PyPI SHOULD use a `transaction log`__ to record project transaction @@ -781,9 +778,9 @@ For example, if a client downloads a snapshot file, it should retrieve the versi of targets metadata that existed when that snapshot file was created, even if the targets metadata has been updated concurrently with the client requests. Fortunately, the use of hash / version delegations handle this case automatically -since clients request targets unambiguously. Once no clients could reasonably +since clients request targets unambiguously. Once no client could reasonably be requesting outdated targets, snapshot or timestamp files, those files may be -removed to save space. Thus files that were obsoleted some reasonable time +removed to save space. Thus, files that were obsoleted some reasonable time in the past (such as 1 hour) may be safely discarded. Revoking Trust in Projects and Versions @@ -793,7 +790,7 @@ From time to time either a project or a version of a package will need to be rev To revoke trust in a version of a package, the bin role can simply remove the delegation and re-sign the bin metadata. Similarly, an entire project may be removed by removing the bin metadata references to the metadata and package versions. -All of these actions only require actions with the online bin key. +All of these actions only require the online bin key to complete the task. @@ -802,7 +799,7 @@ Key Compromise Analysis This PEP has covered the minimum security model, the TUF roles that should be added to support continuous delivery of distributions, and how to generate and -sign the metadata of each role. The remaining sections discuss how PyPI +sign the metadata for each role. The remaining sections discuss how PyPI SHOULD audit repository metadata, and the methods PyPI can use to detect and recover from a PyPI compromise. @@ -810,7 +807,7 @@ Table 1 summarizes a few of the attacks possible when a threshold number of private cryptographic keys (belonging to any of the PyPI roles) are compromised. The leftmost column lists the roles (or a combination of roles) that have been compromised, and the columns to its right show whether the -compromised roles leaves clients susceptible to malicious updates, a freeze +compromised roles leave clients susceptible to malicious updates, a freeze attack, or metadata inconsistency attacks. +-----------------+-------------------+----------------+--------------------------------+ @@ -853,7 +850,7 @@ attack, or metadata inconsistency attacks. Table 1: Attacks possible by compromising certain combinations of role keys. In `September 2013`__, it was shown how the latest version (at the time) of pip -was susceptible to these attacks and how TUF could protect users against them +was susceptible to these attacks and how TUF could protect users against them [14]_. __ https://mail.python.org/pipermail/distutils-sig/2013-September/022755.html @@ -861,16 +858,16 @@ __ https://mail.python.org/pipermail/distutils-sig/2013-September/022755.html Note that compromising *targets* or any delegated role (except for project targets metadata) does not immediately allow an attacker to serve malicious updates. The attacker must also compromise the *timestamp* and *snapshot* -roles (which are both online and therefore more likely to be compromised). -This means that in order to launch any attack, one must not only be able to -act as a man-in-the-middle but also compromise the *timestamp* key (or +roles, which are both online and therefore more likely to be compromised. +This means that, in order to launch any attack, one must not only be able to +act as a man-in-the-middle, but also compromise the *timestamp* key (or compromise the *root* keys and sign a new *timestamp* key). To launch any attack other than a freeze attack, one must also compromise the *snapshot* key. Finally, a compromise of the PyPI infrastructure MAY introduce malicious updates to *bins* projects because the keys for these roles are online. The maximum security model discussed in the appendix addresses this issue. PEP 480 -also covers the maximum security model and goes into more detail on generating +also covers the maximum security model and goes into more detail about generating developer keys and signing uploaded distributions. @@ -878,7 +875,7 @@ In the Event of a Key Compromise -------------------------------- A key compromise means that a threshold of keys (belonging to the metadata -roles on PyPI), as well as the PyPI infrastructure, have been compromised and +roles on PyPI), as well as the PyPI infrastructure have been compromised and used to sign new metadata on PyPI. If a threshold number of *timestamp*, *snapshot*, *targets*, *bins* or *bin-n* @@ -894,7 +891,7 @@ keys have been compromised, then PyPI MUST take the following steps: *targets* metadata). 3. All targets of the *bin-n* roles SHOULD be compared with the last known - good consistent snapshot where none of the *timestamp*, *snapshot*, + good consistent snapshot in which none of the *timestamp*, *snapshot*, *bins* or *bin-n* keys were known to have been compromised. Added, updated or deleted targets in the compromised consistent snapshot that do not match the last known good @@ -907,11 +904,11 @@ keys have been compromised, then PyPI MUST take the following steps: 5. A new timestamped consistent snapshot MUST be issued. -Following these steps would preemptively protect all of these roles even though +Following these steps would preemptively protect all of these roles, even if only one of them may have been compromised. If a threshold number of *root* keys have been compromised, then PyPI MUST take -above steps and in addition replace all *root* keys in the *root* role. +the above steps and also replace all *root* keys in the *root* role. It is also RECOMMENDED that PyPI sufficiently document compromises with security bulletins. These security bulletins will be most informative when @@ -925,7 +922,7 @@ because a threshold number of existing *root* keys will be used to sign for the integrity of the new *root* metadata. TUF clients will be able to verify the integrity of the new *root* metadata with a threshold number of previously known *root* keys. This will be the common case. Otherwise, in the worst -case, where a threshold number of *root* keys have been revoked due to a +case, in which a threshold number of *root* keys have been revoked due to a compromise, an end-user may choose to update new *root* metadata with `out-of-band`__ mechanisms. @@ -960,7 +957,7 @@ In order to safely restore snapshots in the event of a compromise, PyPI SHOULD maintain a small number of its own mirrors to copy PyPI snapshots according to some schedule. The mirroring protocol can be used immediately for this purpose. The mirrors must be secured and isolated such that they are -responsible only for mirroring PyPI. The mirrors can be checked against one +responsible only for mirroring PyPI. The mirrors can be checked against one another to detect accidental or malicious failures. Another approach is to generate the cryptographic hash of *snapshot* @@ -982,7 +979,7 @@ Managing Future Changes to the Update Process If breaking changes are made to the update process, PyPI should implement these changes without disrupting existing clients. For guidance on how to do so, -see ongoing discussion in the TAP repository__. +see the ongoing discussion in the TAP repository__. __ https://github.com/theupdateframework/taps/pull/107 @@ -996,14 +993,14 @@ security of the update process. Appendix A: Repository Attacks Prevented by TUF =============================================== -* **Arbitrary software installation**: An attacker installs anything they want +* **Arbitrary software installation**: An attacker installs anything it wants on the client system. That is, an attacker can provide arbitrary files in - respond to download requests and the files will not be detected as + response to download requests and the files will not be detected as illegitimate. * **Rollback attacks**: An attacker presents a software update system with - older files than those the client has already seen, causing the client to use - files older than those the client knows about. + files older than those the client has already seen. This causes the client to use + outdated files. * **Indefinite freeze attacks**: An attacker continues to present a software update system with the same files the client has already seen. The result is @@ -1018,7 +1015,7 @@ Appendix A: Repository Attacks Prevented by TUF update process. * **Extraneous dependencies attacks**: An attacker indicates to clients that in - order to install the software they wanted, they also need to install + order to install the software they want, they also need to install unrelated software. This unrelated software can be from a trusted source but may have known vulnerabilities that are exploitable by the attacker. @@ -1036,9 +1033,9 @@ Appendix A: Repository Attacks Prevented by TUF * **Vulnerability to key compromises**: An attacker who is able to compromise a single key or less than a given threshold of keys can compromise clients. - This includes relying on a single online key (such as only being protected - by SSL) or a single offline key (such as most software update systems use - to sign files). + This includes relying on a single online key, such as only being protected + by SSL, or a single offline key, as most software update systems use + to sign files. Appendix B: Extension to the Minimum Security Model @@ -1090,7 +1087,7 @@ The maximum security model relies on developers signing their projects and uploading signed metadata to PyPI. If the PyPI infrastructure were to be compromised, attackers would be unable to serve malicious versions of claimed projects without access to the project's developer key. Figure 3 depicts the -changes made to figure 2, namely that developer roles are now supported and +changes made to Figure 2, namely that developer roles are now supported, and that three new delegated roles exist: *claimed*, *recently-claimed*, and *unclaimed*. The *bins* role has been renamed *unclaimed* and can contain any projects that have not been added to *claimed*. The strength of this model @@ -1151,7 +1148,7 @@ projects hosted externally: will gather the external distribution's file size and hashes and generate appropriate TUF metadata. -3. External projects MUST submit to PyPI the file size and cryptographic hash +3. External projects MUST submit the file size and cryptographic hash to PyPI for a distribution. 4. External projects MUST upload to PyPI a developer public key for the @@ -1160,9 +1157,9 @@ projects hosted externally: client will fetch the external TUF metadata as part of the package update process. -5. External projects MUST upload to PyPI signed TUF metadata (as allowed by +5. External projects MUST upload signed TUF metadata to PyPI (as allowed by the maximum security model) about the distributions that they host - externally, and a developer public key. Package managers verify + externally, along with a developer public key. Package managers verify distributions by consulting the signed metadata uploaded to PyPI. Only one of the options listed above should be implemented on PyPI. Option @@ -1222,11 +1219,11 @@ for helping us to think about how to usably and efficiently integrate TUF with PyPI. Roger Dingledine, Sebastian Hahn, Nick Mathewson, Martin Peck and Justin Samuel -helped us to design TUF from its predecessor Thandy of the Tor project. +helped us to design TUF from its predecessor, Thandy of the Tor project. We appreciate the efforts of Konstantin Andrianov, Geremy Condra, Zane Fisher, -Justin Samuel, Tian Tian, Santiago Torres, John Ward, and Yuyu Zheng to -develop TUF. +Justin Samuel, Tian Tian, Santiago Torres, John Ward, and Yuyu Zheng in +developing TUF. Vladimir Diaz, Monzur Muhammad and Sai Teja Peddinti helped us to review this PEP. From e3599473983dcc745ec429efdc3d7d81adce7297 Mon Sep 17 00:00:00 2001 From: Justin Cappos Date: Wed, 16 Oct 2019 10:29:14 -0400 Subject: [PATCH 047/122] Update pep-0458.txt --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 42227df8915..2cc760622ca 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -792,7 +792,7 @@ Revoking Trust in Projects and Versions From time to time either a project or a version of a package will need to be revoked. To revoke trust in either a project or a version of a package, the bin role can simply remove the delegation and re-sign the bin metadata. This action only -requires actions with the online bin key. +requires actions with the online bin-n key. From d37029bf8f60f9d1bcd1191fb043c08321e87ad6 Mon Sep 17 00:00:00 2001 From: Justin Cappos Date: Wed, 16 Oct 2019 10:29:22 -0400 Subject: [PATCH 048/122] Update pep-0458.txt --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 2cc760622ca..2469817687b 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -790,7 +790,7 @@ Revoking Trust in Projects and Versions ======================================= From time to time either a project or a version of a package will need to be revoked. -To revoke trust in either a project or a version of a package, the bin role can +To revoke trust in either a project or a version of a package, the associated bin-n role can simply remove the delegation and re-sign the bin metadata. This action only requires actions with the online bin-n key. From fa1ab4ff376aad276288618ec907ca838f47af7c Mon Sep 17 00:00:00 2001 From: Justin Cappos Date: Wed, 16 Oct 2019 10:29:28 -0400 Subject: [PATCH 049/122] Update pep-0458.txt --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 2469817687b..578bb1573e1 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -791,7 +791,7 @@ Revoking Trust in Projects and Versions From time to time either a project or a version of a package will need to be revoked. To revoke trust in either a project or a version of a package, the associated bin-n role can -simply remove the delegation and re-sign the bin metadata. This action only +simply remove the delegation and re-sign the bin-n metadata. This action only requires actions with the online bin-n key. From b1f1cb00f606d6c2776824b7643f005db3f43f42 Mon Sep 17 00:00:00 2001 From: Justin Cappos Date: Wed, 16 Oct 2019 11:49:45 -0400 Subject: [PATCH 050/122] Update pep-0458.txt Co-Authored-By: lukpueh --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 578bb1573e1..f5b2afb4117 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -791,7 +791,7 @@ Revoking Trust in Projects and Versions From time to time either a project or a version of a package will need to be revoked. To revoke trust in either a project or a version of a package, the associated bin-n role can -simply remove the delegation and re-sign the bin-n metadata. This action only +simply remove the corresponding targets and re-sign the bin-n metadata. This action only requires actions with the online bin-n key. From a04d0e3cd649e4c80aa3aa58970d84457dfc1f6f Mon Sep 17 00:00:00 2001 From: Lois Anne DeLong Date: Wed, 16 Oct 2019 17:16:59 -0400 Subject: [PATCH 051/122] Update pep-0458.txt Co-Authored-By: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 64c88685af8..5a2a74425ba 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -320,7 +320,7 @@ The top-level *root* role signs for the keys of the top-level *timestamp*, *snapshot*, *targets*, and *root* roles. The *timestamp* role signs for every new snapshot of the repository metadata. The *snapshot* role signs for *root*, *targets*, and all delegated targets roles. The delegated targets role *bins* -further delegatees to the *bin-n* roles, which sign for all distributions +further delegates to the *bin-n* roles, which sign for all distributions belonging to registered PyPI projects. Figure 2 provides an overview of the roles available within PyPI, which From 35c33a9752255fa22d86d43a23eb441a76c9aba2 Mon Sep 17 00:00:00 2001 From: Lois Anne DeLong Date: Wed, 16 Oct 2019 17:17:32 -0400 Subject: [PATCH 052/122] Update pep-0458.txt Co-Authored-By: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 5a2a74425ba..cd3b1111f3e 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -445,7 +445,7 @@ install or upgrade a PyPI project via TUF. __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/TUTORIAL.md#delegate-to-hashed-bins -Based on our findings as of the time of the time this document was updated for implementation +Based on our findings as of the time this document was updated for implementation (Oct 7 2019), PyPI SHOULD split all targets in the *bins* role by delegating them to 16,384 *bin-n* roles. Each *bin-n* role would sign for the PyPI targets whose hashes fall into that bin (see Figure 2). It was found__ From b407046415ba09cd43d37d7f1fd219896fcc7788 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Wed, 16 Oct 2019 17:23:08 -0400 Subject: [PATCH 053/122] Add Lois to authors --- pep-0458.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/pep-0458.txt b/pep-0458.txt index cd3b1111f3e..61adc60c8aa 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -6,6 +6,7 @@ Author: Trishank Karthik Kuppusamy , Vladimir Diaz , Marina Moore , Lukas Puehringer , + Lois Anne DeLong , Donald Stufft , Justin Cappos BDFL-Delegate: Donald Stufft From 7826d7a77d70580fc6e7006758f802144940189f Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Wed, 16 Oct 2019 20:14:26 -0400 Subject: [PATCH 054/122] keep @lukpueh and @mnm678 happy --- pep-0458.txt | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 99d3b0c2361..6ed6a342038 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -451,22 +451,24 @@ Based on our findings as of the time of updating it for implementation (Oct 7 2019), PyPI SHOULD split all targets in the *bins* role by delegating them to 16,384 *bin-n* roles, each of which would sign for PyPI targets whose hashes fall into that bin (see Figure 2). It was found__ -that this number of bins would result in a 12-17% metadata overhead for -returning users, and a 148% overhead for new users who are installing -pip for the first time. +that this number of bins would result in a 12-17% metadata overhead +(relative to the average size of downloaded packages) for returning users +(assuming 256-byte target filenames for all packages), and a 148% overhead +for new users who are installing pip for the first time. __ https://docs.google.com/spreadsheets/d/11_XkeHrf4GdhMYVqpYWsug6JNz5ZK6HvvmDZX0__K2I/edit?usp=sharing This number of bins SHOULD double when the metadata overhead for returning -users exceeds 50% (relative to the average size of downloaded packages). -Presently, this SHOULD happen when the number of targets (simple indices -and packages) increase at least 4x from over 2M to nearly 9M, at which -point the metadata overhead for returning and new users would be around -49-54% and 185% respectively, assuming that the number of bins stay fixed. -If the number of bins is increased, then the cost for all users would -effectively be the cost for new users. If the cost for new users should -prove to be too much, then this subject SHOULD be revisited before that -happens. +users exceeds 50%. Presently, this SHOULD happen when the number of targets +increase at least 4x from over 2M to nearly 9M, at which point the metadata +overhead for returning and new users would be around 49-54% (assuming 256-byte +target filenames for all packages) and 185% respectively, assuming that the +number of bins stay fixed. If the number of bins is increased, then the cost +for all users would effectively be the cost for new users, because their cost +would be dominated by the (once-in-a-while) cost of downloading the large +number of delegations in the `bins` metadata. If the cost for new users +should prove to be too much, then this subject SHOULD be revisited before +that happens. It is possible to make TUF metadata more compact by representing it in a binary format as opposed to the JSON text format. Nevertheless, a sufficiently large From 504841acb652d4cc0133f28a67737c68b18e5ca1 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Wed, 16 Oct 2019 21:57:15 +0100 Subject: [PATCH 055/122] PEP 480: drop 'Appendix B: Extension to the Minimum Security Model' The Maximum Security Model is more fully documented in PEP 480. Rather than try to keep this appendix and that document up-to-date and in-sync refer readers to PEP 480 to understand the maximum security model --- pep-0458-3.png | Bin 35954 -> 0 bytes pep-0458.txt | 110 ++++++------------------------------------------- 2 files changed, 13 insertions(+), 97 deletions(-) delete mode 100644 pep-0458-3.png diff --git a/pep-0458-3.png b/pep-0458-3.png deleted file mode 100644 index 99edad82f6ac582c760b96530af101264f4eef6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35954 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 Date: Wed, 16 Oct 2019 15:54:33 +0100 Subject: [PATCH 056/122] PEP 458: clarify that we are recommending Ed25519 --- pep-0458.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 61adc60c8aa..1abea0205ec 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -471,13 +471,11 @@ PyPI and Key Requirements In this section, the kinds of keys required to sign for TUF roles on PyPI are examined. TUF is agnostic with respect to choices of digital signature -algorithms. For the purpose of discussion, it is assumed that all digital -signatures will be produced with the Ed25519 algorithm [25]_ as it -has native and well-tested Python support. -Nevertheless, we do NOT recommend any particular digital signature algorithm in -this PEP because there are a few important constraints: first, cryptography -changes over time; and second, TUF -recommends diversity of keys for certain applications. +algorithms. However, this PEP RECOMMENDS that all digital signatures be +produced with the Ed25519 algorithm [25]_. Ed25519 has native and +well-tested Python support (allowing for verification of signatures without +additional, non-Python, dependencies), uses small keys, and is supported +by modern HSM and authentication token hardware. Number and Type Of Keys Recommended From 9353b3876b796bb98669c8e9b4a0dbc11da86541 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Wed, 16 Oct 2019 13:00:52 +0100 Subject: [PATCH 057/122] PEP 458: clarify snapshot metadata compression The snapshot metadata compression should be handled at a separate layer, as TUF doesn't provide compression (see TAP 10). --- pep-0458.txt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index d78dccfe29e..1e87528fc0d 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -457,14 +457,16 @@ pip for the first time. __ https://docs.google.com/spreadsheets/d/11_XkeHrf4GdhMYVqpYWsug6JNz5ZK6HvvmDZX0__K2I/edit?usp=sharing -It is possible to make TUF metadata more compact by representing it in a binary -format, as opposed to the JSON text format. Nevertheless, a sufficiently large +While it is possible to make TUF metadata more compact by representing it in a +binary format, as opposed to the JSON text format, a sufficiently large number of projects and distributions will introduce scalability challenges at some point, and therefore the *bins* role will still need delegations (as -outlined in figure 2) in order to address the problem. Furthermore, the JSON -format is an open and well-known standard for data interchange. Due to the -large number of delegated metadata, compressed versions of *snapshot* metadata -SHOULD also be made available to clients. +outlined in figure 2) in order to address the problem. The JSON format is an +open and well-known standard for data interchange, which is already supported by +the TUF reference implementation, and therefore the recommended data format by +this PEP. However, due to the large number of delegations, compressed +versions of all metadata SHOULD also be made available to clients via the +existing Warehouse mechanisms for HTTP compression. PyPI and Key Requirements From 7cd6f5fa6893f306c867044bfe9b4acbfcec7540 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Tue, 15 Oct 2019 16:55:41 -0400 Subject: [PATCH 058/122] talk about when to double number of bins --- pep-0458.txt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 1e87528fc0d..d85856b8187 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -457,8 +457,19 @@ pip for the first time. __ https://docs.google.com/spreadsheets/d/11_XkeHrf4GdhMYVqpYWsug6JNz5ZK6HvvmDZX0__K2I/edit?usp=sharing -While it is possible to make TUF metadata more compact by representing it in a -binary format, as opposed to the JSON text format, a sufficiently large +This number of bins SHOULD increase when the metadata overhead for returning +users exceeds 50% (relative to the average size of downloaded packages). +Presently, this SHOULD happen when the number of targets (simple indices +and packages) increase at least 4x from over 2M to nearly 9M, at which +point the metadata overhead for returning and new users would be around +49-54% and 185% respectively, assuming that the number of bins stay fixed. +If the number of bins is increased, then the cost for all users would +effectively be the cost for new users. If the cost for new users should +prove to be too much, then this subject SHOULD be revisited before that +happens. + +It is possible to make TUF metadata more compact by representing it in a binary +format, as opposed to the JSON text format. Nevertheless, a sufficiently large number of projects and distributions will introduce scalability challenges at some point, and therefore the *bins* role will still need delegations (as outlined in figure 2) in order to address the problem. The JSON format is an From e87eb1c04e08ddd865cf0e5e0c5b7c6b8cc0d6df Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Wed, 16 Oct 2019 20:14:26 -0400 Subject: [PATCH 059/122] keep @lukpueh and @mnm678 happy --- pep-0458.txt | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index d85856b8187..887af429da4 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -451,22 +451,24 @@ Based on our findings as of the time this document was updated for implementatio (Oct 7 2019), PyPI SHOULD split all targets in the *bins* role by delegating them to 16,384 *bin-n* roles. Each *bin-n* role would sign for the PyPI targets whose hashes fall into that bin (see Figure 2). It was found__ -that this number of bins would result in a 12-17% metadata overhead for -returning users, and a 148% overhead for new users who are installing -pip for the first time. +that this number of bins would result in a 12-17% metadata overhead +(relative to the average size of downloaded packages) for returning users +(assuming 256-byte target filenames for all packages), and a 148% overhead +for new users who are installing pip for the first time. __ https://docs.google.com/spreadsheets/d/11_XkeHrf4GdhMYVqpYWsug6JNz5ZK6HvvmDZX0__K2I/edit?usp=sharing This number of bins SHOULD increase when the metadata overhead for returning -users exceeds 50% (relative to the average size of downloaded packages). -Presently, this SHOULD happen when the number of targets (simple indices -and packages) increase at least 4x from over 2M to nearly 9M, at which -point the metadata overhead for returning and new users would be around -49-54% and 185% respectively, assuming that the number of bins stay fixed. -If the number of bins is increased, then the cost for all users would -effectively be the cost for new users. If the cost for new users should -prove to be too much, then this subject SHOULD be revisited before that -happens. +users exceeds 50%. Presently, this SHOULD happen when the number of targets +increase at least 4x from over 2M to nearly 9M, at which point the metadata +overhead for returning and new users would be around 49-54% (assuming 256-byte +target filenames for all packages) and 185% respectively, assuming that the +number of bins stay fixed. If the number of bins is increased, then the cost +for all users would effectively be the cost for new users, because their cost +would be dominated by the (once-in-a-while) cost of downloading the large +number of delegations in the `bins` metadata. If the cost for new users +should prove to be too much, then this subject SHOULD be revisited before +that happens. It is possible to make TUF metadata more compact by representing it in a binary format, as opposed to the JSON text format. Nevertheless, a sufficiently large From 7a583233ac5045dbd418b6f87356a2abe8eba7a2 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Thu, 17 Oct 2019 11:31:44 +0100 Subject: [PATCH 060/122] PEP 458: note that changes to num bins are transparent to client --- pep-0458.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pep-0458.txt b/pep-0458.txt index 887af429da4..ad36a481946 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -470,6 +470,11 @@ number of delegations in the `bins` metadata. If the cost for new users should prove to be too much, then this subject SHOULD be revisited before that happens. +Note that changes to the number of bins on the server are transparent to the +client. The package manager will be required to download a fresh set of +metadata, as though it were a new user, but this operation will not require any +explicit code logic or user interaction in order to do so. + It is possible to make TUF metadata more compact by representing it in a binary format, as opposed to the JSON text format. Nevertheless, a sufficiently large number of projects and distributions will introduce scalability challenges at From 0f06b16ec574e3a7c97479b0811c6af10535afd0 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Thu, 17 Oct 2019 16:30:38 +0100 Subject: [PATCH 061/122] PEP 458: Add Joshua Lock to authors list --- pep-0458.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/pep-0458.txt b/pep-0458.txt index 1e87528fc0d..9baad1162b2 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -7,6 +7,7 @@ Author: Trishank Karthik Kuppusamy , Marina Moore , Lukas Puehringer , Lois Anne DeLong , + Joshua Lock , Donald Stufft , Justin Cappos BDFL-Delegate: Donald Stufft From b1873bf056f7927dffdad43592562f4c33142cce Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 17 Oct 2019 18:09:48 +0200 Subject: [PATCH 062/122] Clarify targets delegation tree Clarify why targets does not directly delegate to bin-n roles, but instead delegates to bins, which delegates to bin-n roles. --- pep-0458.txt | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index ad36a481946..8410e0d6c6e 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -408,14 +408,20 @@ have any of the keys required to sign for projects. However, it does not protect projects from attackers who have compromised PyPI, since they can then manipulate TUF metadata using the keys stored online. -This PEP proposes that the *bin-n* roles sign for all -PyPI projects with online keys. The *targets* role, which only signs with an -offline key, MUST delegate all PyPI projects to the *bins* role. This means -that when a package manager such as pip (i.e., using TUF) downloads a -distribution from a project on PyPI, it will consult the *bins* role about the -TUF metadata for the project. If no *bin-n* roles delegated by *bins* specify the -project's distribution, then the project is considered to be non-existent on -PyPI. +This PEP proposes that the *bin-n* roles sign for all PyPI projects with online +keys. They MUST all be delegated by the upper-level *bins* role, which is signed +with an offline key, and in turn MUST be delegated by the top-level *targets* +role, which is also signed with an offline key. +This means that when a package manager such as pip (i.e., using TUF) downloads +a distribution from a project on PyPI, it will consult the *targets* role about +the TUF metadata for the project. If ultimately no *bin-n* roles delegated by +*targets* via *bins* specify the project's distribution, then the project is +considered to be non-existent on PyPI. + +Note, the reason why *targets* does not directly delegate to *bin-n*, but +instead uses the intermediary *bins* role, is so that other delegations can +easily be added or removed, without affecting the *bins*-to-*bin-n* mapping. +This is crucial for the implementation of PEP 480 [26]_. Metadata Expiry Times From 0678039d5e5ac60b0a4f92adb4a9101be6ec92b4 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Thu, 17 Oct 2019 15:55:36 -0400 Subject: [PATCH 063/122] fix #36 and #38 --- pep-0458.txt | 72 ++++++++++++++-------------------------------------- 1 file changed, 19 insertions(+), 53 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 3de8b282b22..00838d484f8 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -258,11 +258,25 @@ Integrating PyPI with TUF ========================= A software update system must complete two main tasks to integrate with TUF. -First, it must add the framework to the client side of the update system. For -example, TUF MAY be integrated with the pip package manager. Second, the -repository on the server side MUST be modified to provide signed TUF metadata. -This PEP is concerned with the second part of the integration, and the changes -on PyPI required to support software updates with TUF. +First, it must add the framework to the client side of the update system. For +example, TUF MAY be integrated with the pip package manager. Thus, new versions +of pip going forward SHOULD use TUF by default to download and verify packages +from PyPI before installing them. However, there may be unforeseen issues that +might prevent users from installing or updating packages, including pip itself, +via TUF. Therefore, pip MAY provide an undocumented option +(e.g., `--no-pep-458`) in order to work around such issues until they are +resolved. + +Second, the repository on the server side MUST be modified to provide signed +TUF metadata. This PEP is concerned with the second part of the integration, +and the changes on PyPI required to support software updates with TUF. +We assume that pip would use TUF to verify packages downloaded only from PyPI. +pip MAY support TAP 4__ in order use TUF to also verify packages downloaded +from elsewhere__. + +__ https://github.com/theupdateframework/taps/blob/master/tap4.md +__ https://www.python.org/dev/peps/pep-0470/ + What Additional Repository Files are Required on PyPI? @@ -1064,54 +1078,6 @@ Appendix A: Repository Attacks Prevented by TUF to sign files. -Appendix C: PEP 470 and Projects Hosted Externally -================================================== - -How should TUF handle distributions that are not hosted on PyPI? According to -`PEP 470`__, projects may opt to host their distributions externally and are -only required to provide PyPI a link to its external index, which package -managers like pip can use to find the project's distributions. PEP 470 does -not mention whether externally hosted projects are considered unverified by -default, as projects that use this option are not required to submit any -information about their distributions (e.g., file size and cryptographic hash) -when the project is registered, nor include a cryptographic hash of the file -in download links. - -__ http://www.python.org/dev/peps/pep-0470/ - -Potential approaches that PyPI administrators MAY consider to handle -projects hosted externally: - -1. Download external distributions but do not verify them. The targets - metadata will not include information for externally hosted projects. - -2. PyPI will periodically download information from the external index. PyPI - will gather the external distribution's file size and hashes and generate - appropriate TUF metadata. - -3. External projects MUST submit the file size and cryptographic hash to PyPI - for a distribution. - -4. External projects MUST upload to PyPI a developer public key for the - index. The distribution MUST create TUF metadata that is stored at the - index, and signed with the developer's corresponding private key. The - client will fetch the external TUF metadata as part of the package - update process. - -5. External projects MUST upload signed TUF metadata to PyPI (as allowed by - the maximum security model) about the distributions that they host - externally, along with a developer public key. Package managers verify - distributions by consulting the signed metadata uploaded to PyPI. - -Only one of the options listed above should be implemented on PyPI. Option -(4) or (5) is RECOMMENDED because external distributions are signed by -developers. External distributions that are forged (due to a compromised -PyPI account or external host) may be detected if external developers are -required to sign metadata, although this requirement is likely only practical -if an easy-to-use key management solution and developer scripts are provided -by PyPI. - - References ========== From 92fe340fd0af9770f208bac004831e0993806755 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Thu, 17 Oct 2019 16:58:54 -0400 Subject: [PATCH 064/122] simplify the table and explain the security when the online keys are all stored together --- pep-0458.txt | 53 +++++++++++++++++++++------------------------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index c7bc0065dc6..cb0d26e1617 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -808,8 +808,8 @@ Revoking Trust in Projects and Versions ======================================= From time to time either a project or a version of a package will need to be revoked. -To revoke trust in either a project or a version of a package, the associated bin-n role can -simply remove the corresponding targets and re-sign the bin-n metadata. This action only +To revoke trust in either a project or a version of a package, the associated bin-n role can +simply remove the corresponding targets and re-sign the bin-n metadata. This action only requires actions with the online bin-n key. @@ -828,41 +828,25 @@ private cryptographic keys (belonging to any of the PyPI roles) are compromised. The leftmost column lists the roles (or a combination of roles) that have been compromised, and the columns to its right show whether the compromised roles leave clients susceptible to malicious updates, a freeze -attack, or metadata inconsistency attacks. +attack, or metadata inconsistency attacks. Note that if the timestamp, snapshot, +and bin-n roles are stored in the same online location, a compromise of one +means they will all be compromised. Therefore the table considers these +roles together. A version of this table that considers these roles separately +is included in PEP 480 [26]_. +-----------------+-------------------+----------------+--------------------------------+ | Role Compromise | Malicious Updates | Freeze Attack | Metadata Inconsistency Attacks | +=================+===================+================+================================+ -| timestamp | NO | YES | NO | -| | snapshot and | limited by | snapshot needs to cooperate | -| | targets or any | earliest root, | | -| | of the bins need | targets, or | | -| | to cooperate | bin expiry | | -| | | time | | -+-----------------+-------------------+----------------+--------------------------------+ -| snapshot | NO | NO | NO | -| | timestamp and | timestamp | timestamp needs to cooperate | -| | targets or any of | needs to | | -| | the bins need to | cooperate | | -| | cooperate | | | -+-----------------+-------------------+----------------+--------------------------------+ -| timestamp | NO | YES | YES | -| **AND** | targets or any | limited by | limited by earliest root, | -| snapshot | of the bins need | earliest root, | targets, or bin metadata | -| | to cooperate | targets, or | expiry time | -| | | bin metadata | | -| | | expiry time | | -+-----------------+-------------------+----------------+--------------------------------+ -| targets | NO | NOT APPLICABLE | NOT APPLICABLE | +| targets | NO | NO | NO | | **OR** | timestamp and | need timestamp | need timestamp and snapshot | -| bin | snapshot need to | and snapshot | | +| bins | snapshot need to | and snapshot | | | | cooperate | | | +-----------------+-------------------+----------------+--------------------------------+ | timestamp | YES | YES | YES | -| **AND** | | limited by | limited by earliest root, | -| snapshot | | earliest root, | targets, or bin metadata | -| **AND** | | targets, or | expiry time | -| bin | | bin metadata | | +| **AND** | limited by | limited by | limited by earliest root, | +| snapshot | earliest root, | earliest root, | targets, or bins metadata | +| **AND** | targets, or bins | targets, or | expiry time | +| bin-n | expiry time | bins metadata | | | | | expiry time | | +-----------------+-------------------+----------------+--------------------------------+ | root | YES | YES | YES | @@ -875,17 +859,22 @@ was susceptible to these attacks and how TUF could protect users against them __ https://mail.python.org/pipermail/distutils-sig/2013-September/022755.html -Note that compromising *targets* or any delegated role (except for project -targets metadata) does not immediately allow an attacker to serve malicious +Note that compromising *targets* or *bins* +does not immediately allow an attacker to serve malicious updates. The attacker must also compromise the *timestamp* and *snapshot* roles, which are both online and therefore more likely to be compromised. This means that, in order to launch any attack, one must not only be able to act as a man-in-the-middle, but also compromise the *timestamp* key (or compromise the *root* keys and sign a new *timestamp* key). To launch any attack other than a freeze attack, one must also compromise the *snapshot* key. +In practice, this PEP recommends storing the *snapshot*, *timestamp*, and +*bin-n* keys together, or even using the same key for all of these roles. +Because of this, the attacker only needs to compromise this single server to +perform any of the attacks listed above. However, the offline *root* key will +allow the repository to recover from an attack by revoking the online key(s). Finally, a compromise of the PyPI infrastructure MAY introduce malicious -updates to *bins* projects because the keys for these roles are online. The +updates to *bin-n* projects because the keys for these roles are online. The maximum security model discussed in PEP 480 [26]_ addresses this issue and goes into more detail about generating developer keys and signing uploaded distributions. From 91a35fa100bfa3dbbc5975fd9565e936239c4e81 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Fri, 18 Oct 2019 09:46:31 +0200 Subject: [PATCH 065/122] Use noun instead of pronoun for clarity --- pep-0458.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 00838d484f8..3ce38c7139f 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -424,9 +424,9 @@ protect projects from attackers who have compromised PyPI, since they can then manipulate TUF metadata using the keys stored online. This PEP proposes that the *bin-n* roles sign for all PyPI projects with online -keys. They MUST all be delegated by the upper-level *bins* role, which is signed -with an offline key, and in turn MUST be delegated by the top-level *targets* -role, which is also signed with an offline key. +keys. These *bin-n* roles MUST all be delegated by the upper-level *bins* role, +which is signed with an offline key, and in turn MUST be delegated by the +top-level *targets* role, which is also signed with an offline key. This means that when a package manager such as pip (i.e., using TUF) downloads a distribution from a project on PyPI, it will consult the *targets* role about the TUF metadata for the project. If ultimately no *bin-n* roles delegated by From bb5c7b1b971e6b725c4ef38c2af4f15ae99e8aab Mon Sep 17 00:00:00 2001 From: mnm678 Date: Fri, 18 Oct 2019 11:28:07 -0400 Subject: [PATCH 066/122] Update pep-0458.txt Co-Authored-By: lukpueh --- pep-0458.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index cb0d26e1617..e9dcece436f 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -870,7 +870,9 @@ attack other than a freeze attack, one must also compromise the *snapshot* key. In practice, this PEP recommends storing the *snapshot*, *timestamp*, and *bin-n* keys together, or even using the same key for all of these roles. Because of this, the attacker only needs to compromise this single server to -perform any of the attacks listed above. However, the offline *root* key will +perform any of the attacks listed above. Note that clients are still protected +against compromises of non-signing infrastructure such as CDNs or mirrors. +Moreover, the offline *root* key will allow the repository to recover from an attack by revoking the online key(s). Finally, a compromise of the PyPI infrastructure MAY introduce malicious From 4c23511bdb1de375b55b6ecfec0c960afe79f508 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Fri, 18 Oct 2019 11:39:15 -0400 Subject: [PATCH 067/122] collapse table columns and remove duplicated text --- pep-0458.txt | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index e9dcece436f..fd3a1c890bc 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -808,8 +808,8 @@ Revoking Trust in Projects and Versions ======================================= From time to time either a project or a version of a package will need to be revoked. -To revoke trust in either a project or a version of a package, the associated bin-n role can -simply remove the corresponding targets and re-sign the bin-n metadata. This action only +To revoke trust in either a project or a version of a package, the associated bin-n role can +simply remove the corresponding targets and re-sign the bin-n metadata. This action only requires actions with the online bin-n key. @@ -837,19 +837,17 @@ is included in PEP 480 [26]_. +-----------------+-------------------+----------------+--------------------------------+ | Role Compromise | Malicious Updates | Freeze Attack | Metadata Inconsistency Attacks | +=================+===================+================+================================+ -| targets | NO | NO | NO | -| **OR** | timestamp and | need timestamp | need timestamp and snapshot | -| bins | snapshot need to | and snapshot | | -| | cooperate | | | +| targets | NO | +| **OR** | timestamp and snapshot need to cooperate | +| bins | | +-----------------+-------------------+----------------+--------------------------------+ -| timestamp | YES | YES | YES | -| **AND** | limited by | limited by | limited by earliest root, | -| snapshot | earliest root, | earliest root, | targets, or bins metadata | -| **AND** | targets, or bins | targets, or | expiry time | -| bin-n | expiry time | bins metadata | | -| | | expiry time | | +| timestamp | YES | +| **AND** | limited by earliest root, targets, or bins metadata expiry time | +| snapshot | | +| **AND** | | +| bin-n | | +-----------------+-------------------+----------------+--------------------------------+ -| root | YES | YES | YES | +| root | YES | +-----------------+-------------------+----------------+--------------------------------+ Table 1: Attacks possible by compromising certain combinations of role keys. @@ -867,7 +865,7 @@ This means that, in order to launch any attack, one must not only be able to act as a man-in-the-middle, but also compromise the *timestamp* key (or compromise the *root* keys and sign a new *timestamp* key). To launch any attack other than a freeze attack, one must also compromise the *snapshot* key. -In practice, this PEP recommends storing the *snapshot*, *timestamp*, and +In practice, this PEP recommends storing the *snapshot*, *timestamp*, and *bin-n* keys together, or even using the same key for all of these roles. Because of this, the attacker only needs to compromise this single server to perform any of the attacks listed above. Note that clients are still protected @@ -875,11 +873,9 @@ against compromises of non-signing infrastructure such as CDNs or mirrors. Moreover, the offline *root* key will allow the repository to recover from an attack by revoking the online key(s). -Finally, a compromise of the PyPI infrastructure MAY introduce malicious -updates to *bin-n* projects because the keys for these roles are online. The -maximum security model discussed in PEP 480 [26]_ addresses this issue and goes -into more detail about generating developer keys and signing uploaded -distributions. +The maximum security model shows how TUF mitigates online key compromises by +introducing additional roles for end-to-signing. Details about how to generate +developer keys and sign upload distributions are provided in PEP 480 [26]_. In the Event of a Key Compromise From 92e905572ac86392db3be9279e5a78a2918a9a24 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Wed, 23 Oct 2019 16:31:53 -0400 Subject: [PATCH 068/122] clarify what causes the cost for new users --- pep-0458.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 35825cfeb42..8f8b325da21 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -488,8 +488,9 @@ number of bins stay fixed. If the number of bins is increased, then the cost for all users would effectively be the cost for new users, because their cost would be dominated by the (once-in-a-while) cost of downloading the large number of delegations in the `bins` metadata. If the cost for new users -should prove to be too much, then this subject SHOULD be revisited before -that happens. +should prove to be too much due to the overhead of downloading the `bins` +metadata upon installation, then this subject SHOULD be revisited before that +happens. Note that changes to the number of bins on the server are transparent to the client. The package manager will be required to download a fresh set of From 76c4710c479d3aea90cd434500369408ccf07acd Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Wed, 23 Oct 2019 18:01:22 -0400 Subject: [PATCH 069/122] clarify cryptographic hashing algos --- pep-0458.txt | 56 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 35825cfeb42..843f7e3b896 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -284,10 +284,13 @@ What Additional Repository Files are Required on PyPI? In order for package managers like pip to download and verify packages with TUF, a few extra files MUST be added to PyPI. These extra repository files are -called TUF metadata, and they contain such information as which keys can be trusted, the cryptographic hashes of files, signatures to the metadata, +called TUF metadata, and they contain such information as which keys can be trusted, +the `cryptographic hashes`__ of files, signatures to the metadata, metadata version numbers, and the date after which the metadata should be considered expired. +__ https://en.wikipedia.org/wiki/Cryptographic_hash_function + When a package manager wants to check for updates, it asks TUF to do the work. That is, a package manager never has to deal with this additional metadata or understand what's going on underneath. If TUF reports back that there are @@ -309,8 +312,9 @@ PyPI and TUF Metadata TUF metadata provides information that clients can use to make update decisions. For example, a *targets* metadata lists the available distributions on PyPI and includes for each the required signatures, cryptographic hashes, and -file sizes. Different metadata files provide different information, which are signed by separate roles. -The *root* role indicates what metadata belongs to each role. The concept of roles allows TUF to delegate responsibilities +file sizes. Different metadata files provide different information, which are +signed by separate roles. The *root* role indicates what metadata belongs to +each role. The concept of roles allows TUF to delegate responsibilities to multiple roles, thus minimizing the impact of any one compromised role. TUF requires four top-level roles. These are *root*, *timestamp*, *snapshot*, @@ -319,16 +323,27 @@ top-level roles (including its own). The *timestamp* role references the latest *snapshot* and can signify when a new snapshot of the repository is available. The *snapshot* role indicates the latest version of all the TUF metadata files (other than *timestamp*). The *targets* role lists the file -paths of available target files together with their hashes. The file paths must -be specified relative to a base URL. This allows the actual target files to be -served from anywhere, as long as the base URL can be accessed by the client. -Each top-level role will serve its responsibilities without exception. Figure -1 provides a table of the roles used in TUF. +paths of available target files together with their cryptographic hashes. +The file paths must be specified relative to a base URL. This allows the +actual target files to be served from anywhere, as long as the base URL +can be accessed by the client. Each top-level role will serve its +responsibilities without exception. Figure 1 provides a table of the +roles used in TUF. .. image:: pep-0458-1.png Figure 1: An overview of the TUF roles. +This PEP RECOMMENDS that every metadata or target file be hashed using +both the SHA-256 and SHA-512 functions of the `SHA-2`__ family. SHA-2 +has native and well-tested Python support (allowing for verification of +these hashes without additional, non-Python dependencies), and using +both functions should provide sufficient protection against +`collision attacks`__ for the foreseeable future. + +__ https://en.wikipedia.org/wiki/SHA-2 +__ https://en.wikipedia.org/wiki/Collision_attack + Signing Metadata and Repository Management ------------------------------------------ @@ -471,12 +486,13 @@ __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/TUTORIAL.md#deleg Based on our findings as of the time this document was updated for implementation (Oct 7 2019), PyPI SHOULD split all targets in the *bins* role by delegating them to 16,384 *bin-n* roles. Each *bin-n* role would sign for the PyPI targets whose -hashes fall into that bin (see Figure 2). It was found__ -that this number of bins would result in a 12-17% metadata overhead +cryptographic hashes (specifically__ SHA-256) fall into that bin (see Figure 2). +It was found__ that this number of bins would result in a 12-17% metadata overhead (relative to the average size of downloaded packages) for returning users (assuming 256-byte target filenames for all packages), and a 148% overhead for new users who are installing pip for the first time. +__ https://github.com/theupdateframework/specification/blob/master/tuf-spec.md#4-document-formats __ https://docs.google.com/spreadsheets/d/11_XkeHrf4GdhMYVqpYWsug6JNz5ZK6HvvmDZX0__K2I/edit?usp=sharing This number of bins SHOULD increase when the metadata overhead for returning @@ -516,7 +532,7 @@ examined. TUF is agnostic with respect to choices of digital signature algorithms. However, this PEP RECOMMENDS that all digital signatures be produced with the Ed25519 algorithm [25]_. Ed25519 has native and well-tested Python support (allowing for verification of signatures without -additional, non-Python, dependencies), uses small keys, and is supported +additional, non-Python dependencies), uses small keys, and is supported by modern HSM and authentication token hardware. @@ -695,19 +711,19 @@ version of the *snapshot* metadata, which in turn lists the versions of the *targets* and delegated targets metadata, all as part of a given consistent snapshot. -Eventually, *targets* or delegated targets metadata point to the actual target -files, including their `cryptographic hashes`__. Thus, to mark a target file as -part of a consistent snapshot it MUST, when written to disk, include its hash -in its filename: +The *targets* or delegated targets metadata refer to the actual target +files, including their SHA-256 and SHA-512 cryptographic hashes. Thus, +to mark a target file as part of a consistent snapshot it MUST, when +written to disk, include its hash in its filename: HASH.FILENAME - where HASH is the `hex digest`__ of the `SHA-256`__ hash of the file - contents and FILENAME is the original filename. + where HASH is the `hex digest`__ of the SHA-256 or SHA-512 hash + of the file contents and FILENAME is the original filename. -__ https://en.wikipedia.org/wiki/Cryptographic_hash_function -__ https://docs.python.org/3.7/library/hashlib.html#hashlib.hash.hexdigest -__ https://en.wikipedia.org/wiki/SHA-2 +This means that there are two copies of every target file, one each for +the SHA-256 and SHA-512 hash functions. +__ https://docs.python.org/3.7/library/hashlib.html#hashlib.hash.hexdigest Assuming infinite disk space, strictly incrementing version numbers, and no `hash collisions`__, a client may safely read from one snapshot while PyPI From e6e532900e0c060bd666a726bdcf18310a07b065 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Thu, 24 Oct 2019 12:52:55 +0100 Subject: [PATCH 070/122] PEP 458: update references Remove no longer referenced items from the references and shuffle reference numbers so that they aren't any gaps introduced by the removal. --- pep-0458.txt | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 35825cfeb42..35fe819d3c9 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -50,7 +50,7 @@ interested in adopting TUF on the client side may consult its `library documentation`__, which was created for this purpose. There is also no discussion in this PEP of support for project distributions that are signed by developers (maximum security model). This possible future extension is covered -in detail in PEP 480 [26]_. The maximum security model requires more PyPI +in detail in PEP 480 [21]_. The maximum security model requires more PyPI administrative work (though no added work for clients), but it also proposes an easy-to-use key management solution for developers, ideas on how to interface with a potential future build farm on PyPI infrastructure, and the feasibility @@ -65,7 +65,7 @@ PEP Status Due to the amount of work required to implement this PEP, in early 2019 it was deferred until appropriate funding could be secured to implement the PEP. The Python Software Foundation secured this funding -[27]_. +[22]_. Motivation @@ -410,7 +410,7 @@ one proposed in this PEP is the minimum security model, which supports verification of PyPI distributions signed with private cryptographic keys stored on PyPI. Distributions uploaded by developers are signed by PyPI and immediately available for download. A possible future extension to this -PEP, discussed in PEP 480 [26]_, proposes the maximum security model and allows +PEP, discussed in PEP 480 [21]_, proposes the maximum security model and allows a developer to sign for his/her project. Developer keys are not stored online: therefore, projects are safe from PyPI compromises. @@ -436,7 +436,7 @@ considered to be non-existent on PyPI. Note, the reason why *targets* does not directly delegate to *bin-n*, but instead uses the intermediary *bins* role, is so that other delegations can easily be added or removed, without affecting the *bins*-to-*bin-n* mapping. -This is crucial for the implementation of PEP 480 [26]_. +This is crucial for the implementation of PEP 480 [21]_. Metadata Expiry Times @@ -514,7 +514,7 @@ PyPI and Key Requirements In this section, the kinds of keys required to sign for TUF roles on PyPI are examined. TUF is agnostic with respect to choices of digital signature algorithms. However, this PEP RECOMMENDS that all digital signatures be -produced with the Ed25519 algorithm [25]_. Ed25519 has native and +produced with the Ed25519 algorithm [15]_. Ed25519 has native and well-tested Python support (allowing for verification of signatures without additional, non-Python, dependencies), uses small keys, and is supported by modern HSM and authentication token hardware. @@ -545,7 +545,7 @@ the *targets* role be permanently discarded as soon as they have been created and used to sign for the role. Therefore, the *targets* role SHOULD require (2, 2) keys. Again, this is because the keys are going to be permanently discarded, and more offline keys will not help resist key recovery -attacks [21]_ unless the diversity of cryptographic algorithms is maintained. +attacks [20]_ unless the diversity of cryptographic algorithms is maintained. For similar reasons, the keys for the *bins* role SHOULD be set up similar to the keys for the *targets* role. @@ -852,7 +852,7 @@ attack, or metadata inconsistency attacks. Note that if the timestamp, snapshot, and bin-n roles are stored in the same online location, a compromise of one means they will all be compromised. Therefore the table considers these roles together. A version of this table that considers these roles separately -is included in PEP 480 [26]_. +is included in PEP 480 [21]_. +-----------------+-------------------+----------------+--------------------------------+ | Role Compromise | Malicious Updates | Freeze Attack | Metadata Inconsistency Attacks | @@ -895,7 +895,7 @@ allow the repository to recover from an attack by revoking the online key(s). The maximum security model shows how TUF mitigates online key compromises by introducing additional roles for end-to-signing. Details about how to generate -developer keys and sign upload distributions are provided in PEP 480 [26]_. +developer keys and sign upload distributions are provided in PEP 480 [21]_. In the Event of a Key Compromise @@ -1084,20 +1084,15 @@ References http://www.python.org/dev/peps/pep-0449/ .. [13] https://theupdateframework.github.io/papers/attacks-on-package-managers-ccs2008.pdf .. [14] https://mail.python.org/pipermail/distutils-sig/2013-September/022755.html -.. [15] https://pypi.python.org/security +.. [15] http://ed25519.cr.yp.to/ .. [16] https://github.com/theupdateframework/specification/blob/master/tuf-spec.md .. [17] PEP 426, Metadata for Python Software Packages 2.0, Coghlan, Holth, Stufft http://www.python.org/dev/peps/pep-0426/ .. [18] https://en.wikipedia.org/wiki/Continuous_delivery .. [19] https://mail.python.org/pipermail/distutils-sig/2013-August/022154.html -.. [20] https://en.wikipedia.org/wiki/RSA_%28algorithm%29 -.. [21] https://en.wikipedia.org/wiki/Key-recovery_attack -.. [22] https://doi.org/10.6028/NIST.SP.800-57pt1r4 -.. [23] https://www.openssl.org/ -.. [24] https://github.com/pyca/cryptography -.. [25] http://ed25519.cr.yp.to/ -.. [26] https://www.python.org/dev/peps/pep-0480/ -.. [27] https://pyfound.blogspot.com/2019/09/pypi-security-q4-2019-request-for.html +.. [20] https://en.wikipedia.org/wiki/Key-recovery_attack +.. [21] https://www.python.org/dev/peps/pep-0480/ +.. [22] https://pyfound.blogspot.com/2019/09/pypi-security-q4-2019-request-for.html Acknowledgements ================ From 089bbf5e3c5ea484317b405596b613967b8cc221 Mon Sep 17 00:00:00 2001 From: mnm678 Date: Thu, 24 Oct 2019 10:21:04 -0400 Subject: [PATCH 071/122] Update pep-0458.txt Co-Authored-By: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 8f8b325da21..517b050dad0 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -489,7 +489,7 @@ for all users would effectively be the cost for new users, because their cost would be dominated by the (once-in-a-while) cost of downloading the large number of delegations in the `bins` metadata. If the cost for new users should prove to be too much due to the overhead of downloading the `bins` -metadata upon installation, then this subject SHOULD be revisited before that +metadata, then this subject SHOULD be revisited before that happens. Note that changes to the number of bins on the server are transparent to the From 7dd4f8e84e61d2b5b966292dbd445278de30618e Mon Sep 17 00:00:00 2001 From: marinamoore Date: Thu, 24 Oct 2019 10:22:36 -0400 Subject: [PATCH 072/122] add primarily --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 517b050dad0..80ce974292e 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -488,7 +488,7 @@ number of bins stay fixed. If the number of bins is increased, then the cost for all users would effectively be the cost for new users, because their cost would be dominated by the (once-in-a-while) cost of downloading the large number of delegations in the `bins` metadata. If the cost for new users -should prove to be too much due to the overhead of downloading the `bins` +should prove to be too much, primarily due to the overhead of downloading the `bins` metadata, then this subject SHOULD be revisited before that happens. From 8ef0b5cd07097abdbb3e53136f9c545d75fbd9f4 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Thu, 24 Oct 2019 11:59:13 -0400 Subject: [PATCH 073/122] add possibility of using SHA-3 --- pep-0458.txt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 843f7e3b896..ef429b7371a 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -334,15 +334,21 @@ roles used in TUF. Figure 1: An overview of the TUF roles. -This PEP RECOMMENDS that every metadata or target file be hashed using -both the SHA-256 and SHA-512 functions of the `SHA-2`__ family. SHA-2 -has native and well-tested Python support (allowing for verification of -these hashes without additional, non-Python dependencies), and using -both functions should provide sufficient protection against -`collision attacks`__ for the foreseeable future. +Unless otherwise specified, this PEP RECOMMENDS that every metadata or +target file be hashed using both the SHA-256 and SHA-512 functions of +the `SHA-2`__ family. SHA-2 has native and well-tested Python 2 and 3 +support (allowing for verification of these hashes without additional, +non-Python dependencies), and using both functions should provide +sufficient protection against `collision attacks`__ for the foreseeable +future. However, this assumes that a collision attack for SHA-256 does +not easily translate to SHA-512. If stronger security guarantees are +required, then SHA-2 and SHA-3 MAY be used instead, since they are based +on very different designs from each other. However, SHA-3 requires +installing additional, non-Python dependencies for Python 2. __ https://en.wikipedia.org/wiki/SHA-2 __ https://en.wikipedia.org/wiki/Collision_attack +__ https://en.wikipedia.org/wiki/SHA-3 Signing Metadata and Repository Management From 7a372f2ba5c35fb9e4473556e7f78332920169bd Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Thu, 24 Oct 2019 13:44:02 -0400 Subject: [PATCH 074/122] clarify choice of algos --- pep-0458.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index ef429b7371a..87231d4be4e 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -342,9 +342,9 @@ non-Python dependencies), and using both functions should provide sufficient protection against `collision attacks`__ for the foreseeable future. However, this assumes that a collision attack for SHA-256 does not easily translate to SHA-512. If stronger security guarantees are -required, then SHA-2 and SHA-3 MAY be used instead, since they are based -on very different designs from each other. However, SHA-3 requires -installing additional, non-Python dependencies for Python 2. +required, then SHA2-256 and SHA3-256 MAY be used instead, since they +are based on very different designs from each other. However, SHA-3 +requires installing additional, non-Python dependencies for Python 2. __ https://en.wikipedia.org/wiki/SHA-2 __ https://en.wikipedia.org/wiki/Collision_attack From 6d71d9b1e13ee108de7af03a3874348d6977e9d3 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Thu, 24 Oct 2019 14:00:53 -0400 Subject: [PATCH 075/122] concretely specify all algos --- pep-0458.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 87231d4be4e..6544fe56cfe 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -335,13 +335,13 @@ roles used in TUF. Figure 1: An overview of the TUF roles. Unless otherwise specified, this PEP RECOMMENDS that every metadata or -target file be hashed using both the SHA-256 and SHA-512 functions of +target file be hashed using both the SHA2-256 and SHA2-512 functions of the `SHA-2`__ family. SHA-2 has native and well-tested Python 2 and 3 support (allowing for verification of these hashes without additional, non-Python dependencies), and using both functions should provide sufficient protection against `collision attacks`__ for the foreseeable -future. However, this assumes that a collision attack for SHA-256 does -not easily translate to SHA-512. If stronger security guarantees are +future. However, this assumes that a collision attack for SHA2-256 does +not easily translate to SHA2-512. If stronger security guarantees are required, then SHA2-256 and SHA3-256 MAY be used instead, since they are based on very different designs from each other. However, SHA-3 requires installing additional, non-Python dependencies for Python 2. @@ -492,7 +492,7 @@ __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/TUTORIAL.md#deleg Based on our findings as of the time this document was updated for implementation (Oct 7 2019), PyPI SHOULD split all targets in the *bins* role by delegating them to 16,384 *bin-n* roles. Each *bin-n* role would sign for the PyPI targets whose -cryptographic hashes (specifically__ SHA-256) fall into that bin (see Figure 2). +cryptographic hashes (specifically__ SHA2-256) fall into that bin (see Figure 2). It was found__ that this number of bins would result in a 12-17% metadata overhead (relative to the average size of downloaded packages) for returning users (assuming 256-byte target filenames for all packages), and a 148% overhead @@ -718,16 +718,16 @@ version of the *snapshot* metadata, which in turn lists the versions of the snapshot. The *targets* or delegated targets metadata refer to the actual target -files, including their SHA-256 and SHA-512 cryptographic hashes. Thus, +files, including their SHA2-256 and SHA2-512 cryptographic hashes. Thus, to mark a target file as part of a consistent snapshot it MUST, when written to disk, include its hash in its filename: HASH.FILENAME - where HASH is the `hex digest`__ of the SHA-256 or SHA-512 hash + where HASH is the `hex digest`__ of the SHA2-256 or SHA2-512 hash of the file contents and FILENAME is the original filename. This means that there are two copies of every target file, one each for -the SHA-256 and SHA-512 hash functions. +the SHA2-256 and SHA2-512 hash functions. __ https://docs.python.org/3.7/library/hashlib.html#hashlib.hash.hexdigest From ff68ed1cac2942ba6b5377f3a8917553a145fae9 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Thu, 24 Oct 2019 14:02:40 -0400 Subject: [PATCH 076/122] fix link --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 6544fe56cfe..3447282e1e3 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -342,7 +342,7 @@ non-Python dependencies), and using both functions should provide sufficient protection against `collision attacks`__ for the foreseeable future. However, this assumes that a collision attack for SHA2-256 does not easily translate to SHA2-512. If stronger security guarantees are -required, then SHA2-256 and SHA3-256 MAY be used instead, since they +required, then SHA2-256 and `SHA3-256`__ MAY be used instead, since they are based on very different designs from each other. However, SHA-3 requires installing additional, non-Python dependencies for Python 2. From a468d50cbfb6ea2c04b3f5b20debaa3ba47f0d31 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Thu, 24 Oct 2019 14:07:26 -0400 Subject: [PATCH 077/122] clarify --- pep-0458.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 3447282e1e3..932e3c7e37a 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -492,7 +492,7 @@ __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/TUTORIAL.md#deleg Based on our findings as of the time this document was updated for implementation (Oct 7 2019), PyPI SHOULD split all targets in the *bins* role by delegating them to 16,384 *bin-n* roles. Each *bin-n* role would sign for the PyPI targets whose -cryptographic hashes (specifically__ SHA2-256) fall into that bin (see Figure 2). +cryptographic hashes (specifically__ only SHA2-256) fall into that bin (see Figure 2). It was found__ that this number of bins would result in a 12-17% metadata overhead (relative to the average size of downloaded packages) for returning users (assuming 256-byte target filenames for all packages), and a 148% overhead @@ -718,16 +718,16 @@ version of the *snapshot* metadata, which in turn lists the versions of the snapshot. The *targets* or delegated targets metadata refer to the actual target -files, including their SHA2-256 and SHA2-512 cryptographic hashes. Thus, -to mark a target file as part of a consistent snapshot it MUST, when +files, including all of their cryptographic hashes as specified above. +Thus, to mark a target file as part of a consistent snapshot it MUST, when written to disk, include its hash in its filename: HASH.FILENAME - where HASH is the `hex digest`__ of the SHA2-256 or SHA2-512 hash - of the file contents and FILENAME is the original filename. + where HASH is the `hex digest`__ of the hash of the file contents and + FILENAME is the original filename. -This means that there are two copies of every target file, one each for -the SHA2-256 and SHA2-512 hash functions. +This means that there are multiple copies of every target file, one for each +of the cryptographic hash functions specified above. __ https://docs.python.org/3.7/library/hashlib.html#hashlib.hash.hexdigest From dbd3b87ac96352af0a6948cd3638f6f3b237b55b Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Thu, 24 Oct 2019 15:32:50 -0400 Subject: [PATCH 078/122] include tables for calculating metadata overheads --- pep-0458.txt | 116 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 98 insertions(+), 18 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 80ce974292e..aac2eaddde6 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -468,29 +468,109 @@ install or upgrade a PyPI project via TUF. __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/TUTORIAL.md#delegate-to-hashed-bins -Based on our findings as of the time this document was updated for implementation -(Oct 7 2019), PyPI SHOULD split all targets in the *bins* role by delegating -them to 16,384 *bin-n* roles. Each *bin-n* role would sign for the PyPI targets whose -hashes fall into that bin (see Figure 2). It was found__ +Based on our findings as of the time this document was updated for +implementation (Oct 7 2019), summarized in Tables 1-2, PyPI SHOULD +split all targets in the *bins* role by delegating them to 16,384 +*bin-n* roles (see C11 in Table 1). Each *bin-n* role would sign +for the PyPI targets whose SHA2-256 hashes fall into that bin +(see and Figure 2 and :ref:`Consistent Snapshots`). It was found that this number of bins would result in a 12-17% metadata overhead -(relative to the average size of downloaded packages) for returning users -(assuming 256-byte target filenames for all packages), and a 148% overhead -for new users who are installing pip for the first time. +(relative to the average size of downloaded packages; see V14 and +V16 in Table 2) for returning users, and a 148% overhead for new +users who are installing pip for the first time (see V18 in Table 2). + +A few assumptions used in calculating these metadata overhead percentages: + +1. We are ignoring root, timestamp, and top-level targets metadata. +2. pip will always be bundled with the latest good copy of metadata for all + roles. + ++------+----------------------------------------------------------------+-----------+ +| Name | Description | Value | ++------+----------------------------------------------------------------+-----------+ +| C1 | # of bytes in a SHA2-256 hexadecimal digest | 64 | ++------+----------------------------------------------------------------+-----------+ +| C2 | # of bytes in a SHA2-512 hexadecimal digest | 128 | ++------+----------------------------------------------------------------+-----------+ +| C3 | # of bytes for a SHA2-256 public key ID | 64 | ++------+----------------------------------------------------------------+-----------+ +| C4 | # of bytes for an Ed25519 signature | 128 | ++------+----------------------------------------------------------------+-----------+ +| C5 | # of bytes for an Ed25519 public key | 64 | ++------+----------------------------------------------------------------+-----------+ +| C6 | Upper bound on # of bytes for a target relative file path size | 256 | ++------+----------------------------------------------------------------+-----------+ +| C7 | Upper bound on # of bytes for a target file size | 6 | ++------+----------------------------------------------------------------+-----------+ +| C8 | Upper bound on # of bytes for a version number | 3 | ++------+----------------------------------------------------------------+-----------+ +| C9 | # of targets (simple indices and packages) | 2,195,135 | ++------+----------------------------------------------------------------+-----------+ +| C10 | Average # of bytes for a downloaded package | 1,000,000 | ++------+----------------------------------------------------------------+-----------+ +| C11 | # of bins | 16,384 | ++------+----------------------------------------------------------------+-----------+ + +Table 1: A list of constants used to calculate metadata overhead. + ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| Name | Description | Formula | Value | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V1 | Length of a path hash prefix | math.ceil(math.log(C11, 16)) | 4 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V2 | Total # of path hash prefixes | 16**V1 | 65,536 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V3 | Avg # of targets per bin | C9/C11 | 134 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V4 | Avg size of SHA-256 hashes per bin | V3*C1 | 8,576 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V5 | Avg size of SHA-512 hashes per bin | V3*C2 | 17,152 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V6 | Avg size of target paths per bin | V3*C6 | 34,304 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V7 | Avg size of lengths per bin | V3*C7 | 804 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V8 | Avg size of bin metadata (bytes) | V4+V5+V6+V7 | 60,836 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V9 | Total size of public key IDs in bins | C11*C3 | 1,048,576 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V10 | Total size of path hash prefixes in bins | V1*V2 | 262,144 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V11 | Est. size of bins metadata (bytes) | V9+V10 | 1,310,720 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V12 | Est. size of snapshot metadata (bytes) | C11*C8 | 49,152 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V13 | Est. size of metadata overhead per package per returning user (same snapshot) | 2*V8 | 121,672 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V14 | Est. metadata overhead per package per returning user (same snapshot) | round((V13/C10)*100) | 12% | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V15 | Est. size of metadata overhead per package per returning user (diff snapshot) | V13+V12 | 170,824 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V16 | Est. metadata overhead per package per returning user (diff snapshot) | round((V15/C10)*100) | 17% | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V17 | Est. size of metadata overhead per package per new user | V15+V11 | 1,481,544 | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ +| V18 | Est. metadata overhead per package per new user | round((V17/C10)*100) | 148% | ++------+-------------------------------------------------------------------------------+------------------------------+-----------+ + +Table 2: Estimated metadata overheads for new and returning users. + +The interested reader may find an interactive version of the metadata overhead +calculator here__: __ https://docs.google.com/spreadsheets/d/11_XkeHrf4GdhMYVqpYWsug6JNz5ZK6HvvmDZX0__K2I/edit?usp=sharing This number of bins SHOULD increase when the metadata overhead for returning users exceeds 50%. Presently, this SHOULD happen when the number of targets increase at least 4x from over 2M to nearly 9M, at which point the metadata -overhead for returning and new users would be around 49-54% (assuming 256-byte -target filenames for all packages) and 185% respectively, assuming that the -number of bins stay fixed. If the number of bins is increased, then the cost -for all users would effectively be the cost for new users, because their cost -would be dominated by the (once-in-a-while) cost of downloading the large -number of delegations in the `bins` metadata. If the cost for new users -should prove to be too much, primarily due to the overhead of downloading the `bins` -metadata, then this subject SHOULD be revisited before that -happens. +overhead for returning and new users would be around 49-54% and 185% +respectively, assuming that the number of bins stay fixed. If the number of +bins is increased, then the cost for all users would effectively be the cost +for new users, because their cost would be dominated by the (once-in-a-while) +cost of downloading the large number of delegations in the `bins` metadata. +If the cost for new users should prove to be too much, primarily due to the +overhead of downloading the `bins` metadata, then this subject SHOULD be +revisited before that happens. Note that changes to the number of bins on the server are transparent to the client. The package manager will be required to download a fresh set of @@ -844,7 +924,7 @@ sign the metadata for each role. The remaining sections discuss how PyPI SHOULD audit repository metadata, and the methods PyPI can use to detect and recover from a PyPI compromise. -Table 1 summarizes a few of the attacks possible when a threshold number of +Table 3 summarizes a few of the attacks possible when a threshold number of private cryptographic keys (belonging to any of the PyPI roles) are compromised. The leftmost column lists the roles (or a combination of roles) that have been compromised, and the columns to its right show whether the @@ -871,7 +951,7 @@ is included in PEP 480 [26]_. | root | YES | +-----------------+-------------------+----------------+--------------------------------+ -Table 1: Attacks possible by compromising certain combinations of role keys. +Table 3: Attacks possible by compromising certain combinations of role keys. In `September 2013`__, it was shown how the latest version (at the time) of pip was susceptible to these attacks and how TUF could protect users against them [14]_. From ccc8e3de0310da1cdb5aabfb4da978bc8bbb964f Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Wed, 16 Oct 2019 14:18:42 +0100 Subject: [PATCH 079/122] PEP 458: typo --- pep-0458.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 3ac1d3924ca..77fe55d458b 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -601,8 +601,8 @@ MAY be online in the private infrastructure of the project. There SHOULD be an offline key ceremony to generate, backup, and store these keys in such a manner that the private keys can be read only by the Python administrators when necessary (e.g., such as rotating the keys for the -top-level TUF roles). Thus, keys SHOULD be generated—preferably in a physical -location where side-channel attacks__ are not a concern—using: +top-level TUF roles). Thus, keys SHOULD be generated, preferably in a physical +location where side-channel attacks__ are not a concern, using: 1. A trusted, airgapped__ computer with a true random number generator__, and with no **data** persisting after the ceremony From d3c738c243d105b072af93f1a2399f0366d1eeb4 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Tue, 22 Oct 2019 11:11:46 +0100 Subject: [PATCH 080/122] PEP 458: clarify some steps of the offline key ceremony Make it clear why various steps of the offline key ceremony are recommended --- pep-0458.txt | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 77fe55d458b..6549e4eac37 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -607,8 +607,9 @@ location where side-channel attacks__ are not a concern, using: 1. A trusted, airgapped__ computer with a true random number generator__, and with no **data** persisting after the ceremony 2. A trusted operating system -3. A trusted set of third-party packages (e.g., cryptographic libraries, the - TUF reference implementation) +3. A trusted set of third-party packages (such as updated versions of + cryptographic libraries or the TUF reference implementation, where the + versions provided by the trusted operating system are not recent enough) __ https://en.wikipedia.org/wiki/Side-channel_attack __ https://en.wikipedia.org/wiki/Air_gap_(networking) @@ -629,16 +630,23 @@ Passwords used to encrypt keys SHOULD be stored somewhere durable and trustworthy to which only Python admins have access. In order to minimize OPSEC__ errors during the ceremony, scripts SHOULD be -written to automate tedious parts, such as: +written, for execution on the trusted key-generation computer, to automate +tedious steps of the ceremony, such as: - Exporting to sneakernet__ all code and data (e.g., previous TUF metadata, targets, and *root* keys) required to generate new keys and replace old ones - Tightening the firewall, updating the entire operating system in order to fix security vulnerabilities, and airgapping the computer -- Printing and saving cryptographic hashes of new TUF metadata -- Exporting *all* new TUF metadata, targets, and keys to encrypted backup media +- Exporting *all* new TUF metadata, targets, and keys to encrypted backup media. + This backup provides a complete copy of the data required to restore the PyPI + TUF repository - Exporting *only* new TUF metadata, targets, and online keys to encrypted backup - media + media. This backup provides all online data for import into the PyPI + infrastructure and is useful, e.g., when the online data needs to be restored + from a previous archived state +- Printing and saving cryptographic hashes of new TUF metadata. This printed copy + provides an additional offline paper backup, which can be used as a comparison + in the case of a compromise __ https://en.wikipedia.org/wiki/Operations_security __ https://en.wikipedia.org/wiki/Sneakernet From 0012036db5938a4f4c4532a9b010fd37440c29be Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Thu, 24 Oct 2019 12:34:51 +0100 Subject: [PATCH 081/122] PEP 458: remove references to target files in offline key ceremony The targets files are signed in standard operation when distributions are uploaded to PyPI and initially during a one-time initialisation process. --- pep-0458.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 6549e4eac37..c13dbb8bfbf 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -633,17 +633,17 @@ In order to minimize OPSEC__ errors during the ceremony, scripts SHOULD be written, for execution on the trusted key-generation computer, to automate tedious steps of the ceremony, such as: -- Exporting to sneakernet__ all code and data (e.g., previous TUF metadata, - targets, and *root* keys) required to generate new keys and replace old ones +- Exporting to sneakernet__ all code and data (previous TUF metadata and *root* + keys) required to generate new keys and replace old ones - Tightening the firewall, updating the entire operating system in order to fix security vulnerabilities, and airgapping the computer -- Exporting *all* new TUF metadata, targets, and keys to encrypted backup media. +- Exporting *all* new TUF metadata and keys to encrypted backup media. This backup provides a complete copy of the data required to restore the PyPI TUF repository -- Exporting *only* new TUF metadata, targets, and online keys to encrypted backup - media. This backup provides all online data for import into the PyPI - infrastructure and is useful, e.g., when the online data needs to be restored - from a previous archived state +- Exporting *only* new TUF metadata and online keys to encrypted backup media. + This backup provides all online data for import into the PyPI infrastructure + and is useful, e.g., when the online data needs to be restored from a previous + archived state - Printing and saving cryptographic hashes of new TUF metadata. This printed copy provides an additional offline paper backup, which can be used as a comparison in the case of a compromise From 39d1e335feae38d2ad1ed339d4aedc2c537dba65 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Thu, 24 Oct 2019 12:36:20 +0100 Subject: [PATCH 082/122] PEP 458: mention one-time init to create and sign bin-n metadata --- pep-0458.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pep-0458.txt b/pep-0458.txt index c13dbb8bfbf..625fd04427a 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -369,6 +369,12 @@ generate cryptographic keys, and manage a TUF repository. __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/TUTORIAL.md#how-to-create-and-modify-a-tuf-repository +In standard operation, the *bin-n* metadata will be updated and signed as new +distributions are uploaded to PyPI. However, there will also need to be a +one-time online initialization mechanism to create and sign *bin-n* metadata for +all existing distributions that are part of the PyPI repository every time PyPI +is re-initialized. + How to Establish Initial Trust in the PyPI Root Keys ---------------------------------------------------- From 558e714084af8c911a215581fdc68a5c5fb6b463 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 24 Oct 2019 17:13:04 +0200 Subject: [PATCH 083/122] Revise Producing Consistent Snapshots section - Merges "Producing Consistent Snapshots" and "Snapshot Process" sections - Revises description of transaction and snapshot processes (aims at making it more concises) - Adds a caveats section and suggestsions to address @ewdurbin's review comment in: https://github.com/python/peps/pull/1203#discussion_r336229332 --- pep-0458.txt | 127 ++++++++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 56 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 35825cfeb42..12405f69e6c 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -730,57 +730,77 @@ efficiently transfer consistent snapshots from PyPI. Producing Consistent Snapshots ------------------------------ -When given a project, PyPI is responsible for updating the *bin-n* metadata. Every -project MUST upload its release in a single transaction. The uploaded set of -files is called the "project transaction." How PyPI MAY validate the files in -a project transaction is discussed in a later section. For now, the focus is -on how PyPI responds to a project transaction. - -When a project uploads a new transaction, the project transaction process MUST -add all new targets and relevant delegated *bin-n* metadata. The process then MUST inform the snapshot process about any new -*bin-n* metadata. - -Project transaction processes SHOULD be automated and MUST also be applied -atomically. This means either all metadata and targets -- or none of them -- are added. -The project transaction and snapshot processes SHOULD work concurrently. -Finally, project transaction processes SHOULD use the latest *bin-n* -metadata so that they will be correctly updated in new consistent snapshots. - -Signing updated *timestamp*, *snapshot*, and *bin-n* metadata needs to be done on each -update. Fortunately, the actual operation of signing is fast enough that this -may be done a thousand or more times per second. However, locking must be -used so that project transactions are handled sequentially. To achieve this, -all project transactions MAY be placed in a single queue and processed -serially. Alternatively, the queue MAY be processed concurrently in order of -appearance, provided that the following rules are observed: - -1. No pair of project transaction processes must concurrently work on the same - project. - -2. No pair of project transaction processes must concurrently work on - projects that belong to the same delegated *bin-n* role. - -These rules MUST be observed so that metadata is not read from or written to -inconsistently. - - -Snapshot Process ----------------- - -The snapshot process is fairly simple and SHOULD be automated. The snapshot -process SHOULD use the latest working set of the *targets* and all -delegated targets roles (i.e. *bins* and *bin-n* roles) metadata. Upon an update, the -snapshot process will sign for this -latest working set. (Recall that project transaction processes continuously -inform the snapshot process about the latest delegated metadata in a -concurrency-safe manner. The snapshot process will actually sign for a copy of -the latest working set while the latest working set in memory will be updated -with information that is continuously communicated by the project transaction -processes.) The snapshot process MUST generate and sign new *timestamp* -metadata that will vouch for the metadata (*root*, *targets*, and delegated -roles) generated in the previous step. Finally, the snapshot process MUST make -the new *timestamp* and *snapshot* metadata representing -the latest snapshot available to clients. +When a new project release is uploaded to PyPI, PyPI MUST update the *bin-n* +metadata responsible for the target files of the project release (remember that +target files are sorted into bins by their filename hashes). Consequentially, +PyPI MUST update *snaphot* to account for the updated *bin-n* metadata, and +*timestamp* to account for the updated *snapshot* metadata. These updates +SHOULD be handled by automated processes, e.g. one or more *transaction +processes* and one *snapshot process*. + +The transaction process keeps track of a project upload, adds all new target +files to the most recent, relevant *bin-n* metadata (in memory) and informs the +snapshot process to produce a consistent snapshot. Each project release SHOULD +be handled in an atomic transaction, so that a given consistent snapshot +contains all target files of a project release. However, transaction processes +MAY be parallelized under the following constraints: + +- No pair of transaction processes MUST concurrently work on the same project. +- No pair of transaction processes MUST concurrently work on projects that + belong to the same *bin-n* role. + +When a transaction process is finished updating the relevant *bin-n* metadata +it informs the snapshot process to generate a new consistent snapshot. The +snapshot process does so by taking the updated *bin-n* metadata, incrementing +their respective version numbers, signing them with the *bin-n* role key(s), +and writing them to *VERSION_NUMBER.bin-N.json*. + +Similarly, the snapshot process then takes the most recent *snapshot* metadata, +updates its *bin-n* metadata hashes and version numbers, increments its own +version number, signs it with the *snapshot* role key, and writes it to +*VERSION_NUMBER.snapshot.json*. + +And finally, the snapshot process takes the most recent *timestamp* metadata, +updates its *snapshot* metadata hash and version number, increments its own +version number, sets a new expiration time, signs it with the *timestamp* role +key, and writes it to *timestamp.json*. + +The snapshot process MUST generate consistent snapshots sequentially, reading +the notifications received from the transaction process(es) from a +concurrency-safe FIFO queue. Fortunately, the operation of signing is fast +enough that this may be done a thousand or more times per second. + +The following caveats exist in regards to project transactions and immediate +availability of target files: + +- PyPI allows adding files to a project release at arbitrary later times. +- PyPI may want to make files available for download before all files of the + project release have been uploaded. + +To solve this, PyPI MAY define variable transaction windows that mark a +transaction as finished even before all target files of a project release have +been uploaded. A variable transaction window SHOULD be chosen so that a client, +who downloads target files of a project release, is not left in an inconsistent +state (with our without TUF metadata). +Furthermore, the transaction window MAY only apply to the generation of a TUF +consistent snapshot. That is, uploaded target files MAY become available for +download immediately after being uploaded and before TUF has generated a +consistent snapshot. If a user then downloads those target files in that short +time frame, the client (e.g. pip) SHOULD warn the user about missing TUF +protection and advice to abort download and try later. + +At any rate, PyPI SHOULD use a `transaction log`__ to record project +transaction processes and the snapshot queue for auditing and to recover from +errors after a server failure. + +__ https://en.wikipedia.org/wiki/Transaction_log + + + + + +# TODO: I think we can move down/ merge below paragraphs with "Cleaning up old metadata" + At this stage, its important to acknowledge a few implementation notes. So far, we have seen only that new metadata and targets are added, but not that old metadata and targets are @@ -801,11 +821,6 @@ every metadata and target file (except for *timestamp* metadata) by including, in the file request, the version of the file (for metadata), or the cryptographic hash of the file (for target files) in the filename. -Finally, PyPI SHOULD use a `transaction log`__ to record project transaction -processes and queues so that it will be easier to recover from errors after a -server failure. - -__ https://en.wikipedia.org/wiki/Transaction_log Cleaning up old metadata From 4d5cbb407d4746789d0459d5a20cf398ce773011 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Fri, 25 Oct 2019 14:19:40 +0200 Subject: [PATCH 084/122] Revise Cleaning up old metadata section - Merges relevant paragraphs from "Snapshot Process" section into "Cleaning up old metadata" section. - Moves parts about client requests to "Consistent Snapshots" section. - Emphasizes that old *root* metadata must be kept - Generally re-phrases --- pep-0458.txt | 54 ++++++++++++++++++++-------------------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 12405f69e6c..b6bf3791449 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -715,6 +715,11 @@ produces another snapshot. __ https://en.wikipedia.org/wiki/Collision_(computer_science) +Clients, such as pip, using the TUF protocol MUST be modified to download +every metadata and target file (except for *timestamp* metadata) by including, +in the file request, the version of the file (for metadata), or the +cryptographic hash of the file (for target files) in the filename. + In this simple but effective manner, PyPI is able to capture a consistent snapshot of all projects and the associated metadata at a given time. The next subsection provides implementation details of this idea. @@ -796,48 +801,31 @@ errors after a server failure. __ https://en.wikipedia.org/wiki/Transaction_log +Cleaning up old metadata +------------------------ +To avoid running out of disk space, by constantly producing new consistent +snapshots, PyPI SHOULD regularly delete old consistent snapshots, i.e. metadata +and target files that were obsoleted some reasonable time in the past, such as +1 hour. +In order to preserve the latest consistent snapshot PyPI MAY use a +"mark-and-sweep" algorithm. That is, walk from the root of the latest +consistent snapshot, i.e. *timestamp* over *snapshot* over *targets* and +delegated targets until the target files, marking all visited files, and +delete all unmarked files. The last few consistent snapshots may be preserved +in a similar fashion. -# TODO: I think we can move down/ merge below paragraphs with "Cleaning up old metadata" - - -At this stage, its important to acknowledge a few implementation notes. So far, we have seen only that -new metadata and targets are added, but not that old metadata and targets are -removed. Practical constraints are such that eventually PyPI will run out of -disk space to produce a new consistent snapshot. In that case, PyPI MAY then -use something like a "mark-and-sweep" algorithm to delete sufficiently old -consistent snapshots. In order to preserve the latest consistent snapshot, PyPI -should walk objects beginning from the root (*timestamp*) of the latest -consistent snapshot, mark all visited objects, and delete all unmarked objects. -The last few consistent snapshots may be preserved in a similar fashion. Deleting a consistent snapshot will cause clients to see nothing except HTTP 404 responses to any request for a file within that consistent snapshot. Clients SHOULD then retry their requests (as before) with the latest consistent snapshot. -All clients, such as pip, using the TUF protocol MUST be modified to download -every metadata and target file (except for *timestamp* metadata) by including, -in the file request, the version of the file (for metadata), or the -cryptographic hash of the file (for target files) in the filename. - - - -Cleaning up old metadata ------------------------- +Note that *root* metadata, even though versioned, is not part of any consistent +snapshot. PyPI MUST NOT delete old versions of *root* metadata. This guarantees +that clients can update to the latest *root* role keys, no matter how outdated +their local *root* metadata is. -Prior versions of snapshot, targets, and timestamp metadata does not need to -be kept indefinitely. (Root files must be indefinitely retained.) -However, a client that performs an update MUST be able -to retrieve a consistent set of versions of the files on the repository. -For example, if a client downloads a snapshot file, it should retrieve the versions -of targets metadata that existed when that snapshot file was created, even if -the targets metadata has been updated concurrently with the client requests. -Fortunately, the use of hash / version delegations handle this case automatically -since clients request targets unambiguously. Once no client could reasonably -be requesting outdated targets, snapshot or timestamp files, those files may be -removed to save space. Thus, files that were obsoleted some reasonable time -in the past (such as 1 hour) may be safely discarded. Revoking Trust in Projects and Versions ======================================= From 041370923312383529e68b2c8de6c46e3bc061af Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Fri, 25 Oct 2019 11:57:00 -0400 Subject: [PATCH 085/122] Update pep-0458.txt Co-Authored-By: lukpueh --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index aac2eaddde6..1e0d208fa1b 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -473,7 +473,7 @@ implementation (Oct 7 2019), summarized in Tables 1-2, PyPI SHOULD split all targets in the *bins* role by delegating them to 16,384 *bin-n* roles (see C11 in Table 1). Each *bin-n* role would sign for the PyPI targets whose SHA2-256 hashes fall into that bin -(see and Figure 2 and :ref:`Consistent Snapshots`). It was found +(see and Figure 2 and `Consistent Snapshots`_). It was found that this number of bins would result in a 12-17% metadata overhead (relative to the average size of downloaded packages; see V14 and V16 in Table 2) for returning users, and a 148% overhead for new From 6f55ea4517ee695dab633ff4cc07bbfafd9986e6 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Fri, 25 Oct 2019 19:27:20 +0200 Subject: [PATCH 086/122] Address most Trishank's review comments --- pep-0458.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index b6bf3791449..0d56d74e4f3 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -744,7 +744,7 @@ SHOULD be handled by automated processes, e.g. one or more *transaction processes* and one *snapshot process*. The transaction process keeps track of a project upload, adds all new target -files to the most recent, relevant *bin-n* metadata (in memory) and informs the +files to the most recent, relevant *bin-n* metadata and informs the snapshot process to produce a consistent snapshot. Each project release SHOULD be handled in an atomic transaction, so that a given consistent snapshot contains all target files of a project release. However, transaction processes @@ -761,8 +761,8 @@ their respective version numbers, signing them with the *bin-n* role key(s), and writing them to *VERSION_NUMBER.bin-N.json*. Similarly, the snapshot process then takes the most recent *snapshot* metadata, -updates its *bin-n* metadata hashes and version numbers, increments its own -version number, signs it with the *snapshot* role key, and writes it to +updates its *bin-n* metadata version numbers, increments its own version +number, signs it with the *snapshot* role key, and writes it to *VERSION_NUMBER.snapshot.json*. And finally, the snapshot process takes the most recent *timestamp* metadata, @@ -778,7 +778,8 @@ enough that this may be done a thousand or more times per second. The following caveats exist in regards to project transactions and immediate availability of target files: -- PyPI allows adding files to a project release at arbitrary later times. +- PyPI allows for upload of additional files for a release at any point in the + future. - PyPI may want to make files available for download before all files of the project release have been uploaded. @@ -786,7 +787,7 @@ To solve this, PyPI MAY define variable transaction windows that mark a transaction as finished even before all target files of a project release have been uploaded. A variable transaction window SHOULD be chosen so that a client, who downloads target files of a project release, is not left in an inconsistent -state (with our without TUF metadata). +state (with or without TUF metadata). Furthermore, the transaction window MAY only apply to the generation of a TUF consistent snapshot. That is, uploaded target files MAY become available for download immediately after being uploaded and before TUF has generated a From a096376cd982361bb82dfe2f887ebae5e8967fc6 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Fri, 25 Oct 2019 14:08:46 -0400 Subject: [PATCH 087/122] slightly more realistic numbers --- pep-0458.txt | 72 ++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index aac2eaddde6..889aed0e469 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -474,9 +474,9 @@ split all targets in the *bins* role by delegating them to 16,384 *bin-n* roles (see C11 in Table 1). Each *bin-n* role would sign for the PyPI targets whose SHA2-256 hashes fall into that bin (see and Figure 2 and :ref:`Consistent Snapshots`). It was found -that this number of bins would result in a 12-17% metadata overhead +that this number of bins would result in a 12-22% metadata overhead (relative to the average size of downloaded packages; see V14 and -V16 in Table 2) for returning users, and a 148% overhead for new +V16 in Table 2) for returning users, and a 153% overhead for new users who are installing pip for the first time (see V18 in Table 2). A few assumptions used in calculating these metadata overhead percentages: @@ -485,31 +485,31 @@ A few assumptions used in calculating these metadata overhead percentages: 2. pip will always be bundled with the latest good copy of metadata for all roles. -+------+----------------------------------------------------------------+-----------+ -| Name | Description | Value | -+------+----------------------------------------------------------------+-----------+ -| C1 | # of bytes in a SHA2-256 hexadecimal digest | 64 | -+------+----------------------------------------------------------------+-----------+ -| C2 | # of bytes in a SHA2-512 hexadecimal digest | 128 | -+------+----------------------------------------------------------------+-----------+ -| C3 | # of bytes for a SHA2-256 public key ID | 64 | -+------+----------------------------------------------------------------+-----------+ -| C4 | # of bytes for an Ed25519 signature | 128 | -+------+----------------------------------------------------------------+-----------+ -| C5 | # of bytes for an Ed25519 public key | 64 | -+------+----------------------------------------------------------------+-----------+ -| C6 | Upper bound on # of bytes for a target relative file path size | 256 | -+------+----------------------------------------------------------------+-----------+ -| C7 | Upper bound on # of bytes for a target file size | 6 | -+------+----------------------------------------------------------------+-----------+ -| C8 | Upper bound on # of bytes for a version number | 3 | -+------+----------------------------------------------------------------+-----------+ -| C9 | # of targets (simple indices and packages) | 2,195,135 | -+------+----------------------------------------------------------------+-----------+ -| C10 | Average # of bytes for a downloaded package | 1,000,000 | -+------+----------------------------------------------------------------+-----------+ -| C11 | # of bins | 16,384 | -+------+----------------------------------------------------------------+-----------+ ++------+---------------------------------------------+-----------+ +| Name | Description | Value | ++------+---------------------------------------------+-----------+ +| C1 | # of bytes in a SHA2-256 hexadecimal digest | 64 | ++------+---------------------------------------------+-----------+ +| C2 | # of bytes in a SHA2-512 hexadecimal digest | 128 | ++------+---------------------------------------------+-----------+ +| C3 | # of bytes for a SHA2-256 public key ID | 64 | ++------+---------------------------------------------+-----------+ +| C4 | # of bytes for an Ed25519 signature | 128 | ++------+---------------------------------------------+-----------+ +| C5 | # of bytes for an Ed25519 public key | 64 | ++------+---------------------------------------------+-----------+ +| C6 | # of bytes for a target relative file path | 256 | ++------+---------------------------------------------+-----------+ +| C7 | # of bytes to encode a target file size | 7 | ++------+---------------------------------------------+-----------+ +| C8 | # of bytes to encode a version number | 6 | ++------+---------------------------------------------+-----------+ +| C9 | # of targets (simple indices and packages) | 2,195,135 | ++------+---------------------------------------------+-----------+ +| C10 | Average # of bytes for a downloaded package | 1,000,000 | ++------+---------------------------------------------+-----------+ +| C11 | # of bins | 16,384 | ++------+---------------------------------------------+-----------+ Table 1: A list of constants used to calculate metadata overhead. @@ -528,9 +528,9 @@ Table 1: A list of constants used to calculate metadata overhead. +------+-------------------------------------------------------------------------------+------------------------------+-----------+ | V6 | Avg size of target paths per bin | V3*C6 | 34,304 | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V7 | Avg size of lengths per bin | V3*C7 | 804 | +| V7 | Avg size of lengths per bin | V3*C7 | 938 | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V8 | Avg size of bin metadata (bytes) | V4+V5+V6+V7 | 60,836 | +| V8 | Avg size of bin-n metadata (bytes) | V4+V5+V6+V7 | 60,970 | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ | V9 | Total size of public key IDs in bins | C11*C3 | 1,048,576 | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ @@ -538,19 +538,19 @@ Table 1: A list of constants used to calculate metadata overhead. +------+-------------------------------------------------------------------------------+------------------------------+-----------+ | V11 | Est. size of bins metadata (bytes) | V9+V10 | 1,310,720 | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V12 | Est. size of snapshot metadata (bytes) | C11*C8 | 49,152 | +| V12 | Est. size of snapshot metadata (bytes) | C11*C8 | 98,304 | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V13 | Est. size of metadata overhead per package per returning user (same snapshot) | 2*V8 | 121,672 | +| V13 | Est. size of metadata overhead per package per returning user (same snapshot) | 2*V8 | 121,940 | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ | V14 | Est. metadata overhead per package per returning user (same snapshot) | round((V13/C10)*100) | 12% | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V15 | Est. size of metadata overhead per package per returning user (diff snapshot) | V13+V12 | 170,824 | +| V15 | Est. size of metadata overhead per package per returning user (diff snapshot) | V13+V12 | 220,244 | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V16 | Est. metadata overhead per package per returning user (diff snapshot) | round((V15/C10)*100) | 17% | +| V16 | Est. metadata overhead per package per returning user (diff snapshot) | round((V15/C10)*100) | 22% | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V17 | Est. size of metadata overhead per package per new user | V15+V11 | 1,481,544 | +| V17 | Est. size of metadata overhead per package per new user | V15+V11 | 1,530,964 | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V18 | Est. metadata overhead per package per new user | round((V17/C10)*100) | 148% | +| V18 | Est. metadata overhead per package per new user | round((V17/C10)*100) | 153% | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ Table 2: Estimated metadata overheads for new and returning users. @@ -563,7 +563,7 @@ __ https://docs.google.com/spreadsheets/d/11_XkeHrf4GdhMYVqpYWsug6JNz5ZK6HvvmDZX This number of bins SHOULD increase when the metadata overhead for returning users exceeds 50%. Presently, this SHOULD happen when the number of targets increase at least 4x from over 2M to nearly 9M, at which point the metadata -overhead for returning and new users would be around 49-54% and 185% +overhead for returning and new users would be around 49-59% and 190% respectively, assuming that the number of bins stay fixed. If the number of bins is increased, then the cost for all users would effectively be the cost for new users, because their cost would be dominated by the (once-in-a-while) From 15036960984e9b105d1e4b2afb2731ae211a68b8 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Fri, 25 Oct 2019 14:49:20 -0400 Subject: [PATCH 088/122] clarify --- pep-0458.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index e76225ae77d..120c8321462 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -285,9 +285,8 @@ What Additional Repository Files are Required on PyPI? In order for package managers like pip to download and verify packages with TUF, a few extra files MUST be added to PyPI. These extra repository files are called TUF metadata, and they contain such information as which keys can be trusted, -the `cryptographic hashes`__ of files, signatures to the metadata, -metadata version numbers, and the date after which the metadata should be -considered expired. +the `cryptographic hashes`__ of files, signatures, metadata version numbers, and +the date after which the metadata should be considered expired. __ https://en.wikipedia.org/wiki/Cryptographic_hash_function @@ -344,11 +343,12 @@ future. However, this assumes that a collision attack for SHA2-256 does not easily translate to SHA2-512. If stronger security guarantees are required, then SHA2-256 and `SHA3-256`__ MAY be used instead, since they are based on very different designs from each other. However, SHA-3 -requires installing additional, non-Python dependencies for Python 2. +requires installing additional, non-Python dependencies for `Python 2`__. __ https://en.wikipedia.org/wiki/SHA-2 __ https://en.wikipedia.org/wiki/Collision_attack __ https://en.wikipedia.org/wiki/SHA-3 +__ https://pip.pypa.io/en/latest/development/release-process/#python-2-support Signing Metadata and Repository Management From 0e925966b88011029c561b90497cdc7982de6a84 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Fri, 25 Oct 2019 15:18:44 -0400 Subject: [PATCH 089/122] fix minor typo --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index b276efaa709..0e397cb7f73 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -93,7 +93,7 @@ requiring SSL to communicate with PyPI [6]_, restricting project names [7]_, and migrating from MD5 to SHA-2 hashes [8]_. -Though necessary, these steps are insufficient to protect package because attacks are still +Though necessary, these steps are insufficient to protect packages because attacks are still possible through other avenues. For example, a public mirror is trusted to honestly mirror PyPI, but some mirrors may misbehave, whether by accident or through malicious intervention. From 5db64eb7f7d0c05b0c8d6edf1b967700b39bab1e Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Fri, 25 Oct 2019 15:41:57 -0400 Subject: [PATCH 090/122] edits and improvements --- pep-0458.txt | 60 ++++++++++++++++++++-------------------------------- 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 0e397cb7f73..3e9c0bf0e10 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -4,10 +4,10 @@ Version: $Revision$ Last-Modified: $Date$ Author: Trishank Karthik Kuppusamy , Vladimir Diaz , - Marina Moore , Lukas Puehringer , - Lois Anne DeLong , + Marina Moore , Joshua Lock , + Lois Anne DeLong , Donald Stufft , Justin Cappos BDFL-Delegate: Donald Stufft @@ -151,7 +151,7 @@ interpreted as described in RFC 2119__. __ http://www.ietf.org/rfc/rfc2119.txt This PEP focuses only on integrating TUF into PyPI. However, the reader is -encouraged to review TUF's design principles [2]_ and SHOULD be +encouraged to review TUF design principles [2]_ and SHOULD be familiar with the TUF specification [16]_. Terms used in this PEP are defined as follows: @@ -796,10 +796,10 @@ produces another snapshot. __ https://en.wikipedia.org/wiki/Collision_(computer_science) -Clients, such as pip, using the TUF protocol MUST be modified to download -every metadata and target file (except for *timestamp* metadata) by including, -in the file request, the version of the file (for metadata), or the -cryptographic hash of the file (for target files) in the filename. +Clients, such as pip, that use the TUF protocol MUST be modified to download +every metadata and target file, except for *timestamp* metadata. This is done +by including, in the file request, the version of the file (for metadata), +or the cryptographic hash of the file (for target files) in the filename. In this simple but effective manner, PyPI is able to capture a consistent snapshot of all projects and the associated metadata at a given time. The next @@ -817,22 +817,22 @@ Producing Consistent Snapshots ------------------------------ When a new project release is uploaded to PyPI, PyPI MUST update the *bin-n* -metadata responsible for the target files of the project release (remember that -target files are sorted into bins by their filename hashes). Consequentially, -PyPI MUST update *snaphot* to account for the updated *bin-n* metadata, and +metadata responsible for the target files of the project release. Remember that +target files are sorted into bins by their filename hashes. Consequentially, +PyPI MUST update *snapshot* to account for the updated *bin-n* metadata, and *timestamp* to account for the updated *snapshot* metadata. These updates SHOULD be handled by automated processes, e.g. one or more *transaction processes* and one *snapshot process*. -The transaction process keeps track of a project upload, adds all new target +Each transaction process keeps track of a project upload, adds all new target files to the most recent, relevant *bin-n* metadata and informs the snapshot process to produce a consistent snapshot. Each project release SHOULD be handled in an atomic transaction, so that a given consistent snapshot contains all target files of a project release. However, transaction processes MAY be parallelized under the following constraints: -- No pair of transaction processes MUST concurrently work on the same project. -- No pair of transaction processes MUST concurrently work on projects that +- Pairs of transaction processes MUST NOT concurrently work on the same project. +- Pairs of transaction processes MUST NOT concurrently work on projects that belong to the same *bin-n* role. When a transaction process is finished updating the relevant *bin-n* metadata @@ -856,25 +856,11 @@ the notifications received from the transaction process(es) from a concurrency-safe FIFO queue. Fortunately, the operation of signing is fast enough that this may be done a thousand or more times per second. -The following caveats exist in regards to project transactions and immediate -availability of target files: - -- PyPI allows for upload of additional files for a release at any point in the - future. -- PyPI may want to make files available for download before all files of the - project release have been uploaded. - -To solve this, PyPI MAY define variable transaction windows that mark a -transaction as finished even before all target files of a project release have -been uploaded. A variable transaction window SHOULD be chosen so that a client, -who downloads target files of a project release, is not left in an inconsistent -state (with or without TUF metadata). -Furthermore, the transaction window MAY only apply to the generation of a TUF -consistent snapshot. That is, uploaded target files MAY become available for -download immediately after being uploaded and before TUF has generated a -consistent snapshot. If a user then downloads those target files in that short -time frame, the client (e.g. pip) SHOULD warn the user about missing TUF -protection and advice to abort download and try later. +If there are multiple files in a release, a project MAY release these files in +separate transactions. For example, a project MAY release files for Windows in +one transaction, and the files for *nix in another transaction. However, a project +SHOULD release files that must belong together in order for everything to work +in the same transaction. At any rate, PyPI SHOULD use a `transaction log`__ to record project transaction processes and the snapshot queue for auditing and to recover from @@ -886,10 +872,10 @@ __ https://en.wikipedia.org/wiki/Transaction_log Cleaning up old metadata ------------------------ -To avoid running out of disk space, by constantly producing new consistent -snapshots, PyPI SHOULD regularly delete old consistent snapshots, i.e. metadata -and target files that were obsoleted some reasonable time in the past, such as -1 hour. +To avoid running out of disk space due to the constant production of new +consistent snapshots, PyPI SHOULD regularly delete old consistent snapshots, +i.e. metadata and target files that were obsoleted some reasonable time in +the past, such as 1 hour. In order to preserve the latest consistent snapshot PyPI MAY use a "mark-and-sweep" algorithm. That is, walk from the root of the latest @@ -1074,7 +1060,7 @@ another to detect accidental or malicious failures. Another approach is to generate the cryptographic hash of *snapshot* periodically and tweet it. Perhaps a user comes forward with the actual -metadata and the repository maintainers can verify the metadata's cryptographic +metadata and the repository maintainers can verify the metadata file's cryptographic hash. Alternatively, PyPI may periodically archive its own versions of *snapshot* rather than rely on externally provided metadata. In this case, PyPI SHOULD take the cryptographic hash of every package on the repository and From 9ea6913de4de1aad6d876a61aae3aecf9e3aefff Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Fri, 25 Oct 2019 16:43:38 -0400 Subject: [PATCH 091/122] fix stupid bugs --- pep-0458.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 33749cd71af..7551a463f16 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -547,7 +547,7 @@ Table 1: A list of constants used to calculate metadata overhead. +------+-------------------------------------------------------------------------------+------------------------------+-----------+ | V2 | Total # of path hash prefixes | 16**V1 | 65,536 | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V3 | Avg # of targets per bin | C9/C11 | 134 | +| V3 | Avg # of targets per bin | math.ceil(C9/C11) | 134 | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ | V4 | Avg size of SHA-256 hashes per bin | V3*C1 | 8,576 | +------+-------------------------------------------------------------------------------+------------------------------+-----------+ @@ -893,7 +893,7 @@ enough that this may be done a thousand or more times per second. If there are multiple files in a release, a project MAY release these files in separate transactions. For example, a project MAY release files for Windows in -one transaction, and the files for *nix in another transaction. However, a project +one transaction, and the files for Linux in another transaction. However, a project SHOULD release files that must belong together in order for everything to work in the same transaction. From 786ae236b0d16aecb52b9d983061bda7d7041941 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Wed, 30 Oct 2019 21:47:37 -0400 Subject: [PATCH 092/122] Update pep-0458.txt Co-Authored-By: Dustin Ingram --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 7551a463f16..f9c81712ac2 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -71,7 +71,7 @@ implement the PEP. The Python Software Foundation secured this funding Motivation ========== -On January 5, 2013, the Python Software Foundation (PSF) announced that [4]_a security +On January 5, 2013, the Python Software Foundation (PSF) announced that [4]_ a security breach had occurred on the python.org wikis for Python, Jython, and as a result all of the wiki data was destroyed. Fortunately, the PyPI infrastructure was not affected by this breach. From f75aa8db8d41bdd9a778c16d4a13cdfd799ed12f Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Wed, 30 Oct 2019 21:47:49 -0400 Subject: [PATCH 093/122] Update pep-0458.txt Co-Authored-By: Dustin Ingram --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index f9c81712ac2..49c84e4adaf 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -73,7 +73,7 @@ Motivation On January 5, 2013, the Python Software Foundation (PSF) announced that [4]_ a security breach had occurred on the -python.org wikis for Python, Jython, and as a result all of the wiki data was destroyed. +python.org wikis for Python and Jython. As a result, all of the wiki data was destroyed. Fortunately, the PyPI infrastructure was not affected by this breach. However, the incident is a reminder that PyPI needed to take defensive steps to protect users as much as possible in the event of a compromise. Attacks on From 2ab1c1640dc6058a0f54efd657954e6c83e3210b Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Wed, 30 Oct 2019 21:48:03 -0400 Subject: [PATCH 094/122] Update pep-0458.txt Co-Authored-By: Dustin Ingram --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 49c84e4adaf..14e03de4241 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -102,7 +102,7 @@ packages downloaded from a public mirror [9]_, but none are known to actually do so [10]_. Therefore, it would be wise to add more security measures to detect attacks from public mirrors or content delivery networks [11]_ (CDNs). -Even though official mirrors are being deprecated on PyPI [12]_, a +Even though official mirrors have been deprecated on PyPI [12]_, a wide variety of other attack vectors on package managers remain [13]_. These attacks can crash client systems, cause obsolete packages to be installed, or even allow an attacker to execute arbitrary code. In `September 2013`__, a post was From a54422206d92aacfda845dbe0f8b59fd0ee02cc5 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Wed, 30 Oct 2019 21:48:17 -0400 Subject: [PATCH 095/122] Update pep-0458.txt Co-Authored-By: Dustin Ingram --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 14e03de4241..05c4c379dbf 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -171,7 +171,7 @@ Terms used in this PEP are defined as follows: distributions of a project [17]_. * Roles: There is one *root* role in PyPI. There are multiple other roles for which the *root* role delegates - responsibilities, directly or indirectly. The term top-level role refers to the *root* role and any role + responsibilities, directly or indirectly. The term *top-level* role refers to the *root* role and any role specified directly by the *root* role, i.e. *timestamp*, *snapshot* and *targets* roles. Each role has a single metadata file that it is trusted to provide. From 16b67a9ae007ba67efb4ff123ba85779eb8fa293 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Wed, 30 Oct 2019 21:49:08 -0400 Subject: [PATCH 096/122] Update pep-0458.txt Co-Authored-By: Dustin Ingram --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 05c4c379dbf..e7ce5cb6080 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -310,7 +310,7 @@ PyPI and TUF Metadata TUF metadata provides information that clients can use to make update decisions. For example, a *targets* metadata lists the available distributions -on PyPI and includes for each the required signatures, cryptographic hashes, and +on PyPI and includes the required signatures, cryptographic hashes, and file sizes. Different metadata files provide different information, which are signed by separate roles. The *root* role indicates what metadata belongs to each role. The concept of roles allows TUF to delegate responsibilities From e3ae8fbb762676832b9cbbbfaeb8e08412332a0d Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Wed, 30 Oct 2019 21:49:22 -0400 Subject: [PATCH 097/122] Update pep-0458.txt Co-Authored-By: Dustin Ingram --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index e7ce5cb6080..54b27543ada 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -311,7 +311,7 @@ PyPI and TUF Metadata TUF metadata provides information that clients can use to make update decisions. For example, a *targets* metadata lists the available distributions on PyPI and includes the required signatures, cryptographic hashes, and -file sizes. Different metadata files provide different information, which are +file sizes for each. Different metadata files provide different information, which are signed by separate roles. The *root* role indicates what metadata belongs to each role. The concept of roles allows TUF to delegate responsibilities to multiple roles, thus minimizing the impact of any one compromised role. From 8508d5339160b4e822b2f22d519f86292baf0a15 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Wed, 30 Oct 2019 21:49:52 -0400 Subject: [PATCH 098/122] Update pep-0458.txt Co-Authored-By: Dustin Ingram --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 54b27543ada..20369e5070a 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -761,7 +761,7 @@ __ https://en.wikipedia.org/wiki/Sneakernet Note the one-time keys for the *targets* and *bins* roles MAY be safely generated, used, and deleted during the offline key ceremony. Furthermore, the *root* keys MAY not be generated during the offline key ceremony itself. -instead, a threshold t of n Python administrators, as discussed above, may +Instead, a threshold t of n Python administrators, as discussed above, MAY independently sign the *root* metadata **after** the offline key ceremony used to generate all other keys. From d178c5b462abf5bed2a8c19f2386ae26f3ada306 Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> Date: Wed, 30 Oct 2019 21:51:05 -0400 Subject: [PATCH 099/122] Update pep-0458.txt Co-Authored-By: Dustin Ingram --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 20369e5070a..272ea8823c9 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -1104,7 +1104,7 @@ indicates an attack. As for attacks that serve different versions of metadata, or freeze a version of a package at a specific version, they can be handled by TUF with techniques -like implicit key revocation and metadata mismatch detection [2]. +like implicit key revocation and metadata mismatch detection [2]_. Managing Future Changes to the Update Process From 56e4bf7686f104990867ad28f70eb2a4a987736d Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Tue, 5 Nov 2019 10:53:19 +0000 Subject: [PATCH 100/122] PEP 458: make pip verification disabling option more evocative Update the suggested option name for disabling package verification on pip to be more evocative of the outcome and its impact. --- pep-0458.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 272ea8823c9..0e1982bbb2d 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -263,9 +263,9 @@ example, TUF MAY be integrated with the pip package manager. Thus, new versions of pip going forward SHOULD use TUF by default to download and verify packages from PyPI before installing them. However, there may be unforeseen issues that might prevent users from installing or updating packages, including pip itself, -via TUF. Therefore, pip MAY provide an undocumented option -(e.g., `--no-pep-458`) in order to work around such issues until they are -resolved. +via TUF. Therefore, pip SHOULD provide an option (e.g., +`--unsafely-disable-package-verification`) in order to work around such issues +until they are resolved. Second, the repository on the server side MUST be modified to provide signed TUF metadata. This PEP is concerned with the second part of the integration, From 72fe3fd5672cf2b14c2f76582cf42f2225dfe99c Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Tue, 5 Nov 2019 10:59:59 +0000 Subject: [PATCH 101/122] PEP 458: add additional acknowledgements --- pep-0458.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 0e1982bbb2d..42ceb44f9ff 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -1220,8 +1220,8 @@ We appreciate the efforts of Konstantin Andrianov, Geremy Condra, Zane Fisher, Justin Samuel, Tian Tian, Santiago Torres, John Ward, and Yuyu Zheng in developing TUF. -Vladimir Diaz, Monzur Muhammad and Sai Teja Peddinti helped us to review this -PEP. +Vladimir Diaz, Monzur Muhammad, Sai Teja Peddinti, Sumana Harihareswara, +Ernest W. Durbin III and Dustin Ingram helped us to review this PEP. Zane Fisher helped us to review and transcribe this PEP. From 502adb250440a048c9b2d9122c686e41d47b74a2 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Wed, 6 Nov 2019 11:04:29 +0000 Subject: [PATCH 102/122] PEP 458: note the reasoning behind a long option to disable TUF Add a comment on the verbosity of --unsafely-disable-package-verification --- pep-0458.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 42ceb44f9ff..974d3236cd9 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -263,9 +263,11 @@ example, TUF MAY be integrated with the pip package manager. Thus, new versions of pip going forward SHOULD use TUF by default to download and verify packages from PyPI before installing them. However, there may be unforeseen issues that might prevent users from installing or updating packages, including pip itself, -via TUF. Therefore, pip SHOULD provide an option (e.g., -`--unsafely-disable-package-verification`) in order to work around such issues -until they are resolved. +via TUF. Therefore, pip SHOULD provide an option e.g., +`--unsafely-disable-package-verification`, in order to work around such issues +until they are resolved. Note, the proposed option name is purposefully long, +because a user must be helped to understand that the action is unsafe and not +generally recommended. Second, the repository on the server side MUST be modified to provide signed TUF metadata. This PEP is concerned with the second part of the integration, From d6b5c6fa45f1cf5a84bbab56abbaefaf32f40474 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 7 Nov 2019 13:57:38 +0100 Subject: [PATCH 103/122] Revise target file related terminology - Update link to python package glossary (used to point to outdated PEP 426). - Remove terms "projects", "releases" and "distributions" from definitions sections and refer the reader to the PPA glossary. - Add definition for "distribution file" and clarify that it means "distribution package" as defined in the PPA glossary and may be referred to by the single word "distribution" in this document. - Add definition for target files. - Use distribution or distribution file, and in some cases target file, instead of package throughout the document. --- pep-0458.txt | 248 +++++++++++++++++++++++++-------------------------- 1 file changed, 124 insertions(+), 124 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 974d3236cd9..b1b6634354d 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -82,35 +82,35 @@ possibility of security breaches and prepare PyPI accordingly because it is a valuable resource used by thousands, if not millions, of people. Before the wiki attack, PyPI used MD5 hashes to tell package managers, such as -pip, whether or not a package was corrupted in transit. However, the absence +pip, whether or not a distribution file was corrupted in transit. However, the absence of SSL made it hard for package managers to verify transport integrity to PyPI. It was therefore easy to launch a man-in-the-middle attack between pip and -PyPI, and change package content arbitrarily. As a result, users could be tricked into -installing malicious packages. After the wiki +PyPI, and change distribution content arbitrarily. As a result, users could be tricked into +installing malicious distributions. After the wiki attack, several steps were proposed (some of which were implemented) to deliver a much higher level of security than was previously the case. These steps included requiring SSL to communicate with PyPI [6]_, restricting project names [7]_, and migrating from MD5 to SHA-2 hashes [8]_. -Though necessary, these steps are insufficient to protect packages because attacks are still +Though necessary, these steps are insufficient to protect distributions because attacks are still possible through other avenues. For example, a public mirror is trusted to honestly mirror PyPI, but some mirrors may misbehave, whether by accident or through malicious intervention. Package managers such as pip are supposed to use signatures from PyPI to verify -packages downloaded from a public mirror [9]_, but none are known to actually +distribution files downloaded from a public mirror [9]_, but none are known to actually do so [10]_. Therefore, it would be wise to add more security measures to detect attacks from public mirrors or content delivery networks [11]_ (CDNs). Even though official mirrors have been deprecated on PyPI [12]_, a wide variety of other attack vectors on package managers remain [13]_. These attacks -can crash client systems, cause obsolete packages to be installed, or even +can crash client systems, cause obsolete distributions to be installed, or even allow an attacker to execute arbitrary code. In `September 2013`__, a post was made to the Distutils mailing list showing that the latest version of pip (at the time) was susceptible to such attacks, and how TUF could protect users against them [14]_. Specifically, testing was done to see how pip would respond to these attacks with and without TUF. Attacks tested included replay -and freeze, arbitrary packages, slow retrieval, and endless data. The post +and freeze, arbitrary distribution, slow retrieval, and endless data. The post also included a demonstration of how pip would respond if PyPI were compromised. @@ -137,7 +137,7 @@ The threat model assumes the following: An attacker is considered successful if it can cause a client to install (or leave installed) something other than the most up-to-date version of a -software package or file. If the attacker is preventing the installation +software distribution file. If the attacker is preventing the installation of updates, they do not want clients to realize there is anything wrong. @@ -154,41 +154,42 @@ This PEP focuses only on integrating TUF into PyPI. However, the reader is encouraged to review TUF design principles [2]_ and SHOULD be familiar with the TUF specification [16]_. -Terms used in this PEP are defined as follows: +The following terms used in this PEP are defined in the Python Packaging +Glossary [17]_: *project*, *release*, *distribution*. -* Projects: Projects are software components that are made available for - integration. Projects include Python libraries, frameworks, scripts, - plugins, applications, collections of data or other resources, and various - combinations thereof. Public Python projects are typically registered on the - Python Package Index [17]_. +Additional terms used in this PEP are defined as follows: -* Releases: Releases are uniquely identified snapshots of a project [17]_. +* Role: TUF specifies one *root* role and multiple other roles to which the + *root* role delegates responsibilities, directly or indirectly. The term + *top-level* role refers to the *root* role and any role specified directly by + the *root* role, i.e. *timestamp*, *snapshot* and *targets* roles. Each role + has a single metadata file that it is trusted to provide. -* Distributions: Distributions are the packaged files that are used to publish - and distribute a release [17]_. +* Distribution file: A versioned archive file that contains Python packages, + modules, and other resource files that are used to distribute a release. In + this document a distribution file may also be referred to with the single + word distribution. -* Simple index: The HTML page that contains internal links to the - distributions of a project [17]_. +* Simple index: The HTML page that contains internal links to distribution + files. -* Roles: There is one *root* role in PyPI. There are multiple other roles for which the *root* role delegates - responsibilities, directly or indirectly. The term *top-level* role refers to the *root* role and any role - specified directly by the *root* role, i.e. *timestamp*, *snapshot* and - *targets* roles. Each role has a single metadata file that it is trusted to - provide. +* Target files: As a rule of thumb, target files are all files on PyPI whose + integrity should be guaranteed with TUF. Typically, this includes + distribution files, and PyPI metadata such as simple indices. * Metadata: Metadata are signed files that describe roles, other metadata, and - target files. + target files. If not specified otherwise metadata means TUF-specific + metadata. * Repository: A repository is a source for named metadata and target files. Clients request metadata and target files stored on a repository. -* Consistent snapshot: A set of TUF metadata and PyPI targets that capture the +* Consistent snapshot: A set of TUF metadata and target files that capture the complete state of all projects on PyPI as they existed at some fixed point in time. * Developer: Either the owner or maintainer of a project who is allowed to - update the TUF metadata, as well as distribution metadata and files for the - project. + update the TUF metadata, as well as target files for a project. * Online key: A private cryptographic key that MUST be stored on the PyPI server infrastructure. This is usually to allow automated signing with the @@ -260,11 +261,11 @@ Integrating PyPI with TUF A software update system must complete two main tasks to integrate with TUF. First, it must add the framework to the client side of the update system. For example, TUF MAY be integrated with the pip package manager. Thus, new versions -of pip going forward SHOULD use TUF by default to download and verify packages +of pip going forward SHOULD use TUF by default to download and verify distributions from PyPI before installing them. However, there may be unforeseen issues that -might prevent users from installing or updating packages, including pip itself, +might prevent users from installing or updating distributions, including pip itself, via TUF. Therefore, pip SHOULD provide an option e.g., -`--unsafely-disable-package-verification`, in order to work around such issues +`--unsafely-disable-distribution-verification`, in order to work around such issues until they are resolved. Note, the proposed option name is purposefully long, because a user must be helped to understand that the action is unsafe and not generally recommended. @@ -272,8 +273,8 @@ generally recommended. Second, the repository on the server side MUST be modified to provide signed TUF metadata. This PEP is concerned with the second part of the integration, and the changes on PyPI required to support software updates with TUF. -We assume that pip would use TUF to verify packages downloaded only from PyPI. -pip MAY support TAP 4__ in order use TUF to also verify packages downloaded +We assume that pip would use TUF to verify distributions downloaded only from PyPI. +pip MAY support TAP 4__ in order use TUF to also verify distributions downloaded from elsewhere__. __ https://github.com/theupdateframework/taps/blob/master/tap4.md @@ -284,7 +285,7 @@ __ https://www.python.org/dev/peps/pep-0470/ What Additional Repository Files are Required on PyPI? ------------------------------------------------------ -In order for package managers like pip to download and verify packages with +In order for package managers like pip to download and verify distributions with TUF, a few extra files MUST be added to PyPI. These extra repository files are called TUF metadata, and they contain such information as which keys can be trusted, the `cryptographic hashes`__ of files, signatures, metadata version numbers, and @@ -311,7 +312,7 @@ PyPI and TUF Metadata ===================== TUF metadata provides information that clients can use to make update -decisions. For example, a *targets* metadata lists the available distributions +decisions. For example, a *targets* metadata lists the available target files on PyPI and includes the required signatures, cryptographic hashes, and file sizes for each. Different metadata files provide different information, which are signed by separate roles. The *root* role indicates what metadata belongs to @@ -360,7 +361,7 @@ The top-level *root* role signs for the keys of the top-level *timestamp*, *snapshot*, *targets*, and *root* roles. The *timestamp* role signs for every new snapshot of the repository metadata. The *snapshot* role signs for *root*, *targets*, and all delegated targets roles. The delegated targets role *bins* -further delegates to the *bin-n* roles, which sign for all distributions +further delegates to the *bin-n* roles, which sign for all distribution files belonging to registered PyPI projects. Figure 2 provides an overview of the roles available within PyPI, which @@ -445,7 +446,7 @@ therefore, projects are safe from PyPI compromises. The minimum security model requires no action from a developer and protects against malicious CDNs [19]_ and public mirrors. To support continuous -delivery of uploaded packages, PyPI signs for projects with an online key. +delivery of uploaded distributions, PyPI signs for projects with an online key. This level of security prevents projects from being accidentally or deliberately tampered with by a mirror or a CDN because neither will have any of the keys required to sign for projects. However, it does not @@ -457,9 +458,9 @@ keys. These *bin-n* roles MUST all be delegated by the upper-level *bins* role, which is signed with an offline key, and in turn MUST be delegated by the top-level *targets* role, which is also signed with an offline key. This means that when a package manager such as pip (i.e., using TUF) downloads -a distribution from a project on PyPI, it will consult the *targets* role about -the TUF metadata for the project. If ultimately no *bin-n* roles delegated by -*targets* via *bins* specify the project's distribution, then the project is +a distribution file from a project on PyPI, it will consult the *targets* role about +the TUF metadata for that distribution file. If ultimately no *bin-n* roles +delegated by *targets* via *bins* specify the distribution file, then it is considered to be non-existent on PyPI. Note, the reason why *targets* does not directly delegate to *bin-n*, but @@ -504,7 +505,7 @@ split all targets in the *bins* role by delegating them to 16,384 for the PyPI targets whose SHA2-256 hashes fall into that bin (see and Figure 2 and `Consistent Snapshots`_). It was found that this number of bins would result in a 12-22% metadata overhead -(relative to the average size of downloaded packages; see V14 and +(relative to the average size of downloaded distribution files; see V14 and V16 in Table 2) for returning users, and a 153% overhead for new users who are installing pip for the first time (see V18 in Table 2). @@ -514,73 +515,73 @@ A few assumptions used in calculating these metadata overhead percentages: 2. pip will always be bundled with the latest good copy of metadata for all roles. -+------+---------------------------------------------+-----------+ -| Name | Description | Value | -+------+---------------------------------------------+-----------+ -| C1 | # of bytes in a SHA2-256 hexadecimal digest | 64 | -+------+---------------------------------------------+-----------+ -| C2 | # of bytes in a SHA2-512 hexadecimal digest | 128 | -+------+---------------------------------------------+-----------+ -| C3 | # of bytes for a SHA2-256 public key ID | 64 | -+------+---------------------------------------------+-----------+ -| C4 | # of bytes for an Ed25519 signature | 128 | -+------+---------------------------------------------+-----------+ -| C5 | # of bytes for an Ed25519 public key | 64 | -+------+---------------------------------------------+-----------+ -| C6 | # of bytes for a target relative file path | 256 | -+------+---------------------------------------------+-----------+ -| C7 | # of bytes to encode a target file size | 7 | -+------+---------------------------------------------+-----------+ -| C8 | # of bytes to encode a version number | 6 | -+------+---------------------------------------------+-----------+ -| C9 | # of targets (simple indices and packages) | 2,195,135 | -+------+---------------------------------------------+-----------+ -| C10 | Average # of bytes for a downloaded package | 1,000,000 | -+------+---------------------------------------------+-----------+ -| C11 | # of bins | 16,384 | -+------+---------------------------------------------+-----------+ ++------+--------------------------------------------------+-----------+ +| Name | Description | Value | ++------+--------------------------------------------------+-----------+ +| C1 | # of bytes in a SHA2-256 hexadecimal digest | 64 | ++------+--------------------------------------------------+-----------+ +| C2 | # of bytes in a SHA2-512 hexadecimal digest | 128 | ++------+--------------------------------------------------+-----------+ +| C3 | # of bytes for a SHA2-256 public key ID | 64 | ++------+--------------------------------------------------+-----------+ +| C4 | # of bytes for an Ed25519 signature | 128 | ++------+--------------------------------------------------+-----------+ +| C5 | # of bytes for an Ed25519 public key | 64 | ++------+--------------------------------------------------+-----------+ +| C6 | # of bytes for a target relative file path | 256 | ++------+--------------------------------------------------+-----------+ +| C7 | # of bytes to encode a target file size | 7 | ++------+--------------------------------------------------+-----------+ +| C8 | # of bytes to encode a version number | 6 | ++------+--------------------------------------------------+-----------+ +| C9 | # of targets (simple indices and distributions) | 2,195,135 | ++------+--------------------------------------------------+-----------+ +| C10 | Average # of bytes for a downloaded distribution | 1,000,000 | ++------+--------------------------------------------------+-----------+ +| C11 | # of bins | 16,384 | ++------+--------------------------------------------------+-----------+ Table 1: A list of constants used to calculate metadata overhead. -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| Name | Description | Formula | Value | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V1 | Length of a path hash prefix | math.ceil(math.log(C11, 16)) | 4 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V2 | Total # of path hash prefixes | 16**V1 | 65,536 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V3 | Avg # of targets per bin | math.ceil(C9/C11) | 134 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V4 | Avg size of SHA-256 hashes per bin | V3*C1 | 8,576 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V5 | Avg size of SHA-512 hashes per bin | V3*C2 | 17,152 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V6 | Avg size of target paths per bin | V3*C6 | 34,304 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V7 | Avg size of lengths per bin | V3*C7 | 938 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V8 | Avg size of bin-n metadata (bytes) | V4+V5+V6+V7 | 60,970 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V9 | Total size of public key IDs in bins | C11*C3 | 1,048,576 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V10 | Total size of path hash prefixes in bins | V1*V2 | 262,144 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V11 | Est. size of bins metadata (bytes) | V9+V10 | 1,310,720 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V12 | Est. size of snapshot metadata (bytes) | C11*C8 | 98,304 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V13 | Est. size of metadata overhead per package per returning user (same snapshot) | 2*V8 | 121,940 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V14 | Est. metadata overhead per package per returning user (same snapshot) | round((V13/C10)*100) | 12% | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V15 | Est. size of metadata overhead per package per returning user (diff snapshot) | V13+V12 | 220,244 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V16 | Est. metadata overhead per package per returning user (diff snapshot) | round((V15/C10)*100) | 22% | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V17 | Est. size of metadata overhead per package per new user | V15+V11 | 1,530,964 | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ -| V18 | Est. metadata overhead per package per new user | round((V17/C10)*100) | 153% | -+------+-------------------------------------------------------------------------------+------------------------------+-----------+ ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| Name | Description | Formula | Value | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V1 | Length of a path hash prefix | math.ceil(math.log(C11, 16)) | 4 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V2 | Total # of path hash prefixes | 16**V1 | 65,536 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V3 | Avg # of targets per bin | math.ceil(C9/C11) | 134 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V4 | Avg size of SHA-256 hashes per bin | V3*C1 | 8,576 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V5 | Avg size of SHA-512 hashes per bin | V3*C2 | 17,152 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V6 | Avg size of target paths per bin | V3*C6 | 34,304 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V7 | Avg size of lengths per bin | V3*C7 | 938 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V8 | Avg size of bin-n metadata (bytes) | V4+V5+V6+V7 | 60,970 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V9 | Total size of public key IDs in bins | C11*C3 | 1,048,576 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V10 | Total size of path hash prefixes in bins | V1*V2 | 262,144 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V11 | Est. size of bins metadata (bytes) | V9+V10 | 1,310,720 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V12 | Est. size of snapshot metadata (bytes) | C11*C8 | 98,304 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V13 | Est. size of metadata overhead per distribution per returning user (same snapshot) | 2*V8 | 121,940 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V14 | Est. metadata overhead per distribution per returning user (same snapshot) | round((V13/C10)*100) | 12% | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V15 | Est. size of metadata overhead per distribution per returning user (diff snapshot) | V13+V12 | 220,244 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V16 | Est. metadata overhead per distribution per returning user (diff snapshot) | round((V15/C10)*100) | 22% | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V17 | Est. size of metadata overhead per distribution per new user | V15+V11 | 1,530,964 | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ +| V18 | Est. metadata overhead per distribution per new user | round((V17/C10)*100) | 153% | ++------+------------------------------------------------------------------------------------+------------------------------+-----------+ Table 2: Estimated metadata overheads for new and returning users. @@ -774,9 +775,9 @@ How Should Metadata be Generated? Project developers expect the distributions they upload to PyPI to be immediately available for download. Unfortunately, there will be problems when many readers and writers simultaneously access the same metadata and -distributions. That is, there needs to be a way to ensure consistency of -metadata and repository files when multiple developers simultaneously change the -same metadata or distributions. There are also issues with consistency on PyPI +target files. That is, there needs to be a way to ensure consistency of +metadata and target files when multiple developers simultaneously change these +files. There are also issues with consistency on PyPI without TUF, but the problem is more severe with signed metadata that MUST keep track of the files available on PyPI in real-time. @@ -932,13 +933,13 @@ that clients can update to the latest *root* role keys, no matter how outdated their local *root* metadata is. -Revoking Trust in Projects and Versions -======================================= +Revoking Trust in Projects and Distributions +============================================ -From time to time either a project or a version of a package will need to be revoked. -To revoke trust in either a project or a version of a package, the associated bin-n role can -simply remove the corresponding targets and re-sign the bin-n metadata. This action only -requires actions with the online bin-n key. +From time to time either a project or a distribution will need to be revoked. +To revoke trust in either a project or a distribution, the associated bin-n +role can simply remove the corresponding targets and re-sign the bin-n +metadata. This action only requires actions with the online bin-n key. @@ -1084,9 +1085,9 @@ information must be validated: since the last period should be discarded. As a result, developers of new projects will need to re-register their projects. -3. If the packages themselves may have been tampered with, they can be - validated using the stored hash information for packages that existed at the - time of the last period. +3. If the target files themselves may have been tampered with, they can be + validated using the stored hash information for distributions that existed + at the time of the last period. In order to safely restore snapshots in the event of a compromise, PyPI SHOULD maintain a small number of its own mirrors to copy PyPI snapshots according to @@ -1100,12 +1101,12 @@ periodically and tweet it. Perhaps a user comes forward with the actual metadata and the repository maintainers can verify the metadata file's cryptographic hash. Alternatively, PyPI may periodically archive its own versions of *snapshot* rather than rely on externally provided metadata. In this case, -PyPI SHOULD take the cryptographic hash of every package on the repository and -store this data on an offline device. If any package hash has changed, this -indicates an attack. +PyPI SHOULD take the cryptographic hash of every target file on the +repository and store this data on an offline device. If any target file +hash has changed, this indicates an attack. As for attacks that serve different versions of metadata, or freeze a version -of a package at a specific version, they can be handled by TUF with techniques +of a distribution at a specific version, they can be handled by TUF with techniques like implicit key revocation and metadata mismatch detection [2]_. @@ -1119,7 +1120,7 @@ see the ongoing discussion in the TAP repository__. __ https://github.com/theupdateframework/taps/pull/107 Note that the changes to PyPI from this PEP will be backwards compatible. The -location of targets files and simple indices are not changed in this PEP, so any +location of target files and simple indices are not changed in this PEP, so any existing PyPI clients will still be able to perform updates using these files. This PEP adds the ability for clients to use TUF metadata to improve the security of the update process. @@ -1194,8 +1195,7 @@ References .. [14] https://mail.python.org/pipermail/distutils-sig/2013-September/022755.html .. [15] http://ed25519.cr.yp.to/ .. [16] https://github.com/theupdateframework/specification/blob/master/tuf-spec.md -.. [17] PEP 426, Metadata for Python Software Packages 2.0, Coghlan, Holth, Stufft - http://www.python.org/dev/peps/pep-0426/ +.. [17] https://packaging.python.org/glossary .. [18] https://en.wikipedia.org/wiki/Continuous_delivery .. [19] https://mail.python.org/pipermail/distutils-sig/2013-August/022154.html .. [20] https://en.wikipedia.org/wiki/Key-recovery_attack From 976c826b80f27ac52ab89932847fdbab580be264 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 7 Nov 2019 14:56:19 +0100 Subject: [PATCH 104/122] Update additional repository files section Mention duplication of target files required for consistent snapshots. --- pep-0458.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pep-0458.txt b/pep-0458.txt index 974d3236cd9..77caeab691c 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -306,6 +306,11 @@ kinds of metadata RECOMMENDED for PyPI. __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/METADATA.md +In addition, all target files SHOULD be stored on disk at least three times. +Once under their original filename, to provide backwards compatibility, and +twice with their SHA-256 and SHA-512 hash respectively included in their +filename. This is required to produce `Consistent Snapshots`_. + PyPI and TUF Metadata ===================== From 337a9ff561fa815ebedf4190c7117670a8e4ab2c Mon Sep 17 00:00:00 2001 From: lukpueh Date: Thu, 7 Nov 2019 14:59:55 +0100 Subject: [PATCH 105/122] Refine distribution file definition Acknowledge that it may be used interchangeably with "distribution package", as it is defined in the PPA glossary, or simply "distribution" or "package", for brevity, or because it is more common, e.g. for a pip option. Co-Authored-By: Joshua Lock --- pep-0458.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index b1b6634354d..ee943484b17 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -85,7 +85,7 @@ Before the wiki attack, PyPI used MD5 hashes to tell package managers, such as pip, whether or not a distribution file was corrupted in transit. However, the absence of SSL made it hard for package managers to verify transport integrity to PyPI. It was therefore easy to launch a man-in-the-middle attack between pip and -PyPI, and change distribution content arbitrarily. As a result, users could be tricked into +PyPI, and arbitrarily change the content of distributions. As a result, users could be tricked into installing malicious distributions. After the wiki attack, several steps were proposed (some of which were implemented) to deliver a much higher level of security than was previously the case. These steps included @@ -110,7 +110,7 @@ made to the Distutils mailing list showing that the latest version of pip (at the time) was susceptible to such attacks, and how TUF could protect users against them [14]_. Specifically, testing was done to see how pip would respond to these attacks with and without TUF. Attacks tested included replay -and freeze, arbitrary distribution, slow retrieval, and endless data. The post +and freeze, arbitrary installation, slow retrieval, and endless data. The post also included a demonstration of how pip would respond if PyPI were compromised. @@ -166,16 +166,16 @@ Additional terms used in this PEP are defined as follows: has a single metadata file that it is trusted to provide. * Distribution file: A versioned archive file that contains Python packages, - modules, and other resource files that are used to distribute a release. In - this document a distribution file may also be referred to with the single - word distribution. + modules, and other resource files that are used to distribute a release. The + terms *distribution file*, *distribution package* [17]_, or simply + *distribution* or *package* may be used interchangeably in this PEP. * Simple index: The HTML page that contains internal links to distribution files. * Target files: As a rule of thumb, target files are all files on PyPI whose integrity should be guaranteed with TUF. Typically, this includes - distribution files, and PyPI metadata such as simple indices. + distribution files and PyPI metadata, such as simple indices. * Metadata: Metadata are signed files that describe roles, other metadata, and target files. If not specified otherwise metadata means TUF-specific @@ -265,7 +265,7 @@ of pip going forward SHOULD use TUF by default to download and verify distributi from PyPI before installing them. However, there may be unforeseen issues that might prevent users from installing or updating distributions, including pip itself, via TUF. Therefore, pip SHOULD provide an option e.g., -`--unsafely-disable-distribution-verification`, in order to work around such issues +`--unsafely-disable-package-verification`, in order to work around such issues until they are resolved. Note, the proposed option name is purposefully long, because a user must be helped to understand that the action is unsafe and not generally recommended. @@ -1086,7 +1086,7 @@ information must be validated: projects will need to re-register their projects. 3. If the target files themselves may have been tampered with, they can be - validated using the stored hash information for distributions that existed + validated using the stored hash information for target files that existed at the time of the last period. In order to safely restore snapshots in the event of a compromise, PyPI SHOULD From 9df7a9609b280772bbb32702b69459cfbd5daaab Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 7 Nov 2019 17:48:26 +0100 Subject: [PATCH 106/122] Mention data deduplication for target file copies --- pep-0458.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 77caeab691c..9f72cd42e0e 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -306,11 +306,14 @@ kinds of metadata RECOMMENDED for PyPI. __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/METADATA.md -In addition, all target files SHOULD be stored on disk at least three times. +In addition, all target files SHOULD be available on disk at least three times. Once under their original filename, to provide backwards compatibility, and twice with their SHA-256 and SHA-512 hash respectively included in their filename. This is required to produce `Consistent Snapshots`_. +Depending on the used file system different data deduplication mechanisms MAY +be employed to avoid storage increase from hard copies of target files. + PyPI and TUF Metadata ===================== From cdd3a0e42571e8f72d0fd2adec353bc79c3ac69e Mon Sep 17 00:00:00 2001 From: Trishank K Kuppusamy Date: Thu, 7 Nov 2019 15:24:36 -0500 Subject: [PATCH 107/122] update metadata overhead --- pep-0458.txt | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index d6d2480b8af..b53d39e3e45 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -507,14 +507,14 @@ install or upgrade a PyPI project via TUF. __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/TUTORIAL.md#delegate-to-hashed-bins Based on our findings as of the time this document was updated for -implementation (Oct 7 2019), summarized in Tables 1-2, PyPI SHOULD +implementation (Nov 7 2019), summarized in Tables 1-2, PyPI SHOULD split all targets in the *bins* role by delegating them to 16,384 *bin-n* roles (see C11 in Table 1). Each *bin-n* role would sign for the PyPI targets whose SHA2-256 hashes fall into that bin (see and Figure 2 and `Consistent Snapshots`_). It was found -that this number of bins would result in a 12-22% metadata overhead +that this number of bins would result in a 6-10% metadata overhead (relative to the average size of downloaded distribution files; see V14 and -V16 in Table 2) for returning users, and a 153% overhead for new +V16 in Table 2) for returning users, and a 70% overhead for new users who are installing pip for the first time (see V18 in Table 2). A few assumptions used in calculating these metadata overhead percentages: @@ -542,13 +542,19 @@ A few assumptions used in calculating these metadata overhead percentages: +------+--------------------------------------------------+-----------+ | C8 | # of bytes to encode a version number | 6 | +------+--------------------------------------------------+-----------+ -| C9 | # of targets (simple indices and distributions) | 2,195,135 | +| C9 | # of targets (simple indices and distributions) | 2,273,539 | +------+--------------------------------------------------+-----------+ -| C10 | Average # of bytes for a downloaded distribution | 1,000,000 | +| C10 | Average # of bytes for a downloaded distribution | 2,184,393 | +------+--------------------------------------------------+-----------+ | C11 | # of bins | 16,384 | +------+--------------------------------------------------+-----------+ +C9 by computed querying the number of release files. +C10 was derived by taking the average between a rough estimate of the average +size of release files *downloaded* over the past 31 days (1,628,321 bytes), +and the average size of releases files on disk (2,740,465 bytes). +Ernest W. Durbin III helped to provide these numbers on November 7, 2019. + Table 1: A list of constants used to calculate metadata overhead. +------+------------------------------------------------------------------------------------+------------------------------+-----------+ @@ -558,17 +564,17 @@ Table 1: A list of constants used to calculate metadata overhead. +------+------------------------------------------------------------------------------------+------------------------------+-----------+ | V2 | Total # of path hash prefixes | 16**V1 | 65,536 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V3 | Avg # of targets per bin | math.ceil(C9/C11) | 134 | +| V3 | Avg # of targets per bin | math.ceil(C9/C11) | 139 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V4 | Avg size of SHA-256 hashes per bin | V3*C1 | 8,576 | +| V4 | Avg size of SHA-256 hashes per bin | V3*C1 | 8,896 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V5 | Avg size of SHA-512 hashes per bin | V3*C2 | 17,152 | +| V5 | Avg size of SHA-512 hashes per bin | V3*C2 | 17,792 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V6 | Avg size of target paths per bin | V3*C6 | 34,304 | +| V6 | Avg size of target paths per bin | V3*C6 | 35,584 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V7 | Avg size of lengths per bin | V3*C7 | 938 | +| V7 | Avg size of lengths per bin | V3*C7 | 973 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V8 | Avg size of bin-n metadata (bytes) | V4+V5+V6+V7 | 60,970 | +| V8 | Avg size of bin-n metadata (bytes) | V4+V5+V6+V7 | 63,245 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ | V9 | Total size of public key IDs in bins | C11*C3 | 1,048,576 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ @@ -578,17 +584,17 @@ Table 1: A list of constants used to calculate metadata overhead. +------+------------------------------------------------------------------------------------+------------------------------+-----------+ | V12 | Est. size of snapshot metadata (bytes) | C11*C8 | 98,304 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V13 | Est. size of metadata overhead per distribution per returning user (same snapshot) | 2*V8 | 121,940 | +| V13 | Est. size of metadata overhead per distribution per returning user (same snapshot) | 2*V8 | 126,490 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V14 | Est. metadata overhead per distribution per returning user (same snapshot) | round((V13/C10)*100) | 12% | +| V14 | Est. metadata overhead per distribution per returning user (same snapshot) | round((V13/C10)*100) | 6% | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V15 | Est. size of metadata overhead per distribution per returning user (diff snapshot) | V13+V12 | 220,244 | +| V15 | Est. size of metadata overhead per distribution per returning user (diff snapshot) | V13+V12 | 224,794 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V16 | Est. metadata overhead per distribution per returning user (diff snapshot) | round((V15/C10)*100) | 22% | +| V16 | Est. metadata overhead per distribution per returning user (diff snapshot) | round((V15/C10)*100) | 10% | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V17 | Est. size of metadata overhead per distribution per new user | V15+V11 | 1,530,964 | +| V17 | Est. size of metadata overhead per distribution per new user | V15+V11 | 1,535,514 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V18 | Est. metadata overhead per distribution per new user | round((V17/C10)*100) | 153% | +| V18 | Est. metadata overhead per distribution per new user | round((V17/C10)*100) | 70% | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ Table 2: Estimated metadata overheads for new and returning users. @@ -600,8 +606,8 @@ __ https://docs.google.com/spreadsheets/d/11_XkeHrf4GdhMYVqpYWsug6JNz5ZK6HvvmDZX This number of bins SHOULD increase when the metadata overhead for returning users exceeds 50%. Presently, this SHOULD happen when the number of targets -increase at least 4x from over 2M to nearly 9M, at which point the metadata -overhead for returning and new users would be around 49-59% and 190% +increase at least 8x from over 2M to over 18M, at which point the metadata +overhead for returning and new users would be around 46-51% and 111% respectively, assuming that the number of bins stay fixed. If the number of bins is increased, then the cost for all users would effectively be the cost for new users, because their cost would be dominated by the (once-in-a-while) From 12b8a5e03700ebc9d2d2cde6dadbee5c703aefd2 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Wed, 20 Nov 2019 10:21:28 -0800 Subject: [PATCH 108/122] remove sha256 and redo metadata overhead calculations --- pep-0458.txt | 93 +++++++++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 49 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index b53d39e3e45..4375669bd99 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -307,9 +307,9 @@ kinds of metadata RECOMMENDED for PyPI. __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/METADATA.md -In addition, all target files SHOULD be available on disk at least three times. +In addition, all target files SHOULD be available on disk at least two times. Once under their original filename, to provide backwards compatibility, and -twice with their SHA-256 and SHA-512 hash respectively included in their +twice with their SHA-512 hash included in their filename. This is required to produce `Consistent Snapshots`_. Depending on the used file system different data deduplication mechanisms MAY @@ -321,7 +321,7 @@ PyPI and TUF Metadata TUF metadata provides information that clients can use to make update decisions. For example, a *targets* metadata lists the available target files -on PyPI and includes the required signatures, cryptographic hashes, and +on PyPI and includes the required signatures, cryptographic hash, and file sizes for each. Different metadata files provide different information, which are signed by separate roles. The *root* role indicates what metadata belongs to each role. The concept of roles allows TUF to delegate responsibilities @@ -345,20 +345,19 @@ roles used in TUF. Figure 1: An overview of the TUF roles. Unless otherwise specified, this PEP RECOMMENDS that every metadata or -target file be hashed using both the SHA2-256 and SHA2-512 functions of +target file be hashed using the SHA2-512 function of the `SHA-2`__ family. SHA-2 has native and well-tested Python 2 and 3 support (allowing for verification of these hashes without additional, -non-Python dependencies), and using both functions should provide -sufficient protection against `collision attacks`__ for the foreseeable -future. However, this assumes that a collision attack for SHA2-256 does -not easily translate to SHA2-512. If stronger security guarantees are -required, then SHA2-256 and `SHA3-256`__ MAY be used instead, since they -are based on very different designs from each other. However, SHA-3 +non-Python dependencies). If stronger security guarantees are +required, then both SHA2-256 and SHA2-512 or both SHA2-256 and `SHA3-256`__ +MAY be used instead. SHA2-256 and SHA3-256 +are based on very different designs from each other, providing extra protection +against `collision attacks`__. However, SHA-3 requires installing additional, non-Python dependencies for `Python 2`__. __ https://en.wikipedia.org/wiki/SHA-2 -__ https://en.wikipedia.org/wiki/Collision_attack __ https://en.wikipedia.org/wiki/SHA-3 +__ https://en.wikipedia.org/wiki/Collision_attack __ https://pip.pypa.io/en/latest/development/release-process/#python-2-support @@ -509,13 +508,13 @@ __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/TUTORIAL.md#deleg Based on our findings as of the time this document was updated for implementation (Nov 7 2019), summarized in Tables 1-2, PyPI SHOULD split all targets in the *bins* role by delegating them to 16,384 -*bin-n* roles (see C11 in Table 1). Each *bin-n* role would sign -for the PyPI targets whose SHA2-256 hashes fall into that bin +*bin-n* roles (see C10 in Table 1). Each *bin-n* role would sign +for the PyPI targets whose SHA2-512 hashes fall into that bin (see and Figure 2 and `Consistent Snapshots`_). It was found that this number of bins would result in a 6-10% metadata overhead -(relative to the average size of downloaded distribution files; see V14 and -V16 in Table 2) for returning users, and a 70% overhead for new -users who are installing pip for the first time (see V18 in Table 2). +(relative to the average size of downloaded distribution files; see V13 and +V15 in Table 2) for returning users, and a 70% overhead for new +users who are installing pip for the first time (see V17 in Table 2). A few assumptions used in calculating these metadata overhead percentages: @@ -526,31 +525,29 @@ A few assumptions used in calculating these metadata overhead percentages: +------+--------------------------------------------------+-----------+ | Name | Description | Value | +------+--------------------------------------------------+-----------+ -| C1 | # of bytes in a SHA2-256 hexadecimal digest | 64 | +| C1 | # of bytes in a SHA2-512 hexadecimal digest | 128 | +------+--------------------------------------------------+-----------+ -| C2 | # of bytes in a SHA2-512 hexadecimal digest | 128 | +| C2 | # of bytes for a SHA2-512 public key ID | 64 | +------+--------------------------------------------------+-----------+ -| C3 | # of bytes for a SHA2-256 public key ID | 64 | +| C3 | # of bytes for an Ed25519 signature | 128 | +------+--------------------------------------------------+-----------+ -| C4 | # of bytes for an Ed25519 signature | 128 | +| C4 | # of bytes for an Ed25519 public key | 64 | +------+--------------------------------------------------+-----------+ -| C5 | # of bytes for an Ed25519 public key | 64 | +| C5 | # of bytes for a target relative file path | 256 | +------+--------------------------------------------------+-----------+ -| C6 | # of bytes for a target relative file path | 256 | +| C6 | # of bytes to encode a target file size | 7 | +------+--------------------------------------------------+-----------+ -| C7 | # of bytes to encode a target file size | 7 | +| C7 | # of bytes to encode a version number | 6 | +------+--------------------------------------------------+-----------+ -| C8 | # of bytes to encode a version number | 6 | +| C8 | # of targets (simple indices and distributions) | 2,273,539 | +------+--------------------------------------------------+-----------+ -| C9 | # of targets (simple indices and distributions) | 2,273,539 | +| C9 | Average # of bytes for a downloaded distribution | 2,184,393 | +------+--------------------------------------------------+-----------+ -| C10 | Average # of bytes for a downloaded distribution | 2,184,393 | -+------+--------------------------------------------------+-----------+ -| C11 | # of bins | 16,384 | +| C10 | # of bins | 16,384 | +------+--------------------------------------------------+-----------+ -C9 by computed querying the number of release files. -C10 was derived by taking the average between a rough estimate of the average +C8 by computed querying the number of release files. +C9 was derived by taking the average between a rough estimate of the average size of release files *downloaded* over the past 31 days (1,628,321 bytes), and the average size of releases files on disk (2,740,465 bytes). Ernest W. Durbin III helped to provide these numbers on November 7, 2019. @@ -560,41 +557,39 @@ Table 1: A list of constants used to calculate metadata overhead. +------+------------------------------------------------------------------------------------+------------------------------+-----------+ | Name | Description | Formula | Value | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V1 | Length of a path hash prefix | math.ceil(math.log(C11, 16)) | 4 | +| V1 | Length of a path hash prefix | math.ceil(math.log(C10, 16)) | 4 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ | V2 | Total # of path hash prefixes | 16**V1 | 65,536 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V3 | Avg # of targets per bin | math.ceil(C9/C11) | 139 | -+------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V4 | Avg size of SHA-256 hashes per bin | V3*C1 | 8,896 | +| V3 | Avg # of targets per bin | math.ceil(C8/C10) | 139 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V5 | Avg size of SHA-512 hashes per bin | V3*C2 | 17,792 | +| V4 | Avg size of SHA-512 hashes per bin | V3*C1 | 17,792 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V6 | Avg size of target paths per bin | V3*C6 | 35,584 | +| V5 | Avg size of target paths per bin | V3*C5 | 35,584 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V7 | Avg size of lengths per bin | V3*C7 | 973 | +| V6 | Avg size of lengths per bin | V3*C6 | 973 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V8 | Avg size of bin-n metadata (bytes) | V4+V5+V6+V7 | 63,245 | +| V7 | Avg size of bin-n metadata (bytes) | V4+V5+V76 | 54,349 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V9 | Total size of public key IDs in bins | C11*C3 | 1,048,576 | +| V8 | Total size of public key IDs in bins | C10*C2 | 1,048,576 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V10 | Total size of path hash prefixes in bins | V1*V2 | 262,144 | +| V9 | Total size of path hash prefixes in bins | V1*V2 | 262,144 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V11 | Est. size of bins metadata (bytes) | V9+V10 | 1,310,720 | +| V10 | Est. size of bins metadata (bytes) | V8+V9 | 1,310,720 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V12 | Est. size of snapshot metadata (bytes) | C11*C8 | 98,304 | +| V11 | Est. size of snapshot metadata (bytes) | C10*C7 | 98,304 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V13 | Est. size of metadata overhead per distribution per returning user (same snapshot) | 2*V8 | 126,490 | +| V12 | Est. size of metadata overhead per distribution per returning user (same snapshot) | 2*V7 | 108,698 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V14 | Est. metadata overhead per distribution per returning user (same snapshot) | round((V13/C10)*100) | 6% | +| V13 | Est. metadata overhead per distribution per returning user (same snapshot) | round((V12/C9)*100) | 5% | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V15 | Est. size of metadata overhead per distribution per returning user (diff snapshot) | V13+V12 | 224,794 | +| V14 | Est. size of metadata overhead per distribution per returning user (diff snapshot) | V12+V11 | 207,002 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V16 | Est. metadata overhead per distribution per returning user (diff snapshot) | round((V15/C10)*100) | 10% | +| V15 | Est. metadata overhead per distribution per returning user (diff snapshot) | round((V14/C9)*100) | 9% | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V17 | Est. size of metadata overhead per distribution per new user | V15+V11 | 1,535,514 | +| V16 | Est. size of metadata overhead per distribution per new user | V14+V10 | 1,517,722 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V18 | Est. metadata overhead per distribution per new user | round((V17/C10)*100) | 70% | +| V17 | Est. metadata overhead per distribution per new user | round((V16/C9)*100) | 69% | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ Table 2: Estimated metadata overheads for new and returning users. @@ -829,7 +824,7 @@ version of the *snapshot* metadata, which in turn lists the versions of the snapshot. The *targets* or delegated targets metadata refer to the actual target -files, including all of their cryptographic hashes as specified above. +files, including their cryptographic hashes as specified above. Thus, to mark a target file as part of a consistent snapshot it MUST, when written to disk, include its hash in its filename: From 9b3fd4a08a848791a95ce4f8c73388045096b8b3 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Wed, 20 Nov 2019 10:56:03 -0800 Subject: [PATCH 109/122] add description of version number scalability --- pep-0458.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index b53d39e3e45..699fac45474 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -819,7 +819,13 @@ include a version number in their filename: VERSION_NUMBER.ROLENAME.json, where VERSION_NUMBER is an incrementing integer, and ROLENAME is one of the top-level metadata roles -- *root*, *snapshot* or *targets* -- or one of - the delegated targets roles -- *bins* or *bin-n*. + the delegated targets roles -- *bins* or *bin-n*. The version number for + *snapshot*, *targets*, *bins* and *bin-n* roles may loop back to 0 as long + as the pervious 0-version metadata has expired before this occurs. To + prevent confusion, we suggest retiring the oldest metadata for each non-root + metadata type when 0.5*MAX_INT versions are in use. Root metadata can + not be retired, so the root metadata expiration should be set such that a + version number overflow is unlikely (ex. once a year). The only exception is the *timestamp* metadata file, whose version would not be known in advance when a client performs an update. The *timestamp* metadata From 534f946d2b620dfd835cf34e93437c5a0ba7b79f Mon Sep 17 00:00:00 2001 From: mnm678 Date: Wed, 20 Nov 2019 14:37:44 -0500 Subject: [PATCH 110/122] Update pep-0458.txt Co-Authored-By: Joshua Lock --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index 4375669bd99..a2b4e57e1ff 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -309,7 +309,7 @@ __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/METADATA.md In addition, all target files SHOULD be available on disk at least two times. Once under their original filename, to provide backwards compatibility, and -twice with their SHA-512 hash included in their +once with their SHA-512 hash included in their filename. This is required to produce `Consistent Snapshots`_. Depending on the used file system different data deduplication mechanisms MAY From d0deb7ac6470175a87f2867e430888c0881ebf30 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Wed, 20 Nov 2019 14:51:51 -0800 Subject: [PATCH 111/122] fix typos --- pep-0458.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 699fac45474..f6ab4f814b2 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -820,8 +820,8 @@ include a version number in their filename: where VERSION_NUMBER is an incrementing integer, and ROLENAME is one of the top-level metadata roles -- *root*, *snapshot* or *targets* -- or one of the delegated targets roles -- *bins* or *bin-n*. The version number for - *snapshot*, *targets*, *bins* and *bin-n* roles may loop back to 0 as long - as the pervious 0-version metadata has expired before this occurs. To + *snapshot*, *targets*, *bins*, and *bin-n* roles may eventually loop back to 0 as long + as the previous 0-version metadata has expired before this occurs. To prevent confusion, we suggest retiring the oldest metadata for each non-root metadata type when 0.5*MAX_INT versions are in use. Root metadata can not be retired, so the root metadata expiration should be set such that a From 9c12c3cb7f85351ea52409746b1f802d419c5f5d Mon Sep 17 00:00:00 2001 From: mnm678 Date: Thu, 21 Nov 2019 11:57:42 -0500 Subject: [PATCH 112/122] Update pep-0458.txt Co-Authored-By: lukpueh --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index a2b4e57e1ff..18d00bd2a9a 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -569,7 +569,7 @@ Table 1: A list of constants used to calculate metadata overhead. +------+------------------------------------------------------------------------------------+------------------------------+-----------+ | V6 | Avg size of lengths per bin | V3*C6 | 973 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V7 | Avg size of bin-n metadata (bytes) | V4+V5+V76 | 54,349 | +| V7 | Avg size of bin-n metadata (bytes) | V4+V5+V6 | 54,349 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ | V8 | Total size of public key IDs in bins | C10*C2 | 1,048,576 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ From 9bfcef8968cef111d9394885a6c672eb6e1e8f72 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 21 Nov 2019 17:40:36 +0100 Subject: [PATCH 113/122] Replace tuf roles overview image with text table --- pep-0458-1.png | Bin 95236 -> 0 bytes pep-0458.txt | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) delete mode 100644 pep-0458-1.png diff --git a/pep-0458-1.png b/pep-0458-1.png deleted file mode 100644 index b08801b5841cd35da8f719549e2c5377793e35f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95236 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�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

l*DH_W=+i*G%X`BWj>qD#x4La;3CY(x$bIpj? z^^H82)-TrD1PTT<0%kYm)2*Q@*%{57S2hi7Ea8unGY#CSI z@0g|ECT>ML^-@BMnTv##&5eFMTu7z5=7*(T=!v&42R5N@CA*~fatDG}dO&lEChVBd zv|0igp7+(_^DfDPxaN3JR8yVaT}odCb}!TI8enzi1bF|jG}P+yqw^`!sjiR8;EUbu zOjLcS#9YoL2q`%_uz5!1P-yY3CVEij)?DV|qBm(pDmK%>V}$prDKzL;QwEA4NGrUh zl66szpAC=+#GV-wg3HG2wOO^k50Jb5gkG%xkw`P}3-=u4AEcLVjS(0vea&&g*#+UQu?l8HRWf$Na4uRReP{3mI_ zjKGY}NG9y1`KIkz%D5BBp2n4hwprhr5G3gE05lx=uJkSIwRHr}veFKBk*Ba`Jm_L?--6&KdT8phA767+XCVEP2cWCE(A)@vs`qVNB@#K1;L*Po zn+~n%&>Y#1dYR1$w!mTLQ@i+3Xx_|;IyI@h7 z_68k5NfBfbPgZ{WDT3e#ew^lj+zmhMlZ-eX>9qEyZBD*5{4-Xs5M>bx_p3dU)U zb=BaHLH}0;dAHxSJG$jqa2E|c%T5cAFty6dmqI25ej*7lPYh)HEreQ<5Gt?WU-`29 zn7@MB7UWZs-J^Ch!A95wn_*j-_@))HpG%Gb!^E6WSnuCP=mBlY_qOxW3yWDq zaSggN&eS~KBFlqF$NTorBdhw-&}@MRry_XU7rE{Hf`%Qp>?hk;2Z>OC;oxh`^$#I0 zdl9rDwxt1szC)EEIi$n30%c8ob!m%7`_jndY^Y*2d`M`7!fWkGNHC305GU~d6I=28 zM*F;W#+MYNtbn!cJ8SKT7 z1{d%H^d2Ry0~cxp0iL;aR%pCL1>77Y8uI}qVT*z)J}_ars3XSgM@5#!lX9nYbRF

Q2_=EhmWfsDT>&KuAiO)qADxS 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 Date: Thu, 21 Nov 2019 17:42:04 +0100 Subject: [PATCH 114/122] Change tuf role table caption from figure to table Requires updating other captions: - Figure 2 --> Figure 1 - Table 1, 2, 4 --> Table 2, 3, 4 This commit also removes a stray "and". --- pep-0458.txt | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 72e74c45887..39851ca3792 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -337,7 +337,7 @@ paths of available target files together with their cryptographic hashes. The file paths must be specified relative to a base URL. This allows the actual target files to be served from anywhere, as long as the base URL can be accessed by the client. Each top-level role will serve its -responsibilities without exception. Figure 1 provides a table of the +responsibilities without exception. Table 1 provides an overview of the roles used in TUF. +-----------------------------------------------------------------------------+ @@ -382,7 +382,7 @@ roles used in TUF. | | of snapshot.json. | +-----------------+-----------------------------------------------------------+ -Figure 1: An overview of the TUF roles. +Table 1: An overview of the TUF roles. Unless otherwise specified, this PEP RECOMMENDS that every metadata or target file be hashed using the SHA2-512 function of @@ -411,7 +411,7 @@ new snapshot of the repository metadata. The *snapshot* role signs for *root*, further delegates to the *bin-n* roles, which sign for all distribution files belonging to registered PyPI projects. -Figure 2 provides an overview of the roles available within PyPI, which +Figure 1 provides an overview of the roles available within PyPI, which includes the top-level roles and the roles delegated to by *targets*. The figure also indicates the types of keys used to sign each role, and which roles are trusted to sign for files available on PyPI. The next two sections cover the @@ -419,7 +419,7 @@ details of signing repository files and the types of keys used for each role. .. image:: pep-0458-2.png -Figure 2: An overview of the role metadata available on PyPI. +Figure 1: An overview of the role metadata available on PyPI. The roles that change most frequently are *timestamp*, *snapshot* and roles delegated to by *bins* (i.e., *bin-n*). The *timestamp* and *snapshot* @@ -546,15 +546,16 @@ install or upgrade a PyPI project via TUF. __ https://github.com/theupdateframework/tuf/blob/v0.11.1/docs/TUTORIAL.md#delegate-to-hashed-bins Based on our findings as of the time this document was updated for -implementation (Nov 7 2019), summarized in Tables 1-2, PyPI SHOULD +implementation (Nov 7 2019), summarized in Tables 2-3, PyPI SHOULD split all targets in the *bins* role by delegating them to 16,384 -*bin-n* roles (see C10 in Table 1). Each *bin-n* role would sign +*bin-n* roles (see C10 in Table 2). Each *bin-n* role would sign for the PyPI targets whose SHA2-512 hashes fall into that bin (see and Figure 2 and `Consistent Snapshots`_). It was found that this number of bins would result in a 6-10% metadata overhead (relative to the average size of downloaded distribution files; see V13 and -V15 in Table 2) for returning users, and a 70% overhead for new -users who are installing pip for the first time (see V17 in Table 2). +V15 in Table 3) for returning users, and a 70% overhead for new +users who are installing pip for the first time (see V17 in Table 3). + A few assumptions used in calculating these metadata overhead percentages: @@ -592,7 +593,7 @@ size of release files *downloaded* over the past 31 days (1,628,321 bytes), and the average size of releases files on disk (2,740,465 bytes). Ernest W. Durbin III helped to provide these numbers on November 7, 2019. -Table 1: A list of constants used to calculate metadata overhead. +Table 2: A list of constants used to calculate metadata overhead. +------+------------------------------------------------------------------------------------+------------------------------+-----------+ | Name | Description | Formula | Value | @@ -632,7 +633,7 @@ Table 1: A list of constants used to calculate metadata overhead. | V17 | Est. metadata overhead per distribution per new user | round((V16/C9)*100) | 69% | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -Table 2: Estimated metadata overheads for new and returning users. +Table 3: Estimated metadata overheads for new and returning users. The interested reader may find an interactive version of the metadata overhead calculator here__: @@ -660,7 +661,7 @@ It is possible to make TUF metadata more compact by representing it in a binary format, as opposed to the JSON text format. Nevertheless, a sufficiently large number of projects and distributions will introduce scalability challenges at some point, and therefore the *bins* role will still need delegations (as -outlined in figure 2) in order to address the problem. The JSON format is an +outlined in Figure 1) in order to address the problem. The JSON format is an open and well-known standard for data interchange, which is already supported by the TUF reference implementation, and therefore the recommended data format by this PEP. However, due to the large number of delegations, compressed @@ -1001,7 +1002,7 @@ sign the metadata for each role. The remaining sections discuss how PyPI SHOULD audit repository metadata, and the methods PyPI can use to detect and recover from a PyPI compromise. -Table 3 summarizes a few of the attacks possible when a threshold number of +Table 4 summarizes a few of the attacks possible when a threshold number of private cryptographic keys (belonging to any of the PyPI roles) are compromised. The leftmost column lists the roles (or a combination of roles) that have been compromised, and the columns to its right show whether the @@ -1028,7 +1029,7 @@ is included in PEP 480 [21]_. | root | YES | +-----------------+-------------------+----------------+--------------------------------+ -Table 3: Attacks possible by compromising certain combinations of role keys. +Table 4: Attacks possible by compromising certain combinations of role keys. In `September 2013`__, it was shown how the latest version (at the time) of pip was susceptible to these attacks and how TUF could protect users against them [14]_. From b6f36086704d6309f9f1970af1a0a762f1ba7f4f Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 21 Nov 2019 17:51:05 +0100 Subject: [PATCH 115/122] Rename pep-0458-2.png -> pep-0458-1.png --- pep-0458-2.png => pep-0458-1.png | Bin pep-0458.txt | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename pep-0458-2.png => pep-0458-1.png (100%) diff --git a/pep-0458-2.png b/pep-0458-1.png similarity index 100% rename from pep-0458-2.png rename to pep-0458-1.png diff --git a/pep-0458.txt b/pep-0458.txt index 39851ca3792..56c7b2503f9 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -417,7 +417,7 @@ also indicates the types of keys used to sign each role, and which roles are trusted to sign for files available on PyPI. The next two sections cover the details of signing repository files and the types of keys used for each role. -.. image:: pep-0458-2.png +.. image:: pep-0458-1.png Figure 1: An overview of the role metadata available on PyPI. From 1374e1c0256443712c754445a5791cbff954291e Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 21 Nov 2019 18:26:29 +0100 Subject: [PATCH 116/122] Update overview of tuf role table - Update targets row to say that it signs the targets metadata - Update snapshot row to mention that it only lists targets and targets metadata and why it doesn't list root and timestamp. Co-Authored-By: Trishank Karthik Kuppusamy <33133073+trishankatdatadog@users.noreply.github.com> --- pep-0458.txt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 56c7b2503f9..99fce5be085 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -353,10 +353,10 @@ roles used in TUF. +-----------------+-----------------------------------------------------------+ | targets | The targets role is responsible for indicating which | | | target files are available from the repository. More | -| | precisely, the targets role shares the responsibility of | -| | providing information about the content of updates. The | -| | targets role may also delegate trust of repository files | -| | to other roles (delegated roles). | +| | precisely, it shares the responsibility of providing | +| | information about the content of updates. The targets | +| | role signs targets.json metadata, and can delegate trust | +| | for repository files to other roles (delegated roles). | +-----------------+-----------------------------------------------------------+ | delegated roles | If the top-level targets role performs delegation, the | | | resulting delegated roles can then provide their own | @@ -369,10 +369,13 @@ roles used in TUF. | snapshot | The snapshot role is responsible for ensuring that | | | clients see a consistent repository state. It provides | | | repository state information by indicating the latest | -| | versions of all metadata files on the repository in | -| | snapshot.json. The only metadata file not listed in | -| | snapshot.json is timestamp.json, which is always created | -| | after snapshot.json. | +| | versions of the top-level targets and delegated targets | +| | metadata files on the repository in snapshot.json. root | +| | and timestamp are not listed in snapshot.json, because | +| | timestamp signs for its freshness, after snapshot.json | +| | has been created, and root, which has all top-level keys, | +| | is required ahead of time to trust any of the top-level | +| | roles. | +-----------------+-----------------------------------------------------------+ | timestamp | The timestamp role is responsible for providing | | | information about the timeliness of available updates. | From e3a993ce541aade759ce72bcb347947ed8e5d886 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Fri, 22 Nov 2019 11:44:40 +0100 Subject: [PATCH 117/122] Update metadata calc in text after removing sha256 https://github.com/secure-systems-lab/peps/pull/71 removed sha256 hashes from targets metadata and correctly updated the metadata calculation in the tables, but not in the text. This commit updates the relevant numbers in the text. It further fixes an unrelated wording mistake in the metadata calc section. --- pep-0458.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 99fce5be085..a778677096c 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -554,9 +554,9 @@ split all targets in the *bins* role by delegating them to 16,384 *bin-n* roles (see C10 in Table 2). Each *bin-n* role would sign for the PyPI targets whose SHA2-512 hashes fall into that bin (see and Figure 2 and `Consistent Snapshots`_). It was found -that this number of bins would result in a 6-10% metadata overhead +that this number of bins would result in a 5-9% metadata overhead (relative to the average size of downloaded distribution files; see V13 and -V15 in Table 3) for returning users, and a 70% overhead for new +V15 in Table 3) for returning users, and a 69% overhead for new users who are installing pip for the first time (see V17 in Table 3). @@ -590,7 +590,7 @@ A few assumptions used in calculating these metadata overhead percentages: | C10 | # of bins | 16,384 | +------+--------------------------------------------------+-----------+ -C8 by computed querying the number of release files. +C8 was computed by querying the number of release files. C9 was derived by taking the average between a rough estimate of the average size of release files *downloaded* over the past 31 days (1,628,321 bytes), and the average size of releases files on disk (2,740,465 bytes). @@ -645,8 +645,8 @@ __ https://docs.google.com/spreadsheets/d/11_XkeHrf4GdhMYVqpYWsug6JNz5ZK6HvvmDZX This number of bins SHOULD increase when the metadata overhead for returning users exceeds 50%. Presently, this SHOULD happen when the number of targets -increase at least 8x from over 2M to over 18M, at which point the metadata -overhead for returning and new users would be around 46-51% and 111% +increase at least 10x from over 2M to over 22M, at which point the metadata +overhead for returning and new users would be around 50-54% and 114% respectively, assuming that the number of bins stay fixed. If the number of bins is increased, then the cost for all users would effectively be the cost for new users, because their cost would be dominated by the (once-in-a-while) From e48667b777b64d7bea694aeaeed591cac9e5d2d8 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Fri, 22 Nov 2019 14:44:31 -0800 Subject: [PATCH 118/122] explain snapshot revokation --- pep-0458.txt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index f6ab4f814b2..ffc667f8e36 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -819,13 +819,7 @@ include a version number in their filename: VERSION_NUMBER.ROLENAME.json, where VERSION_NUMBER is an incrementing integer, and ROLENAME is one of the top-level metadata roles -- *root*, *snapshot* or *targets* -- or one of - the delegated targets roles -- *bins* or *bin-n*. The version number for - *snapshot*, *targets*, *bins*, and *bin-n* roles may eventually loop back to 0 as long - as the previous 0-version metadata has expired before this occurs. To - prevent confusion, we suggest retiring the oldest metadata for each non-root - metadata type when 0.5*MAX_INT versions are in use. Root metadata can - not be retired, so the root metadata expiration should be set such that a - version number overflow is unlikely (ex. once a year). + the delegated targets roles -- *bins* or *bin-n*. The only exception is the *timestamp* metadata file, whose version would not be known in advance when a client performs an update. The *timestamp* metadata @@ -834,6 +828,14 @@ version of the *snapshot* metadata, which in turn lists the versions of the *targets* and delegated targets metadata, all as part of a given consistent snapshot. +In normal usage, version number overflow is unlikely to occur as an 8-byte +integer can be incremented once per millisecond and last almost 300 million +years. If an attacker increases the version number arbitrarily +(e.g. to MAX_INT), the repository can recover by revoking the snapshot role +to reset the version number as described in the TUF specification__. + +__https://github.com/theupdateframework/specification/blame/4b82990afdc6c6d77aa9d43e0632f01bb9e7752c/tuf-spec.md#L1112-L1120 + The *targets* or delegated targets metadata refer to the actual target files, including all of their cryptographic hashes as specified above. Thus, to mark a target file as part of a consistent snapshot it MUST, when From b5d8c83fb157f0f6c854eb910928c9b122f62517 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Mon, 25 Nov 2019 10:38:09 +0100 Subject: [PATCH 119/122] Add padding to fix malformed text table --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index a778677096c..423f3c770bb 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -613,7 +613,7 @@ Table 2: A list of constants used to calculate metadata overhead. +------+------------------------------------------------------------------------------------+------------------------------+-----------+ | V6 | Avg size of lengths per bin | V3*C6 | 973 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ -| V7 | Avg size of bin-n metadata (bytes) | V4+V5+V6 | 54,349 | +| V7 | Avg size of bin-n metadata (bytes) | V4+V5+V6 | 54,349 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ | V8 | Total size of public key IDs in bins | C10*C2 | 1,048,576 | +------+------------------------------------------------------------------------------------+------------------------------+-----------+ From bbbf843ee5ffcd4233db6b6a121e41cab3cb829d Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Fri, 22 Nov 2019 16:46:42 +0100 Subject: [PATCH 120/122] Update "Producing Consistent Snapshots" Following discussions with @dstufft and @trishankatdatadog regarding file uploads and simple index generation on PyPI (see secure-systems-lab/peps#70) this commit once more refines the "producing consistent snapshots" section. It includes the following changes: - Remove the notion of *transaction processes* and instead talk about *uploads*. Background: Transaction processes are only relevant if multiple files of a project release need to be handled in a single transaction, which is not the case on PyPI, where each upload of a distribution file is self-contained. With this change, upload process just place files into a queue, without updating bin-n metadata (as transaction processes would have done in parallel), and all the metadata update/creation work is done by the snapshot process in strictly sequential manner. - Add a paragraph about simple index pages and how their hashes should be included in *bin-n* metadata, and how they need to remain stable if re-generated dynamically. --- pep-0458.txt | 78 +++++++++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index 423f3c770bb..aa117ace76d 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -907,55 +907,51 @@ efficiently transfer consistent snapshots from PyPI. Producing Consistent Snapshots ------------------------------ -When a new project release is uploaded to PyPI, PyPI MUST update the *bin-n* -metadata responsible for the target files of the project release. Remember that -target files are sorted into bins by their filename hashes. Consequentially, -PyPI MUST update *snapshot* to account for the updated *bin-n* metadata, and -*timestamp* to account for the updated *snapshot* metadata. These updates -SHOULD be handled by automated processes, e.g. one or more *transaction -processes* and one *snapshot process*. - -Each transaction process keeps track of a project upload, adds all new target -files to the most recent, relevant *bin-n* metadata and informs the -snapshot process to produce a consistent snapshot. Each project release SHOULD -be handled in an atomic transaction, so that a given consistent snapshot -contains all target files of a project release. However, transaction processes -MAY be parallelized under the following constraints: - -- Pairs of transaction processes MUST NOT concurrently work on the same project. -- Pairs of transaction processes MUST NOT concurrently work on projects that - belong to the same *bin-n* role. - -When a transaction process is finished updating the relevant *bin-n* metadata -it informs the snapshot process to generate a new consistent snapshot. The -snapshot process does so by taking the updated *bin-n* metadata, incrementing -their respective version numbers, signing them with the *bin-n* role key(s), -and writing them to *VERSION_NUMBER.bin-N.json*. - -Similarly, the snapshot process then takes the most recent *snapshot* metadata, -updates its *bin-n* metadata version numbers, increments its own version -number, signs it with the *snapshot* role key, and writes it to -*VERSION_NUMBER.snapshot.json*. +When a new distribution file is uploaded to PyPI, PyPI MUST update the +responsible *bin-n* metadata. Remember that all target files are sorted into +bins by their filename hashes. PyPI MUST also update *snapshot* to account for +the updated *bin-n* metadata, and *timestamp* to account for the updated +*snapshot* metadata. These updates SHOULD be handled by an automated *snapshot +process*. + +File uploads MAY be handled in parallel, however, consistent snapshots MUST be +produced in a strictly sequential manner. Furthermore, as long as distribution +files are self-contained, a consistent snapshot MAY be produced for each +uploaded file. To do so upload processes place new distribution files into a +concurrency-safe FIFO queue and the snapshot process reads from that queue one +file at a time and performs the following tasks: + +First, it adds the new file path to the relevant *bin-n* metadata, increments +its version number, signs it with the *bin-n* role key, and writes it to +*VERSION_NUMBER.bin-N.json*. + +Then, it takes the most recent *snapshot* metadata, updates its *bin-n* +metadata version numbers, increments its own version number, signs it with the +*snapshot* role key, and writes it to *VERSION_NUMBER.snapshot.json*. And finally, the snapshot process takes the most recent *timestamp* metadata, updates its *snapshot* metadata hash and version number, increments its own version number, sets a new expiration time, signs it with the *timestamp* role key, and writes it to *timestamp.json*. -The snapshot process MUST generate consistent snapshots sequentially, reading -the notifications received from the transaction process(es) from a -concurrency-safe FIFO queue. Fortunately, the operation of signing is fast -enough that this may be done a thousand or more times per second. +When updating *bin-n* metadata for a consistent snapshot, the snapshot process +SHOULD also include any new or updated hashes of simple index pages in the +relevant *bin-n* metadata. Note that, simple index pages may be generated +dynamically on API calls, so it is important that their output remains stable +throughout the validity of a consistent snapshot. -If there are multiple files in a release, a project MAY release these files in -separate transactions. For example, a project MAY release files for Windows in -one transaction, and the files for Linux in another transaction. However, a project -SHOULD release files that must belong together in order for everything to work -in the same transaction. +Since the snapshot process MUST generate consistent snapshots in a strictly +sequential manner it constitutes a bottleneck. Fortunately, the operation of +signing is fast enough that this may be done a thousand or more times per +second. -At any rate, PyPI SHOULD use a `transaction log`__ to record project -transaction processes and the snapshot queue for auditing and to recover from -errors after a server failure. +Moreover, PyPI MAY serve distribution files to clients before the corresponding +consistent snapshot metadata is generated. In that case the client software +SHOULD inform the user that full TUF protection is not yet available but will +be shortly. + +PyPI SHOULD use a `transaction log`__ to record upload processes and the +snapshot queue for auditing and to recover from errors after a server failure. __ https://en.wikipedia.org/wiki/Transaction_log From 6b080bdde93cf10a3365af3c2d0eb680361a8474 Mon Sep 17 00:00:00 2001 From: mnm678 Date: Mon, 25 Nov 2019 13:00:28 -0500 Subject: [PATCH 121/122] Update pep-0458.txt Co-Authored-By: lukpueh --- pep-0458.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0458.txt b/pep-0458.txt index ffc667f8e36..a4095e9f645 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -834,7 +834,7 @@ years. If an attacker increases the version number arbitrarily (e.g. to MAX_INT), the repository can recover by revoking the snapshot role to reset the version number as described in the TUF specification__. -__https://github.com/theupdateframework/specification/blame/4b82990afdc6c6d77aa9d43e0632f01bb9e7752c/tuf-spec.md#L1112-L1120 +__ https://github.com/theupdateframework/specification/blame/4b82990afdc6c6d77aa9d43e0632f01bb9e7752c/tuf-spec.md#L1112-L1120 The *targets* or delegated targets metadata refer to the actual target files, including all of their cryptographic hashes as specified above. From 6584424b228626d4386ae2704ec660067496ef22 Mon Sep 17 00:00:00 2001 From: marinamoore Date: Mon, 25 Nov 2019 10:07:04 -0800 Subject: [PATCH 122/122] Clarify that an 8 byte integer is not required and reword the version number reset to make it more generic --- pep-0458.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pep-0458.txt b/pep-0458.txt index a4095e9f645..0635a521db2 100644 --- a/pep-0458.txt +++ b/pep-0458.txt @@ -828,11 +828,11 @@ version of the *snapshot* metadata, which in turn lists the versions of the *targets* and delegated targets metadata, all as part of a given consistent snapshot. -In normal usage, version number overflow is unlikely to occur as an 8-byte -integer can be incremented once per millisecond and last almost 300 million -years. If an attacker increases the version number arbitrarily -(e.g. to MAX_INT), the repository can recover by revoking the snapshot role -to reset the version number as described in the TUF specification__. +In normal usage, version number overflow is unlikely to occur. An 8-byte integer, +for instance, can be incremented once per millisecond and last almost 300 million +years. If an attacker increases the version number arbitrarily, the repository +can recover by revoking the compromised keys and resetting the version number as +described in the TUF specification__. __ https://github.com/theupdateframework/specification/blame/4b82990afdc6c6d77aa9d43e0632f01bb9e7752c/tuf-spec.md#L1112-L1120