From ea1f60bc56c5e88f03f5fd563c790139da7df7b5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 18 Sep 2024 16:31:20 -0400 Subject: [PATCH 01/11] Add RFC for JWS-signing PGXN releases --- text/0000-release-meta-spec-v2.md | 305 ++++++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 text/0000-release-meta-spec-v2.md diff --git a/text/0000-release-meta-spec-v2.md b/text/0000-release-meta-spec-v2.md new file mode 100644 index 0000000..6a8fd97 --- /dev/null +++ b/text/0000-release-meta-spec-v2.md @@ -0,0 +1,305 @@ +{{#title PGXN RFC–0 — PGXN Release Meta Spec v2}} +* **RFC:** 0 (fill in with pull request number) +* **Title:** PGXN Release Meta Spec v2 +* **Slug:** `release-meta-spec-v2` +* **Start Date:** 2024-09-18 +* **Status:** Proposed Standard +* **Category:** Packaging +* **Pull Request:** [pgxn/rfcs#4](https://github.com/pgxn/rfcs/pull/4) +* **Implementation Issue:** TBD + +# RFC--0 --- PGXN Release Meta Spec v2 + +## Abstract + +This document describes version 2.0.0 of the *release* format for [PGXN] +source distribution metadata. It extends the *distribution* [PGXN Meta Spec +v2] `META.json` provided by authors in PGXN uploads PGXN with signed metadata +about the release on PGXN. This will allow clients to verify the validity of +PGXN release sources via public key verification. + +## Introduction + +### Background + +When [PGXN Manager] was implemented in 2010, in addition to publishing release +zip files that contain author-supplied [PGXN Meta Spec v1] metadata, it also +published an augmented `META.json` file, appending three fields representing +the release: + +* `user`: The name of the user who made the release +* `date`: A timestamp for the release +* `sha1`: A [SHA-1] digest of the release zip file + +This allowed clients to download a source zip file and validate it against the +checksum. The `user` and `date` information were provided mainly for +informational purposes. + +Compare, for example, pair-0.1.7 [release META.json] to the author-provided +[distribution META.json]. The difference is these three fields: + +``` json +{ + "user": "theory", + "date": "2020-10-25T21:54:02Z", + "sha1": "5b9e3ba948b18703227e4dea17696c0f1d971759" +} +``` + +Using the release metadata, a client can determine the URL to download the zip +file, then validate it against the SHA-1 digest. + +### Signed Releases + +A lot has changed since 2010, including an increasing need for [public key +signing] to validate that distribution files come from validated sources. +[PGXN Meta Spec v2] provides an opportunity to include signed metadata to +enable a much more secure method of validation. + +This RFC therefore proposes to extend [PGXN Meta Spec v2] distribution +metadata with a single additional property, `release`, that contains an [JWS +JSON Serialization] object as defined by [RFC 7515][JWS]. This will allow +clients not only to find the release file to download and verify it against +checksums, but also validate it against a public key provided by PGXN. + +The design allows multiple digital signatures, which in the future may allow +authors or other entities to sign releases with their own keys. The new format +would append a structure such as this to the distribution `META.json` file: + +``` json +{ + "release": { + "pgxn": { + "payload": "eyJ1c2VyIjoidGhlb3J5IiwiZGF0ZSI6IjIwMjQtMDktMTNUMTc6MzI6NTVaIiwidXJpIjoiZGlzdC9wYWlyLzAuMS43L3BhaXItMC4xLjcuemlwIiwiZGlnZXN0cyI6eyJzaGE1MTIiOiJiMzUzYjVhODJiM2I1NGU5NWY0YTI4NTllN2EyYmQwNjQ4YWJjYjM1YTdjMzYxMmIxMjZjMmM3NTQzOGZjMmY4ZThlZTFmMTllNjFmMzBmYTU0ZDdiYjY0YmNmMjE3ZWQxMjY0NzIyYjQ5N2JjYjYxM2Y4MmQ3ODc1MTUxNWI2NyJ9fQ", + "signatures": [ + { + "protected":"eyJhbGciOiJSUzI1NiJ9", + "header": {"kid": "2024-12-29" }, + "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-rLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZES c6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" + } + ] + } + } +} +``` + +This example includes a PGXN release signature. The data signed is the contents +of the `payload` property, which is the Base64 URL-encoded representation of +this JSON object (without the blank space): + +``` json +{ + "user": "theory", + "date": "2024-09-13T17:32:55Z", + "uri": "dist/pair/0.1.7/pair-0.1.7.zip", + "digests": { + "sha512": "b353b5a82b3b54e95f4a2859e7a2bd0648abcb35a7c3612b126c2c75438fc2f8e8ee1f19e61f30fa54d7bb64bcf217ed1264722b497bcb613f82d78751515b67" + } +} +``` + +This would allow a client to verify that the payload was signed by PGXN, and +then use the URI to download the release file and verify it with the SHA-512 +digest. This ensures that, when validation is properly followed, it is rooted +by PGXN's private key, and therefore the distribution file can be fully +trusted as unmodified since PGXN signed it. + +## Guide-level explanation + +### Terminology ### + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in [IETF RFC 2119]. + +This RFC makes use of the following additional terms: + +#### Distribution #### + +A named source code package published by a PGXN user. Each time a user +publishes a new version, it's considered a *Release* of the *Distribution"*. + +#### Distribution Metadata #### + +The `META.json` file maintained by the distribution author and updated with a +new version and any other relevant changes on each release. + +#### Upload #### + +The act of a user wishing to make a new Release of a distribution. The +included `META.json` should have an updated, unique version. + +#### Release #### + +A single release of a Distribution on PGXN, uniquely identified by the +Distribution name and Release version, and signed and published by PGXN. + +#### JWS #### + +[JWS], JSON Web Signature, represents content secured with digital signatures +or Message Authentication Codes (MACs) using JSON-based data structures. This +data format and key-signing pattern to be used by PGXN. + +### Process ### + +A PGXN user maintains a `META.json` file as defined by [PGXN Meta Spec v2] as +part of the source code package they distribute. A minimal example for an +extension distribution named `pair`, which contains a Postgres extension of +the same name: + +```json +{ + "name": "pair", + "abstract": "A key/value pair data type", + "version": "0.1.7", + "maintainers": [ + { + "name": "David E. Wheeler", + "email": "david@justatheory.com" + } + ], + "license": "PostgreSQL", + "contents": { + "extensions": { + "pair": { + "sql": "sql/pair.sql", + "control": "pair.control" + } + } + }, + "meta-spec": { "version": "2.0.0" } +} +``` + +#### Publishing a Release #### + +The steps to publish a signed release on PGXN would be: + +1. User updates the version in the `META.json` as appropriate for the + release, then bundles the `META.json` file and all required and + recommended source code and documentation files into a zip file. From a + Git repository, for example: + + ```sh + git archive --format zip --prefix=pair-0.1.7/ -o pair-0.1.7.zip HEAD + ``` + +2. User uploads the file to [PGXN Manager]. + +3. PGXN Manager validates the `META.json` file and, in some cases, rewrites + the uploaded file (if it doesn't have the directory prefix, for example, or + is uploaded in some other archive format than zip). + +4. PGXN Manager copies the `META.json` file, constructs the payload for + sighing, signs with its public key, then adds the `release` object to the + copied `META.json` file. + +5. PGXN Manager publishes the distribution archive and the `META.json` file + to the root repository, along with an additional file that lists all the + releases of the distribution. These files would be: + + * Release list: `dist/pair.json` + * 0.1.7 release `META.json`: `dist/pair/0.1.7/META.json` + * 0.1.7 release zip file: `dist/pair/0.1.7/pair-0.1.7.zip` + +#### Installing a Release #### + +The steps for a client to find, download, and verify a PGXN release would be: + +1. Using a valid PGXN mirror, assemble and fetch the release list for the + the extension, `dist/pair.json`. +2. Use the release list to determine the best version to install and + assemble its release `META.json` URI. The format is + `dist/{name}/{version}/META.json`; for the above example, that results + in `dist/pair/0.1.7/META.json`. +3. Fetch the release `META.json` file, read in the `releases/pgxn` object, + and use PGXN's public key to verify that it was signed by PGXN. +4. Decode the payload and use its `uri` field to download the release zip + file. +5. Compare the the `sha512` digest from the payload to a digest generated + from the downloaded zip file. +6. If they digests are the same, continue with building and installing. + Otherwise abort with an error. + +## Reference-level explanation + +This design ensures proper validation of PGXN releases by the following chain +of trust: + +1. A root key pair is maintained by PGXN, with the private key kept offline. +2. A release key pair is generated and signed by the private root key, with + the private key kept in an online vault accessible only to PGXN Manager. +3. The public keys for both keys are published by PGXN. +4. PGXN Manager uses the private release key to sign releases as described + above. The most important property in the signed payload is the list of + digests. +5. Clients can verify the signature with the public release and root keys. +6. With the data validated, the client can download and verify the release + file against a signed digest. In this manner, the authenticity of the + release file can be verified all the way to the root key. + +To support this infrastructure, PGXN Manager **MUST** be updated to properly +generate and sign the payload and include it in the release `META.json` files. +Clients **MUST** follow the [JWS validation steps]. + +## Drawbacks + +* This pattern could make it more difficult for clients to install code from + PGXN, especially if they incorrectly validate the signature. +* Some clients may choose not to implement the validation, potentially + leaving users to think they have trusted, validated code when they may + not. + +## Rationale and alternatives + +* This design takes advantage of the [JWS] standard to naturally augment the + PGXN `META.json` format to enable best-in-class digital signatures and + validation. +* Without key signing, questions will gradually mount as to the + trustworthiness of releases published on PGXN. In the era of [what's teh + word I want here?] compromises, it's essential for PGXN to enable + compromise detection. + +## Prior art + +This design was inspired by the [Python wheel] format, particularly its +precedent of signing only the hash digests for a package rather than the +package itself. The use of [JWS JSON Serialization] varies from [Python +wheel], and is enabled by the separation of the release `META.json` file from +the release file it describes. + +The use of [JWS] ensures a widely-vetted key signing and distribution +standard, and the likelihood that clients can take advantage of well-tested, +mature libraries to handle signing and validation. And finally, its design +allows for key rotation when necessary. + +## Unresolved questions + +None currently. + +## Future possibilities + +By embedding the PGXN [JWS] data under its own, key, `pgxn`, the design allows +for other parties to add their own release metadata and signatures. For +example, an organization that provides security scanning services may want to +add their own signature to validate that they have tested a specific release. + +For the PGXN signing itself the proposed use of a separate, offline "root" key +to sign an intermediate "release" key would allow for easier key rotation in +the event the "release" private key was compromised. + + [PGXN]: https://pgxn.org "PostgreSQL Extension Network" + [PGXN Manager]: https://manager.pgxn.org + [PGXN Meta Spec v1]: 0001-meta-spec-v1.md + [SHA-1]: https://en.wikipedia.org/wiki/SHA-1 "Wikipedia: SHA-1" + [release META.json]: https://master.pgxn.org/dist/pair/0.1.7/META.json + [distribution META.json]: https://api.pgxn.org/src/pair/pair-0.1.7/META.json + [public key signing]: https://en.wikipedia.org/wiki/Digital_signature + "Wikipedia: Digital signature" + [PGXN Meta Spec v2]: 0003-meta-spec-v2.md + [JWS]: https://www.rfc-editor.org/rfc/rfc7515.html "JSON Web Signature (JWS)" + [JWS JSON Serialization]: https://www.rfc-editor.org/rfc/rfc7515.html#section-7.2 + "RFC 7515: JWS JSON Serialization" + [IETF RFC 2119]: https://www.ietf.org/rfc/rfc2119.txt + [JWS validation steps]: https://www.rfc-editor.org/rfc/rfc7515.html#section-5.2 + "RFC 7515: Message Signature or MAC Validation" From fa66f7aba0186b7786194ec27f10a7dc77603fc7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Sep 2024 15:00:05 -0400 Subject: [PATCH 02/11] Make an edit pass, define object structures Also add some items to the "Unresolved questions" section and a "Future possibilities" item for author signing. --- text/0000-release-meta-spec-v2.md | 267 ++++++++++++++++++++++++------ 1 file changed, 213 insertions(+), 54 deletions(-) diff --git a/text/0000-release-meta-spec-v2.md b/text/0000-release-meta-spec-v2.md index 6a8fd97..f259b6b 100644 --- a/text/0000-release-meta-spec-v2.md +++ b/text/0000-release-meta-spec-v2.md @@ -14,29 +14,30 @@ This document describes version 2.0.0 of the *release* format for [PGXN] source distribution metadata. It extends the *distribution* [PGXN Meta Spec -v2] `META.json` provided by authors in PGXN uploads PGXN with signed metadata -about the release on PGXN. This will allow clients to verify the validity of -PGXN release sources via public key verification. +v2][v2] `META.json` provided by authors in PGXN uploads PGXN with signed +metadata about the release on PGXN. This will allow clients to verify the +validity of PGXN release sources via public key verification. ## Introduction ### Background -When [PGXN Manager] was implemented in 2010, in addition to publishing release -zip files that contain author-supplied [PGXN Meta Spec v1] metadata, it also -published an augmented `META.json` file, appending three fields representing -the release: +When [PGXN Manager] was implemented in 2010, it published release zip files +that contain author-supplied [PGXN Meta Spec v1][v1] metadata, and also +published an augmented `META.json` file with three additional properties +representing the release: * `user`: The name of the user who made the release * `date`: A timestamp for the release * `sha1`: A [SHA-1] digest of the release zip file -This allowed clients to download a source zip file and validate it against the -checksum. The `user` and `date` information were provided mainly for -informational purposes. +Clients were advised to download both this release `META.json` along with the +source zip file, and to validate the zip file against the checksum. The `user` +and `date` information were provided mainly for informational purposes. -Compare, for example, pair-0.1.7 [release META.json] to the author-provided -[distribution META.json]. The difference is these three fields: +Compare, for example, the pair-0.1.7 [release META.json] to the +author-provided [distribution META.json]. The difference is these three +fields: ``` json { @@ -52,15 +53,15 @@ file, then validate it against the SHA-1 digest. ### Signed Releases A lot has changed since 2010, including an increasing need for [public key -signing] to validate that distribution files come from validated sources. -[PGXN Meta Spec v2] provides an opportunity to include signed metadata to -enable a much more secure method of validation. +signing] to validate that distribution files come from trusted sources. [PGXN +Meta Spec v2][v2] provides an opportunity to update the release `META.json` +format with signed metadata to enable a much more secure method of validation. -This RFC therefore proposes to extend [PGXN Meta Spec v2] distribution -metadata with a single additional property, `release`, that contains an [JWS -JSON Serialization] object as defined by [RFC 7515][JWS]. This will allow -clients not only to find the release file to download and verify it against -checksums, but also validate it against a public key provided by PGXN. +This RFC therefore proposes to extend [v2] distribution metadata with a single +additional property, `release`, that contains a [JWS JSON Serialization] +object as defined by [RFC 7515][JWS]. This property will allow clients not +only to find the release file to download and verify against checksums, but +also validate it against a public key provided by PGXN. The design allows multiple digital signatures, which in the future may allow authors or other entities to sign releases with their own keys. The new format @@ -85,7 +86,7 @@ would append a structure such as this to the distribution `META.json` file: This example includes a PGXN release signature. The data signed is the contents of the `payload` property, which is the Base64 URL-encoded representation of -this JSON object (without the blank space): +this JSON object (with blank space formatting removed): ``` json { @@ -98,10 +99,10 @@ this JSON object (without the blank space): } ``` -This would allow a client to verify that the payload was signed by PGXN, and -then use the URI to download the release file and verify it with the SHA-512 -digest. This ensures that, when validation is properly followed, it is rooted -by PGXN's private key, and therefore the distribution file can be fully +A client **SHOULD** verify that the payload was signed by PGXN, and then use +the URI to download the release file and verify it with the SHA-512 digest. +This pattern ensures that, when validation is properly implemented, it is +rooted by PGXN's private key, and therefore the distribution file can be fully trusted as unmodified since PGXN signed it. ## Guide-level explanation @@ -126,8 +127,8 @@ new version and any other relevant changes on each release. #### Upload #### -The act of a user wishing to make a new Release of a distribution. The -included `META.json` should have an updated, unique version. +The act of a making a new Release of a Distribution. The included `META.json` +**MUST** have an updated, unique version. #### Release #### @@ -138,14 +139,14 @@ Distribution name and Release version, and signed and published by PGXN. [JWS], JSON Web Signature, represents content secured with digital signatures or Message Authentication Codes (MACs) using JSON-based data structures. This -data format and key-signing pattern to be used by PGXN. +RFC proposes to us this standard to sign PGXN Releases. ### Process ### -A PGXN user maintains a `META.json` file as defined by [PGXN Meta Spec v2] as -part of the source code package they distribute. A minimal example for an -extension distribution named `pair`, which contains a Postgres extension of -the same name: +A PGXN user maintains a `META.json` file as defined by [PGXN Meta Spec v2][v2] +as part of the source code package they distribute. This example provides +metadata for a distribution named `pair`, which contains a Postgres extension +of the same name: ```json { @@ -181,7 +182,7 @@ The steps to publish a signed release on PGXN would be: Git repository, for example: ```sh - git archive --format zip --prefix=pair-0.1.7/ -o pair-0.1.7.zip HEAD + git archive --format zip --prefix=pair-0.1.7/ -o pair-0.1.7.zip HEAD ``` 2. User uploads the file to [PGXN Manager]. @@ -191,11 +192,11 @@ The steps to publish a signed release on PGXN would be: is uploaded in some other archive format than zip). 4. PGXN Manager copies the `META.json` file, constructs the payload for - sighing, signs with its public key, then adds the `release` object to the + signing, signs with its private key, then adds the `release` object to the copied `META.json` file. 5. PGXN Manager publishes the distribution archive and the `META.json` file - to the root repository, along with an additional file that lists all the + to the root registry, along with an additional file that lists all the releases of the distribution. These files would be: * Release list: `dist/pair.json` @@ -208,16 +209,17 @@ The steps for a client to find, download, and verify a PGXN release would be: 1. Using a valid PGXN mirror, assemble and fetch the release list for the the extension, `dist/pair.json`. -2. Use the release list to determine the best version to install and - assemble its release `META.json` URI. The format is - `dist/{name}/{version}/META.json`; for the above example, that results - in `dist/pair/0.1.7/META.json`. +2. Use the release list to determine the best version to install and assemble + the URI for its release `META.json`. The format is + `dist/{name}/{version}/META.json`; for the above example, that results in + `dist/pair/0.1.7/META.json`. 3. Fetch the release `META.json` file, read in the `releases/pgxn` object, - and use PGXN's public key to verify that it was signed by PGXN. + and use PGXN's public key to verify that it was signed by PGXN. Abort with + an error if validation fails. 4. Decode the payload and use its `uri` field to download the release zip file. -5. Compare the the `sha512` digest from the payload to a digest generated - from the downloaded zip file. +5. Compare one of the digests from the payload to a digest generated from the + downloaded zip file. 6. If they digests are the same, continue with building and installing. Otherwise abort with an error. @@ -229,19 +231,128 @@ of trust: 1. A root key pair is maintained by PGXN, with the private key kept offline. 2. A release key pair is generated and signed by the private root key, with the private key kept in an online vault accessible only to PGXN Manager. -3. The public keys for both keys are published by PGXN. +3. The public keys for both keys are published by PGXN. Clients should embed + the root public key in their sources. 4. PGXN Manager uses the private release key to sign releases as described above. The most important property in the signed payload is the list of digests. 5. Clients can verify the signature with the public release and root keys. 6. With the data validated, the client can download and verify the release - file against a signed digest. In this manner, the authenticity of the - release file can be verified all the way to the root key. + file against a signed digest (preference order: `sha512`, `sha256`, + `sha1`). In this manner, the authenticity of the release file can be + verified all the way to the root key. To support this infrastructure, PGXN Manager **MUST** be updated to properly generate and sign the payload and include it in the release `META.json` files. Clients **MUST** follow the [JWS validation steps]. +### Release Object Properties + +```json +#{ + "pgxn": { + "payload": "eyJ1c2VyIjoidGhlb3J5IiwiZGF0ZSI6IjIwMjQtMDktMTNUMTc6MzI6NTVaIiwidXJpIjoiZGlzdC9wYWlyLzAuMS43L3BhaXItMC4xLjcuemlwIiwiZGlnZXN0cyI6eyJzaGE1MTIiOiJiMzUzYjVhODJiM2I1NGU5NWY0YTI4NTllN2EyYmQwNjQ4YWJjYjM1YTdjMzYxMmIxMjZjMmM3NTQzOGZjMmY4ZThlZTFmMTllNjFmMzBmYTU0ZDdiYjY0YmNmMjE3ZWQxMjY0NzIyYjQ5N2JjYjYxM2Y4MmQ3ODc1MTUxNWI2NyJ9fQ", + "signatures": [ + { + "protected":"eyJhbGciOiJSUzI1NiJ9", + "header": {"kid": "2024-12-29" }, + "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-rLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZES c6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" + } + ] + } +#} +``` + +The `release` property is a JSON object that supports a single key, `pgxn`. No +other keys are allowed except [v2] custom keys, which must start with `x_` or +`X_`. + +The value for each property **MUST** be a [JWS JSON Serialization] JSON +object, defined as follows: + +> * **payload**: The "payload" member MUST be present and contain the value +> `BASE64URL(JWS Payload)`. +> +> * **signatures**: The "signatures" member value MUST be an array of JSON +> objects. Each object represents a signature or MAC over the JWS Payload +> and the JWS Protected Header. Its fields are: +> +> The following members are defined for use in the JSON objects that are +> elements of the "signatures" array: +> +> * **protected**: The "protected" member **MUST** be present and contain +> the value `BASE64URL(UTF8(JWS Protected Header))` when the JWS Protected +> Header value is non-empty; otherwise, it MUST be absent. These Header +> Parameter values are integrity protected. +> +> * **header**: The "header" member **MUST** be present and contain the +> value JWS Unprotected Header when the JWS Unprotected Header value is +> non- empty; otherwise, it MUST be absent. This value is represented as +> an unencoded JSON object, rather than as a string. These Header +> Parameter values are not integrity protected. +> +> * **signature**: The "signature" member **MUST** be present and contain +> the value `BASE64URL(JWS Signature)`. +> +> At least one of the "protected" and "header" members **MUST** be present for +> each signature/MAC computation so that an "alg" Header Parameter value is +> conveyed. +> +> Additional members can be present in both the JSON objects defined above; if +> not understood by implementations encountering them, they MUST be ignored. + +#### PGXN Payload + +For the `pgxn` JWS `payload` property, the value **MUST** be an object with +the following structure: + +``` json +{ + "user": "theory", + "date": "2024-09-13T17:32:55Z", + "uri": "dist/pair/0.1.7/pair-0.1.7.zip", + "digests": { + "sha512": "b353b5a82b3b54e95f4a2859e7a2bd0648abcb35a7c3612b126c2c75438fc2f8e8ee1f19e61f30fa54d7bb64bcf217ed1264722b497bcb613f82d78751515b67" + } +} +``` + +``` json +{ + "user": "the_grinch", + "date": "2012-04-25T02:48:38Z", + "uri": "dist/widget/0.0.1/widget-0.0.1.zip", + "digests": { + "sha1": "b7ecaa270e912a60e3dd919918004c6fcd4989c9" + } +} +``` + +The format for the `payload` property in the `pgxn` JWS object is itself an +object. When formatting into the payload, its keys must appear in order (by +Unicode [code points] and) must contain no formatting-only blank space +("pretty printing"), but just a single line of JSON. In other words, before +Base64 URL-encoding the second example above, it must be formatted as: + +```json +{"date":"2012-04-25T02:48:38Z","digests":{"sha1":"b7ecaa270e912a60e3dd919918004c6fcd4989c9"},"uri":"dist/widget/0.0.1/widget-0.0.1.zip","user":"the_grinch"} +``` + +The object **MUST** contain all of these properties: + +* **user**: The username of the PGXN user who uploaded the release. +* **date**: The date of the release in [RFC 3339]/[ISO 8601] format in the + UTC time zone (indicated by the `Z`). +* **uri**: The URI for the release archive, relative to a PGXN mirror root, + in the format `dist/{name}/{version}/{name}-{version}.zip`. +* **digests**: An object containing hash digests for the file represented by + the `uri` property. It **MUST** contain *at least* one of the following + keys: + * **sha512**: A SHA-512 hash digest in hex format. Preferred. + * **sha256**: A SHA-256 hash digest in hex format. + * **sha1**: A SHA-1 hash digest in hex format. Deprecated; provided to + support migration of release metadata from [v1 PGXN Metadata][v1]. + ## Drawbacks * This pattern could make it more difficult for clients to install code from @@ -256,9 +367,8 @@ Clients **MUST** follow the [JWS validation steps]. PGXN `META.json` format to enable best-in-class digital signatures and validation. * Without key signing, questions will gradually mount as to the - trustworthiness of releases published on PGXN. In the era of [what's teh - word I want here?] compromises, it's essential for PGXN to enable - compromise detection. + trustworthiness of releases published on PGXN. In the era of [supply chain + attacks], it's essential for PGXN to enable compromise detection. ## Prior art @@ -273,10 +383,6 @@ standard, and the likelihood that clients can take advantage of well-tested, mature libraries to handle signing and validation. And finally, its design allows for key rotation when necessary. -## Unresolved questions - -None currently. - ## Future possibilities By embedding the PGXN [JWS] data under its own, key, `pgxn`, the design allows @@ -284,22 +390,75 @@ for other parties to add their own release metadata and signatures. For example, an organization that provides security scanning services may want to add their own signature to validate that they have tested a specific release. +In the future we may also want to issue key pairs to registered developers and +require that they sign releases, as well. This would allow an extra level of +protection, plus allow key revocation in case an upload has been tampered +with. + For the PGXN signing itself the proposed use of a separate, offline "root" key to sign an intermediate "release" key would allow for easier key rotation in the event the "release" private key was compromised. +## Unresolved questions + +* Is `release` the best name for this new property? Yes the PGXN object + represents signed release metadata, and eventually authors may also use + the pattern to sign releases. But others may sign for different purposes, + such as the security scanning example under [Future + Possibilities](#future-possibilities). Should it perhaps be named + `signatures` or similar, instead? + +* If we retain the format of keys in the `release` object pointing to + signatures, should we relax the requirement that additional keys start + with `x_` or `X_`? In the future if we allowed, say, author signatures, + then we might add the key `author` or `release_user` or some such. Would + we allow any other signatures to appear in the file on PGXN? + +* Should we eliminate the `digests` object in the `payload` and allow + `SHA-512` only? I had made an object with `sha1` to simplify migration + from the PGXN v1 spec, which includes only a `sha1` has, but maybe it'd be + better to simplify the structure here and requires new SHA-512s from the + migration. + + On the other hand, supporting only SHA-512 now means a bit less flexibility + when it's time to add a new algorithm later. If we wanted to support, say, + `SHA3-256` at a future date, would we add another field to the `payload` + object and just let the client find the right one? Or is it better to keep + a sub-object with multiples to simplify backward compatibility for clients + that may not yet support a new algorithm? + +* For PGXN signing, how will the private and public keys be managed? Where + will private keys be stored and secured, and where will public keys be + published? Should they live in a separate domain, or some sort of key + store, so clients can fetch them less fear of compromise? The fear is that + someone may breach the root registry, modify extensions, and then sign + them with their own keys, which replace our own keys. + + I *think* that, in general, if we can keep the root private key offline + and use intermediate keys, we can recommend that clients include the root + public key in their sources, so that any intermediate key breach can be + detected. + + [PGXN]: https://pgxn.org "PostgreSQL Extension Network" [PGXN Manager]: https://manager.pgxn.org - [PGXN Meta Spec v1]: 0001-meta-spec-v1.md + [v1]: 0001-meta-spec-v1.md "PGXN Meta Spec v1" [SHA-1]: https://en.wikipedia.org/wiki/SHA-1 "Wikipedia: SHA-1" [release META.json]: https://master.pgxn.org/dist/pair/0.1.7/META.json [distribution META.json]: https://api.pgxn.org/src/pair/pair-0.1.7/META.json [public key signing]: https://en.wikipedia.org/wiki/Digital_signature "Wikipedia: Digital signature" - [PGXN Meta Spec v2]: 0003-meta-spec-v2.md + [v2]: 0003-meta-spec-v2.md "PGXN Meta Spec v2" [JWS]: https://www.rfc-editor.org/rfc/rfc7515.html "JSON Web Signature (JWS)" [JWS JSON Serialization]: https://www.rfc-editor.org/rfc/rfc7515.html#section-7.2 "RFC 7515: JWS JSON Serialization" [IETF RFC 2119]: https://www.ietf.org/rfc/rfc2119.txt [JWS validation steps]: https://www.rfc-editor.org/rfc/rfc7515.html#section-5.2 "RFC 7515: Message Signature or MAC Validation" + [code points]: https://en.wikipedia.org/wiki/Code_point "Wikipedia: Code point" + [RFC 3339]: https://www.rfc-editor.org/rfc/rfc3339.html + "RFC 3339: Date and Time on the Internet: Timestamps" + [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601 "Wikipedia: ISO 8601" + [supply chain attacks]: https://en.wikipedia.org/wiki/Supply_chain_attack + "Wikipedia: Supply chain attack" + [Python wheel]: https://packaging.python.org/en/latest/specifications/binary-distribution-format/ From 8c8bab29b3f6f5893e09fc98216aa8a637caa047 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 23 Sep 2024 16:48:36 +0100 Subject: [PATCH 03/11] Reinforce use of JWS JSON Serialization again --- text/0000-release-meta-spec-v2.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/text/0000-release-meta-spec-v2.md b/text/0000-release-meta-spec-v2.md index f259b6b..daa7d41 100644 --- a/text/0000-release-meta-spec-v2.md +++ b/text/0000-release-meta-spec-v2.md @@ -84,9 +84,11 @@ would append a structure such as this to the distribution `META.json` file: } ``` -This example includes a PGXN release signature. The data signed is the contents -of the `payload` property, which is the Base64 URL-encoded representation of -this JSON object (with blank space formatting removed): +This example includes a PGXN release signature, which is formatted in +accordance with the [JWS JSON Serialization] format specified by [RFC +7515][JWS]. The signed data is the contents of the `payload` property, which +is the Base64 URL-encoded representation of this JSON object (with blank space +formatting removed): ``` json { @@ -214,8 +216,8 @@ The steps for a client to find, download, and verify a PGXN release would be: `dist/{name}/{version}/META.json`; for the above example, that results in `dist/pair/0.1.7/META.json`. 3. Fetch the release `META.json` file, read in the `releases/pgxn` object, - and use PGXN's public key to verify that it was signed by PGXN. Abort with - an error if validation fails. + and use PGXN's current public keys to verify that it was signed by PGXN. + Abort with an error if validation fails. 4. Decode the payload and use its `uri` field to download the release zip file. 5. Compare one of the digests from the payload to a digest generated from the @@ -232,7 +234,8 @@ of trust: 2. A release key pair is generated and signed by the private root key, with the private key kept in an online vault accessible only to PGXN Manager. 3. The public keys for both keys are published by PGXN. Clients should embed - the root public key in their sources. + the root public key in their sources but regularly fetch and validate + signing keys against it. 4. PGXN Manager uses the private release key to sign releases as described above. The most important property in the signed payload is the list of digests. @@ -380,8 +383,9 @@ the release file it describes. The use of [JWS] ensures a widely-vetted key signing and distribution standard, and the likelihood that clients can take advantage of well-tested, -mature libraries to handle signing and validation. And finally, its design -allows for key rotation when necessary. +mature libraries to handle signing and validation. And finally, the use of RFC +7515-standard [JWS JSON Serialization] allows multiple signatures, which will +simplify key rotation. ## Future possibilities @@ -439,7 +443,6 @@ the event the "release" private key was compromised. public key in their sources, so that any intermediate key breach can be detected. - [PGXN]: https://pgxn.org "PostgreSQL Extension Network" [PGXN Manager]: https://manager.pgxn.org [v1]: 0001-meta-spec-v1.md "PGXN Meta Spec v1" From 01140f3a343796960b6b5ed77e6b6f23ed652f8c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 23 Sep 2024 18:20:59 +0100 Subject: [PATCH 04/11] Reference JWK for key patterns --- text/0000-release-meta-spec-v2.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/text/0000-release-meta-spec-v2.md b/text/0000-release-meta-spec-v2.md index daa7d41..4f0a2db 100644 --- a/text/0000-release-meta-spec-v2.md +++ b/text/0000-release-meta-spec-v2.md @@ -104,8 +104,8 @@ formatting removed): A client **SHOULD** verify that the payload was signed by PGXN, and then use the URI to download the release file and verify it with the SHA-512 digest. This pattern ensures that, when validation is properly implemented, it is -rooted by PGXN's private key, and therefore the distribution file can be fully -trusted as unmodified since PGXN signed it. +rooted by PGXN's certificate chain, and therefore the distribution file can be +fully trusted as unmodified since PGXN signed it. ## Guide-level explanation @@ -216,8 +216,9 @@ The steps for a client to find, download, and verify a PGXN release would be: `dist/{name}/{version}/META.json`; for the above example, that results in `dist/pair/0.1.7/META.json`. 3. Fetch the release `META.json` file, read in the `releases/pgxn` object, - and use PGXN's current public keys to verify that it was signed by PGXN. - Abort with an error if validation fails. + and use PGXN's current public key (downloaded as a [RFC 7517 JWK Set]) to + verify that it was signed by PGXN. Abort with an error if validation + fails. 4. Decode the payload and use its `uri` field to download the release zip file. 5. Compare one of the digests from the payload to a digest generated from the @@ -233,17 +234,17 @@ of trust: 1. A root key pair is maintained by PGXN, with the private key kept offline. 2. A release key pair is generated and signed by the private root key, with the private key kept in an online vault accessible only to PGXN Manager. -3. The public keys for both keys are published by PGXN. Clients should embed - the root public key in their sources but regularly fetch and validate - signing keys against it. +3. The public keys for both keys are published by PGXN in a [RFC 7517 JWK + Set]. 4. PGXN Manager uses the private release key to sign releases as described above. The most important property in the signed payload is the list of digests. -5. Clients can verify the signature with the public release and root keys. +5. Clients **MUST** regularly fetch the [RFC 7517 JWK Set] of public keys, + validate their authenticity, and use them to verify the signature. 6. With the data validated, the client can download and verify the release file against a signed digest (preference order: `sha512`, `sha256`, `sha1`). In this manner, the authenticity of the release file can be - verified all the way to the root key. + verified by PGXN's chain of trust to its root certificate. To support this infrastructure, PGXN Manager **MUST** be updated to properly generate and sign the payload and include it in the release `META.json` files. @@ -465,3 +466,5 @@ the event the "release" private key was compromised. [supply chain attacks]: https://en.wikipedia.org/wiki/Supply_chain_attack "Wikipedia: Supply chain attack" [Python wheel]: https://packaging.python.org/en/latest/specifications/binary-distribution-format/ + [RFC 7517 JWK Set]: https://datatracker.ietf.org/doc/html/rfc7517#section-5 + "RFC 7517 JSON Web Key (JWK): JWK Set Format" From 4ad0236d9281b57d21079ff86ee830a364c596f7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 23 Sep 2024 19:52:55 +0100 Subject: [PATCH 05/11] Rename release to receipts --- text/0000-release-meta-spec-v2.md | 144 ++++++++++++++++-------------- 1 file changed, 75 insertions(+), 69 deletions(-) diff --git a/text/0000-release-meta-spec-v2.md b/text/0000-release-meta-spec-v2.md index 4f0a2db..6eea623 100644 --- a/text/0000-release-meta-spec-v2.md +++ b/text/0000-release-meta-spec-v2.md @@ -58,30 +58,36 @@ Meta Spec v2][v2] provides an opportunity to update the release `META.json` format with signed metadata to enable a much more secure method of validation. This RFC therefore proposes to extend [v2] distribution metadata with a single -additional property, `release`, that contains a [JWS JSON Serialization] -object as defined by [RFC 7515][JWS]. This property will allow clients not -only to find the release file to download and verify against checksums, but -also validate it against a public key provided by PGXN. +additional property, `receipts`, that contains one or more cryptographic +signatures or certifications that attest to the authenticity or other +characteristics of a release on PGXN. -The design allows multiple digital signatures, which in the future may allow -authors or other entities to sign releases with their own keys. The new format -would append a structure such as this to the distribution `META.json` file: +The `receipts` value is an object that contains at least one property, `pgxn`, +which itself contains a PGXN-generated [RFC 7515][JWS] JSON Web Signature in +the [JWS JSON Serialization] format. The `pgxn` property will allow clients +not only to find the release file to download and verify against checksums, +but also validate it against a public key provided by PGXN. + +The design allows multiple signatures, certificates, or other attestations, +which in the future **MAY** allow authors or other entities to sign releases +with their own keys. The new format appends a structure such as this to the +distribution `META.json` file: ``` json -{ - "release": { +#{ + "receipts": { "pgxn": { "payload": "eyJ1c2VyIjoidGhlb3J5IiwiZGF0ZSI6IjIwMjQtMDktMTNUMTc6MzI6NTVaIiwidXJpIjoiZGlzdC9wYWlyLzAuMS43L3BhaXItMC4xLjcuemlwIiwiZGlnZXN0cyI6eyJzaGE1MTIiOiJiMzUzYjVhODJiM2I1NGU5NWY0YTI4NTllN2EyYmQwNjQ4YWJjYjM1YTdjMzYxMmIxMjZjMmM3NTQzOGZjMmY4ZThlZTFmMTllNjFmMzBmYTU0ZDdiYjY0YmNmMjE3ZWQxMjY0NzIyYjQ5N2JjYjYxM2Y4MmQ3ODc1MTUxNWI2NyJ9fQ", "signatures": [ { - "protected":"eyJhbGciOiJSUzI1NiJ9", - "header": {"kid": "2024-12-29" }, + "protected": "eyJhbGciOiJSUzI1NiJ9", + "header": { "kid": "2024-12-29" }, "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-rLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZES c6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" } ] } } -} +#} ``` This example includes a PGXN release signature, which is formatted in @@ -180,8 +186,8 @@ The steps to publish a signed release on PGXN would be: 1. User updates the version in the `META.json` as appropriate for the release, then bundles the `META.json` file and all required and - recommended source code and documentation files into a zip file. From a - Git repository, for example: + recommended source code and documentation files into a zip archive file. + From a Git repository, for example: ```sh git archive --format zip --prefix=pair-0.1.7/ -o pair-0.1.7.zip HEAD @@ -193,9 +199,9 @@ The steps to publish a signed release on PGXN would be: the uploaded file (if it doesn't have the directory prefix, for example, or is uploaded in some other archive format than zip). -4. PGXN Manager copies the `META.json` file, constructs the payload for - signing, signs with its private key, then adds the `release` object to the - copied `META.json` file. +4. PGXN Manager copies the `META.json` file out of the archive file, + constructs the payload for signing, signs with its private key, then adds + the `release` object to the copied `META.json` file. 5. PGXN Manager publishes the distribution archive and the `META.json` file to the root registry, along with an additional file that lists all the @@ -203,7 +209,7 @@ The steps to publish a signed release on PGXN would be: * Release list: `dist/pair.json` * 0.1.7 release `META.json`: `dist/pair/0.1.7/META.json` - * 0.1.7 release zip file: `dist/pair/0.1.7/pair-0.1.7.zip` + * 0.1.7 release archive file: `dist/pair/0.1.7/pair-0.1.7.zip` #### Installing a Release #### @@ -215,7 +221,7 @@ The steps for a client to find, download, and verify a PGXN release would be: the URI for its release `META.json`. The format is `dist/{name}/{version}/META.json`; for the above example, that results in `dist/pair/0.1.7/META.json`. -3. Fetch the release `META.json` file, read in the `releases/pgxn` object, +3. Fetch the release `META.json` file, read in the `receipts/pgxn` object, and use PGXN's current public key (downloaded as a [RFC 7517 JWK Set]) to verify that it was signed by PGXN. Abort with an error if validation fails. @@ -223,7 +229,7 @@ The steps for a client to find, download, and verify a PGXN release would be: file. 5. Compare one of the digests from the payload to a digest generated from the downloaded zip file. -6. If they digests are the same, continue with building and installing. +6. If the digests are the same, continue with building and installing. Otherwise abort with an error. ## Reference-level explanation @@ -234,13 +240,14 @@ of trust: 1. A root key pair is maintained by PGXN, with the private key kept offline. 2. A release key pair is generated and signed by the private root key, with the private key kept in an online vault accessible only to PGXN Manager. -3. The public keys for both keys are published by PGXN in a [RFC 7517 JWK +3. The public keys for both keys are published by PGXN as a [RFC 7517 JWK Set]. 4. PGXN Manager uses the private release key to sign releases as described above. The most important property in the signed payload is the list of digests. 5. Clients **MUST** regularly fetch the [RFC 7517 JWK Set] of public keys, - validate their authenticity, and use them to verify the signature. + validate their authenticity, and use them to verify `receipts/pgxn` + signatures. 6. With the data validated, the client can download and verify the release file against a signed digest (preference order: `sha512`, `sha256`, `sha1`). In this manner, the authenticity of the release file can be @@ -250,7 +257,7 @@ To support this infrastructure, PGXN Manager **MUST** be updated to properly generate and sign the payload and include it in the release `META.json` files. Clients **MUST** follow the [JWS validation steps]. -### Release Object Properties +### Receipts Object Properties ```json #{ @@ -267,32 +274,32 @@ Clients **MUST** follow the [JWS validation steps]. #} ``` -The `release` property is a JSON object that supports a single key, `pgxn`. No -other keys are allowed except [v2] custom keys, which must start with `x_` or -`X_`. +The `receipts` property is a JSON object that supports a single key, `pgxn`. +No other keys are allowed except [v2] custom keys, which must start with `x_` +or `X_`. -The value for each property **MUST** be a [JWS JSON Serialization] JSON -object, defined as follows: +The value for the `pgxn` property **MUST** be formatted according to the [JWS +JSON Serialization], which specifies: -> * **payload**: The "payload" member MUST be present and contain the value -> `BASE64URL(JWS Payload)`. +> * **payload**: The "payload" member **MUST** be present and contain the +> value `BASE64URL(JWS Payload)`. > -> * **signatures**: The "signatures" member value MUST be an array of JSON -> objects. Each object represents a signature or MAC over the JWS Payload -> and the JWS Protected Header. Its fields are: +> * **signatures**: The "signatures" member value **MUST** be an array of +> JSON objects. Each object represents a signature or MAC over the JWS +> Payload and the JWS Protected Header. > > The following members are defined for use in the JSON objects that are > elements of the "signatures" array: > > * **protected**: The "protected" member **MUST** be present and contain > the value `BASE64URL(UTF8(JWS Protected Header))` when the JWS Protected -> Header value is non-empty; otherwise, it MUST be absent. These Header -> Parameter values are integrity protected. +> Header value is non-empty; otherwise, it **MUST** be absent. These +> Header Parameter values are integrity protected. > > * **header**: The "header" member **MUST** be present and contain the > value JWS Unprotected Header when the JWS Unprotected Header value is -> non- empty; otherwise, it MUST be absent. This value is represented as -> an unencoded JSON object, rather than as a string. These Header +> non- empty; otherwise, it **MUST** be absent. This value is represented +> as an unencoded JSON object, rather than as a string. These Header > Parameter values are not integrity protected. > > * **signature**: The "signature" member **MUST** be present and contain @@ -332,26 +339,26 @@ the following structure: } ``` -The format for the `payload` property in the `pgxn` JWS object is itself an -object. When formatting into the payload, its keys must appear in order (by -Unicode [code points] and) must contain no formatting-only blank space -("pretty printing"), but just a single line of JSON. In other words, before -Base64 URL-encoding the second example above, it must be formatted as: +The format for the `payload` property is itself an object. When formatting +into the `payload` value, its keys **MUST** appear in Unicode [code point] +order and **MUST** contain no formatting-only blank space ("pretty printing"), +but just a single line of JSON. In other words, before Base64 URL-encoding the +second example above, it must be formatted as: ```json {"date":"2012-04-25T02:48:38Z","digests":{"sha1":"b7ecaa270e912a60e3dd919918004c6fcd4989c9"},"uri":"dist/widget/0.0.1/widget-0.0.1.zip","user":"the_grinch"} ``` -The object **MUST** contain all of these properties: +The `pgxn` object **MUST** contain all of these properties: * **user**: The username of the PGXN user who uploaded the release. * **date**: The date of the release in [RFC 3339]/[ISO 8601] format in the UTC time zone (indicated by the `Z`). * **uri**: The URI for the release archive, relative to a PGXN mirror root, in the format `dist/{name}/{version}/{name}-{version}.zip`. -* **digests**: An object containing hash digests for the file represented by - the `uri` property. It **MUST** contain *at least* one of the following - keys: +* **digests**: An object containing cryptographic hash digests for the file + represented by the `uri` property. It **MUST** contain *at least* one of + the following keys: * **sha512**: A SHA-512 hash digest in hex format. Preferred. * **sha256**: A SHA-256 hash digest in hex format. * **sha1**: A SHA-1 hash digest in hex format. Deprecated; provided to @@ -396,8 +403,8 @@ example, an organization that provides security scanning services may want to add their own signature to validate that they have tested a specific release. In the future we may also want to issue key pairs to registered developers and -require that they sign releases, as well. This would allow an extra level of -protection, plus allow key revocation in case an upload has been tampered +require that they sign releases. This would allow an extra level of +protection, as well as key revocation in case an upload has been tampered with. For the PGXN signing itself the proposed use of a separate, offline "root" key @@ -406,18 +413,19 @@ the event the "release" private key was compromised. ## Unresolved questions -* Is `release` the best name for this new property? Yes the PGXN object - represents signed release metadata, and eventually authors may also use - the pattern to sign releases. But others may sign for different purposes, - such as the security scanning example under [Future - Possibilities](#future-possibilities). Should it perhaps be named - `signatures` or similar, instead? +* Is `receipts` the best name for this new property? Other possibilities: + * `signatures` (might there be other kinds of certifications?) + * `certifications` (long, close to "certificates", which is overloaded) + * `attestations` (abstract and too JWT-y) + * `jws` (dissuades other formats) -* If we retain the format of keys in the `release` object pointing to +* If we retain the format of keys in the `receipts` object pointing to signatures, should we relax the requirement that additional keys start - with `x_` or `X_`? In the future if we allowed, say, author signatures, - then we might add the key `author` or `release_user` or some such. Would - we allow any other signatures to appear in the file on PGXN? + with `x_` or `X_`? In the future if we allowed, say, author signatures, + then we might add the key `author` or `user` or some such. Would we allow + any other signatures to appear in the file on PGXN? Perhaps allow `x_` and + `X_` properties of any kind, but require any other properties to be [JWS + JSON Serialization] objects? * Should we eliminate the `digests` object in the `payload` and allow `SHA-512` only? I had made an object with `sha1` to simplify migration @@ -433,16 +441,11 @@ the event the "release" private key was compromised. that may not yet support a new algorithm? * For PGXN signing, how will the private and public keys be managed? Where - will private keys be stored and secured, and where will public keys be - published? Should they live in a separate domain, or some sort of key - store, so clients can fetch them less fear of compromise? The fear is that - someone may breach the root registry, modify extensions, and then sign - them with their own keys, which replace our own keys. - - I *think* that, in general, if we can keep the root private key offline - and use intermediate keys, we can recommend that clients include the root - public key in their sources, so that any intermediate key breach can be - detected. + will private keys be stored and secured, and where will public [JWK]s be + published? Should the public live in a separate domain, or some sort of + key store, so clients can fetch them with a higher degree of trust? The + fear is that someone may compromise the root registry, modify extensions, + and then sign them with their own keys, which replace our own keys. [PGXN]: https://pgxn.org "PostgreSQL Extension Network" [PGXN Manager]: https://manager.pgxn.org @@ -459,7 +462,7 @@ the event the "release" private key was compromised. [IETF RFC 2119]: https://www.ietf.org/rfc/rfc2119.txt [JWS validation steps]: https://www.rfc-editor.org/rfc/rfc7515.html#section-5.2 "RFC 7515: Message Signature or MAC Validation" - [code points]: https://en.wikipedia.org/wiki/Code_point "Wikipedia: Code point" + [code point]: https://en.wikipedia.org/wiki/Code_point "Wikipedia: Code point" [RFC 3339]: https://www.rfc-editor.org/rfc/rfc3339.html "RFC 3339: Date and Time on the Internet: Timestamps" [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601 "Wikipedia: ISO 8601" @@ -468,3 +471,6 @@ the event the "release" private key was compromised. [Python wheel]: https://packaging.python.org/en/latest/specifications/binary-distribution-format/ [RFC 7517 JWK Set]: https://datatracker.ietf.org/doc/html/rfc7517#section-5 "RFC 7517 JSON Web Key (JWK): JWK Set Format" + [JWK]: https://datatracker.ietf.org/doc/html/rfc7517 + "RFC 7517 JSON Web Key (JWK)" + \ No newline at end of file From 3f04db6ef0985712e70115efee7884ee4373eec2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 23 Sep 2024 22:29:48 +0100 Subject: [PATCH 06/11] Update list of alternative terms --- text/0000-release-meta-spec-v2.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/0000-release-meta-spec-v2.md b/text/0000-release-meta-spec-v2.md index 6eea623..0705379 100644 --- a/text/0000-release-meta-spec-v2.md +++ b/text/0000-release-meta-spec-v2.md @@ -416,8 +416,11 @@ the event the "release" private key was compromised. * Is `receipts` the best name for this new property? Other possibilities: * `signatures` (might there be other kinds of certifications?) * `certifications` (long, close to "certificates", which is overloaded) + * `certs` (accurate, but overloaded to mean "certificates") * `attestations` (abstract and too JWT-y) * `jws` (dissuades other formats) + * `coupons`, `authentication`, `authenticity`, `credentials`, + `vouchers`, `records` (meh) * If we retain the format of keys in the `receipts` object pointing to signatures, should we relax the requirement that additional keys start From cac6b000c1dfdef6386d4650f434427638ba6b82 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 8 Oct 2024 20:24:10 +0200 Subject: [PATCH 07/11] Rename `receits` to `certs` Still not sure about the name, but I think it's better. --- text/0000-release-meta-spec-v2.md | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/text/0000-release-meta-spec-v2.md b/text/0000-release-meta-spec-v2.md index 0705379..f0ff7b6 100644 --- a/text/0000-release-meta-spec-v2.md +++ b/text/0000-release-meta-spec-v2.md @@ -58,31 +58,30 @@ Meta Spec v2][v2] provides an opportunity to update the release `META.json` format with signed metadata to enable a much more secure method of validation. This RFC therefore proposes to extend [v2] distribution metadata with a single -additional property, `receipts`, that contains one or more cryptographic -signatures or certifications that attest to the authenticity or other -characteristics of a release on PGXN. +additional property, `certs`, that contains one or more certifications that +attest to the authenticity or other characteristics of a release on PGXN. -The `receipts` value is an object that contains at least one property, `pgxn`, +The `certs` value is an object that contains at least one property, `pgxn`, which itself contains a PGXN-generated [RFC 7515][JWS] JSON Web Signature in the [JWS JSON Serialization] format. The `pgxn` property will allow clients not only to find the release file to download and verify against checksums, but also validate it against a public key provided by PGXN. -The design allows multiple signatures, certificates, or other attestations, +The design allows multiple signatures, certifications, or other attestations, which in the future **MAY** allow authors or other entities to sign releases with their own keys. The new format appends a structure such as this to the distribution `META.json` file: ``` json #{ - "receipts": { + "certs": { "pgxn": { "payload": "eyJ1c2VyIjoidGhlb3J5IiwiZGF0ZSI6IjIwMjQtMDktMTNUMTc6MzI6NTVaIiwidXJpIjoiZGlzdC9wYWlyLzAuMS43L3BhaXItMC4xLjcuemlwIiwiZGlnZXN0cyI6eyJzaGE1MTIiOiJiMzUzYjVhODJiM2I1NGU5NWY0YTI4NTllN2EyYmQwNjQ4YWJjYjM1YTdjMzYxMmIxMjZjMmM3NTQzOGZjMmY4ZThlZTFmMTllNjFmMzBmYTU0ZDdiYjY0YmNmMjE3ZWQxMjY0NzIyYjQ5N2JjYjYxM2Y4MmQ3ODc1MTUxNWI2NyJ9fQ", "signatures": [ { "protected": "eyJhbGciOiJSUzI1NiJ9", "header": { "kid": "2024-12-29" }, - "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-rLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZES c6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" + "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-rLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" } ] } @@ -221,8 +220,8 @@ The steps for a client to find, download, and verify a PGXN release would be: the URI for its release `META.json`. The format is `dist/{name}/{version}/META.json`; for the above example, that results in `dist/pair/0.1.7/META.json`. -3. Fetch the release `META.json` file, read in the `receipts/pgxn` object, - and use PGXN's current public key (downloaded as a [RFC 7517 JWK Set]) to +3. Fetch the release `META.json` file, read in the `certs/pgxn` object, and + use PGXN's current public key (downloaded as a [RFC 7517 JWK Set]) to verify that it was signed by PGXN. Abort with an error if validation fails. 4. Decode the payload and use its `uri` field to download the release zip @@ -246,7 +245,7 @@ of trust: above. The most important property in the signed payload is the list of digests. 5. Clients **MUST** regularly fetch the [RFC 7517 JWK Set] of public keys, - validate their authenticity, and use them to verify `receipts/pgxn` + validate their authenticity, and use them to verify `certs/pgxn` signatures. 6. With the data validated, the client can download and verify the release file against a signed digest (preference order: `sha512`, `sha256`, @@ -257,7 +256,7 @@ To support this infrastructure, PGXN Manager **MUST** be updated to properly generate and sign the payload and include it in the release `META.json` files. Clients **MUST** follow the [JWS validation steps]. -### Receipts Object Properties +### Certs Object Properties ```json #{ @@ -267,16 +266,16 @@ Clients **MUST** follow the [JWS validation steps]. { "protected":"eyJhbGciOiJSUzI1NiJ9", "header": {"kid": "2024-12-29" }, - "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-rLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZES c6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" + "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-rLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" } ] } #} ``` -The `receipts` property is a JSON object that supports a single key, `pgxn`. -No other keys are allowed except [v2] custom keys, which must start with `x_` -or `X_`. +The `certs` property is a JSON object that supports a single key, `pgxn`. No +other keys are allowed except [v2] custom keys, which must start with `x_` or +`X_`. The value for the `pgxn` property **MUST** be formatted according to the [JWS JSON Serialization], which specifies: @@ -413,16 +412,17 @@ the event the "release" private key was compromised. ## Unresolved questions -* Is `receipts` the best name for this new property? Other possibilities: +* Is `certs` the best name for this new property? People many assume it + means "certificates". Other possibilities: * `signatures` (might there be other kinds of certifications?) - * `certifications` (long, close to "certificates", which is overloaded) - * `certs` (accurate, but overloaded to mean "certificates") + * `certifications` (long, still close to "certificates", which is overloaded) + * `receipts` (cute, but a bit opaque) * `attestations` (abstract and too JWT-y) * `jws` (dissuades other formats) * `coupons`, `authentication`, `authenticity`, `credentials`, `vouchers`, `records` (meh) -* If we retain the format of keys in the `receipts` object pointing to +* If we retain the format of keys in the `certs` object pointing to signatures, should we relax the requirement that additional keys start with `x_` or `X_`? In the future if we allowed, say, author signatures, then we might add the key `author` or `user` or some such. Would we allow From 855258ee95f34354396fc65ebac6158834c8aef9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 8 Oct 2024 20:27:09 +0200 Subject: [PATCH 08/11] Set RFC number, link to implementation --- ...lease-meta-spec-v2.md => 0005-release-meta-spec-v2.md} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename text/{0000-release-meta-spec-v2.md => 0005-release-meta-spec-v2.md} (98%) diff --git a/text/0000-release-meta-spec-v2.md b/text/0005-release-meta-spec-v2.md similarity index 98% rename from text/0000-release-meta-spec-v2.md rename to text/0005-release-meta-spec-v2.md index f0ff7b6..aeb87db 100644 --- a/text/0000-release-meta-spec-v2.md +++ b/text/0005-release-meta-spec-v2.md @@ -1,14 +1,14 @@ {{#title PGXN RFC–0 — PGXN Release Meta Spec v2}} -* **RFC:** 0 (fill in with pull request number) +* **RFC:** 5 * **Title:** PGXN Release Meta Spec v2 * **Slug:** `release-meta-spec-v2` * **Start Date:** 2024-09-18 * **Status:** Proposed Standard * **Category:** Packaging -* **Pull Request:** [pgxn/rfcs#4](https://github.com/pgxn/rfcs/pull/4) -* **Implementation Issue:** TBD +* **Pull Request:** [pgxn/rfcs#5](https://github.com/pgxn/rfcs/pull/5) +* **Implementation Issue:** [pgxn/planning](https://github.com/pgxn/planning/issues/68) -# RFC--0 --- PGXN Release Meta Spec v2 +# RFC--5 --- PGXN Release Meta Spec v2 ## Abstract From 2d9af4f995545f1c33863c40bcb7596e5a6ba8f3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 8 Oct 2024 21:40:16 +0200 Subject: [PATCH 09/11] Rename RFC and cover flattened syntax Make another edit pass, tweaking some language and structure, but mostly highlight the support for both the "general" and "flattened" JWS JSON Serialization syntaxes. Hide lines beginning with `#` in JSON code fences. --- Makefile | 2 +- book.toml | 3 + ...ec-v2.md => 0005-release-certification.md} | 202 ++++++++++-------- 3 files changed, 114 insertions(+), 93 deletions(-) rename text/{0005-release-meta-spec-v2.md => 0005-release-certification.md} (71%) diff --git a/Makefile b/Makefile index ddf0667..4338239 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -src/SUMMARY.md: generate-book.py README.md text/*.md +src/SUMMARY.md: generate-book.py README.md $(wildcard text/*.md) @./generate-book.py run: src/SUMMARY.md diff --git a/book.toml b/book.toml index a45cbe4..d7c54b6 100644 --- a/book.toml +++ b/book.toml @@ -16,3 +16,6 @@ heading-split-level = 0 [output.html.playground] runnable = false + +[output.html.code.hidelines] +json = "#" diff --git a/text/0005-release-meta-spec-v2.md b/text/0005-release-certification.md similarity index 71% rename from text/0005-release-meta-spec-v2.md rename to text/0005-release-certification.md index aeb87db..d1828a5 100644 --- a/text/0005-release-meta-spec-v2.md +++ b/text/0005-release-certification.md @@ -1,22 +1,23 @@ -{{#title PGXN RFC–0 — PGXN Release Meta Spec v2}} +{{#title PGXN RFC–5 — Release Certification v2}} * **RFC:** 5 -* **Title:** PGXN Release Meta Spec v2 -* **Slug:** `release-meta-spec-v2` +* **Title:** PGXN Release Certification +* **Slug:** `release-certification` * **Start Date:** 2024-09-18 * **Status:** Proposed Standard * **Category:** Packaging * **Pull Request:** [pgxn/rfcs#5](https://github.com/pgxn/rfcs/pull/5) -* **Implementation Issue:** [pgxn/planning](https://github.com/pgxn/planning/issues/68) +* **Implementation Issue:** [pgxn/planning#68](https://github.com/pgxn/planning/issues/68) -# RFC--5 --- PGXN Release Meta Spec v2 +# RFC--5 --- PGXN Release Certification ## Abstract -This document describes version 2.0.0 of the *release* format for [PGXN] -source distribution metadata. It extends the *distribution* [PGXN Meta Spec -v2][v2] `META.json` provided by authors in PGXN uploads PGXN with signed -metadata about the release on PGXN. This will allow clients to verify the -validity of PGXN release sources via public key verification. +This document describes a format for signed release certification in a the +*release* format for [PGXN] source distribution metadata (`META.json`). It +extends the author-provided *distribution* [PGXN Meta Spec v2][v2] `META.json` +with an object to record one or more certifications, starting with +[JWS]-signed metadata about the release on PGXN. This will allow clients to +validate PGXN releases via public key verification. ## Introduction @@ -24,16 +25,16 @@ validity of PGXN release sources via public key verification. When [PGXN Manager] was implemented in 2010, it published release zip files that contain author-supplied [PGXN Meta Spec v1][v1] metadata, and also -published an augmented `META.json` file with three additional properties -representing the release: +published augmented `META.json` files with three additional properties +representing each release: * `user`: The name of the user who made the release * `date`: A timestamp for the release * `sha1`: A [SHA-1] digest of the release zip file -Clients were advised to download both this release `META.json` along with the -source zip file, and to validate the zip file against the checksum. The `user` -and `date` information were provided mainly for informational purposes. +Clients were advised to download this release `META.json` along with the +source zip file, and to validate the zip file against the digest. The `user` +and `date` fields were provided mainly for informational purposes. Compare, for example, the pair-0.1.7 [release META.json] to the author-provided [distribution META.json]. The difference is these three @@ -47,25 +48,26 @@ fields: } ``` -Using the release metadata, a client can determine the URL to download the zip -file, then validate it against the SHA-1 digest. +Using the release metadata, a client determines the URL to download the zip +file, then validates it against the SHA-1 digest. ### Signed Releases A lot has changed since 2010, including an increasing need for [public key -signing] to validate that distribution files come from trusted sources. [PGXN -Meta Spec v2][v2] provides an opportunity to update the release `META.json` -format with signed metadata to enable a much more secure method of validation. +signing] to validate that distribution files come from trusted sources. The +introduction of [PGXN Meta Spec v2][v2] provides an opportunity to update the +release `META.json` format with signed metadata to enable a much more secure +method of validation. This RFC therefore proposes to extend [v2] distribution metadata with a single -additional property, `certs`, that contains one or more certifications that +additional property, `certs`, that contains one or more *certifications* that attest to the authenticity or other characteristics of a release on PGXN. The `certs` value is an object that contains at least one property, `pgxn`, which itself contains a PGXN-generated [RFC 7515][JWS] JSON Web Signature in the [JWS JSON Serialization] format. The `pgxn` property will allow clients -not only to find the release file to download and verify against checksums, -but also validate it against a public key provided by PGXN. +not only to assemble the release URL and verify the downloaded file against +checksums, but also validate it against a public key provided by PGXN. The design allows multiple signatures, certifications, or other attestations, which in the future **MAY** allow authors or other entities to sign releases @@ -75,34 +77,28 @@ distribution `META.json` file: ``` json #{ "certs": { - "pgxn": { - "payload": "eyJ1c2VyIjoidGhlb3J5IiwiZGF0ZSI6IjIwMjQtMDktMTNUMTc6MzI6NTVaIiwidXJpIjoiZGlzdC9wYWlyLzAuMS43L3BhaXItMC4xLjcuemlwIiwiZGlnZXN0cyI6eyJzaGE1MTIiOiJiMzUzYjVhODJiM2I1NGU5NWY0YTI4NTllN2EyYmQwNjQ4YWJjYjM1YTdjMzYxMmIxMjZjMmM3NTQzOGZjMmY4ZThlZTFmMTllNjFmMzBmYTU0ZDdiYjY0YmNmMjE3ZWQxMjY0NzIyYjQ5N2JjYjYxM2Y4MmQ3ODc1MTUxNWI2NyJ9fQ", - "signatures": [ - { - "protected": "eyJhbGciOiJSUzI1NiJ9", - "header": { "kid": "2024-12-29" }, - "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-rLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" - } - ] - } + "pgxn": { + "payload": "eyJ1c2VyIjoidGhlb3J5IiwiZGF0ZSI6IjIwMjQtMDktMTNUMTc6MzI6NTVaIiwidXJpIjoiZGlzdC9wYWlyLzAuMS43L3BhaXItMC4xLjcuemlwIiwiZGlnZXN0cyI6eyJzaGE1MTIiOiJiMzUzYjVhODJiM2I1NGU5NWY0YTI4NTllN2EyYmQwNjQ4YWJjYjM1YTdjMzYxMmIxMjZjMmM3NTQzOGZjMmY4ZThlZTFmMTllNjFmMzBmYTU0ZDdiYjY0YmNmMjE3ZWQxMjY0NzIyYjQ5N2JjYjYxM2Y4MmQ3ODc1MTUxNWI2NyJ9fQ", + "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-rLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" + } } #} ``` This example includes a PGXN release signature, which is formatted in -accordance with the [JWS JSON Serialization] format specified by [RFC -7515][JWS]. The signed data is the contents of the `payload` property, which -is the Base64 URL-encoded representation of this JSON object (with blank space -formatting removed): +accordance with the "flattened" [JWS JSON Serialization] format specified by +[RFC 7515][JWS]. The signed data is the contents of the `payload` property, +which is the Base64 URL-encoded representation of this JSON object (with blank +space formatting removed): ``` json { - "user": "theory", "date": "2024-09-13T17:32:55Z", - "uri": "dist/pair/0.1.7/pair-0.1.7.zip", "digests": { "sha512": "b353b5a82b3b54e95f4a2859e7a2bd0648abcb35a7c3612b126c2c75438fc2f8e8ee1f19e61f30fa54d7bb64bcf217ed1264722b497bcb613f82d78751515b67" - } + }, + "uri": "dist/pair/0.1.7/pair-0.1.7.zip", + "user": "theory" } ``` @@ -146,7 +142,7 @@ Distribution name and Release version, and signed and published by PGXN. [JWS], JSON Web Signature, represents content secured with digital signatures or Message Authentication Codes (MACs) using JSON-based data structures. This -RFC proposes to us this standard to sign PGXN Releases. +RFC proposes to sign PGXN Releases with JWS. ### Process ### @@ -183,10 +179,10 @@ of the same name: The steps to publish a signed release on PGXN would be: -1. User updates the version in the `META.json` as appropriate for the - release, then bundles the `META.json` file and all required and - recommended source code and documentation files into a zip archive file. - From a Git repository, for example: +1. User updates the version and any other necessary fields in the `META.json` + as appropriate for the release, then bundles the `META.json` file and all + required and recommended source code and documentation files into a zip + archive file. From a Git repository, for example: ```sh git archive --format zip --prefix=pair-0.1.7/ -o pair-0.1.7.zip HEAD @@ -202,9 +198,10 @@ The steps to publish a signed release on PGXN would be: constructs the payload for signing, signs with its private key, then adds the `release` object to the copied `META.json` file. -5. PGXN Manager publishes the distribution archive and the `META.json` file - to the root registry, along with an additional file that lists all the - releases of the distribution. These files would be: +5. PGXN Manager publishes the distribution archive and the signature-bearing + "release" `META.json` file to the root registry, along with an additional + file that lists all the releases of the distribution. These files would + be: * Release list: `dist/pair.json` * 0.1.7 release `META.json`: `dist/pair/0.1.7/META.json` @@ -258,6 +255,15 @@ Clients **MUST** follow the [JWS validation steps]. ### Certs Object Properties +The `certs` property is a JSON object that supports a single key, `pgxn`. No +other keys are allowed except [v2] custom keys, which must start with `x_` or +`X_`. + +The value for the `pgxn` property **MUST** be formatted according to the [JWS +JSON Serialization], which specifies either a "general" or "flattened" syntax. + +#### General Syntax + ```json #{ "pgxn": { @@ -273,12 +279,7 @@ Clients **MUST** follow the [JWS validation steps]. #} ``` -The `certs` property is a JSON object that supports a single key, `pgxn`. No -other keys are allowed except [v2] custom keys, which must start with `x_` or -`X_`. - -The value for the `pgxn` property **MUST** be formatted according to the [JWS -JSON Serialization], which specifies: +The [General JWS JSON Serialization Syntax] specifies these properties: > * **payload**: The "payload" member **MUST** be present and contain the > value `BASE64URL(JWS Payload)`. @@ -311,38 +312,55 @@ JSON Serialization], which specifies: > Additional members can be present in both the JSON objects defined above; if > not understood by implementations encountering them, they MUST be ignored. -#### PGXN Payload +#### Flattened Syntax -For the `pgxn` JWS `payload` property, the value **MUST** be an object with -the following structure: +``` json +#{ + "pgxn": { + "protected":"eyJhbGciOiJSUzI1NiJ9", + "header": {"kid": "2024-12-29" }, + "payload": "eyJ1c2VyIjoidGhlb3J5IiwiZGF0ZSI6IjIwMjQtMDktMTNUMTc6MzI6NTVaIiwidXJpIjoiZGlzdC9wYWlyLzAuMS43L3BhaXItMC4xLjcuemlwIiwiZGlnZXN0cyI6eyJzaGE1MTIiOiJiMzUzYjVhODJiM2I1NGU5NWY0YTI4NTllN2EyYmQwNjQ4YWJjYjM1YTdjMzYxMmIxMjZjMmM3NTQzOGZjMmY4ZThlZTFmMTllNjFmMzBmYTU0ZDdiYjY0YmNmMjE3ZWQxMjY0NzIyYjQ5N2JjYjYxM2Y4MmQ3ODc1MTUxNWI2NyJ9fQ", + "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-rLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" + } +#} +``` + + +The [Flattened JWS JSON Serialization Syntax] is based on the general syntax, +but flattens it by it by removing the replacing the `signatures` array with +the `protected`, `header`, and `signature` fields for a single signature at +the same level as the `payload` property. The values in those properties are +otherwise the same as for the general syntax. + +#### PGXN Payload ``` json { - "user": "theory", "date": "2024-09-13T17:32:55Z", - "uri": "dist/pair/0.1.7/pair-0.1.7.zip", "digests": { "sha512": "b353b5a82b3b54e95f4a2859e7a2bd0648abcb35a7c3612b126c2c75438fc2f8e8ee1f19e61f30fa54d7bb64bcf217ed1264722b497bcb613f82d78751515b67" - } + }, + "uri": "dist/pair/0.1.7/pair-0.1.7.zip", + "user": "theory" } ``` ``` json { - "user": "the_grinch", "date": "2012-04-25T02:48:38Z", - "uri": "dist/widget/0.0.1/widget-0.0.1.zip", "digests": { "sha1": "b7ecaa270e912a60e3dd919918004c6fcd4989c9" } + "uri": "dist/widget/0.0.1/widget-0.0.1.zip", + "user": "the_grinch" } ``` -The format for the `payload` property is itself an object. When formatting -into the `payload` value, its keys **MUST** appear in Unicode [code point] -order and **MUST** contain no formatting-only blank space ("pretty printing"), -but just a single line of JSON. In other words, before Base64 URL-encoding the -second example above, it must be formatted as: +The decoded value of the `pgxn` JWS `payload` property **MUST** be an object. +When formatting into the `payload` value, its keys **MUST** appear in Unicode +[code point] order and **MUST** contain no formatting-only blank space +("pretty printing"), but just a single line of JSON. In other words, before +Base64 URL-encoding the second example above, it must be formatted as: ```json {"date":"2012-04-25T02:48:38Z","digests":{"sha1":"b7ecaa270e912a60e3dd919918004c6fcd4989c9"},"uri":"dist/widget/0.0.1/widget-0.0.1.zip","user":"the_grinch"} @@ -352,7 +370,7 @@ The `pgxn` object **MUST** contain all of these properties: * **user**: The username of the PGXN user who uploaded the release. * **date**: The date of the release in [RFC 3339]/[ISO 8601] format in the - UTC time zone (indicated by the `Z`). + UTC time zone, indicated by a trailing `Z`. * **uri**: The URI for the release archive, relative to a PGXN mirror root, in the format `dist/{name}/{version}/{name}-{version}.zip`. * **digests**: An object containing cryptographic hash digests for the file @@ -367,9 +385,8 @@ The `pgxn` object **MUST** contain all of these properties: * This pattern could make it more difficult for clients to install code from PGXN, especially if they incorrectly validate the signature. -* Some clients may choose not to implement the validation, potentially - leaving users to think they have trusted, validated code when they may - not. +* Some clients may choose not to implement validation, potentially leaving + users to think they have trusted, validated code when they may not. ## Rationale and alternatives @@ -389,36 +406,36 @@ wheel], and is enabled by the separation of the release `META.json` file from the release file it describes. The use of [JWS] ensures a widely-vetted key signing and distribution -standard, and the likelihood that clients can take advantage of well-tested, -mature libraries to handle signing and validation. And finally, the use of RFC -7515-standard [JWS JSON Serialization] allows multiple signatures, which will +standard, and the likelihood that clients can use well-tested, mature +libraries to handle signing and validation. And finally, the use of RFC +7515-standard [JWS JSON Serialization] allows multiple signatures, which may simplify key rotation. ## Future possibilities By embedding the PGXN [JWS] data under its own, key, `pgxn`, the design allows -for other parties to add their own release metadata and signatures. For -example, an organization that provides security scanning services may want to -add their own signature to validate that they have tested a specific release. +other parties to add their own certifications and signatures. For example, an +organization that provides security scanning services may want to add their +own signature to certify that they have tested a specific release. In the future we may also want to issue key pairs to registered developers and require that they sign releases. This would allow an extra level of protection, as well as key revocation in case an upload has been tampered with. -For the PGXN signing itself the proposed use of a separate, offline "root" key -to sign an intermediate "release" key would allow for easier key rotation in -the event the "release" private key was compromised. +For the PGXN signing itself, the proposed use of a separate, offline "root" +key to sign an intermediate "release" key would simplify key rotation in the +event of "release" private key compromise. ## Unresolved questions * Is `certs` the best name for this new property? People many assume it means "certificates". Other possibilities: - * `signatures` (might there be other kinds of certifications?) - * `certifications` (long, still close to "certificates", which is overloaded) - * `receipts` (cute, but a bit opaque) - * `attestations` (abstract and too JWT-y) - * `jws` (dissuades other formats) + * `signatures`: Might there be other kinds of certifications? + * `certifications`: Long; still close to "certificates", which is overloaded + * `receipts` Cute, but a bit opaque + * `attestations` Abstract and too JWT-y + * `jws` Dissuades other formats * `coupons`, `authentication`, `authenticity`, `credentials`, `vouchers`, `records` (meh) @@ -426,13 +443,11 @@ the event the "release" private key was compromised. signatures, should we relax the requirement that additional keys start with `x_` or `X_`? In the future if we allowed, say, author signatures, then we might add the key `author` or `user` or some such. Would we allow - any other signatures to appear in the file on PGXN? Perhaps allow `x_` and - `X_` properties of any kind, but require any other properties to be [JWS - JSON Serialization] objects? + any other signatures to appear in the file on PGXN? -* Should we eliminate the `digests` object in the `payload` and allow +* Should we eliminate the `digests` object in the payload and require `SHA-512` only? I had made an object with `sha1` to simplify migration - from the PGXN v1 spec, which includes only a `sha1` has, but maybe it'd be + from the PGXN v1 spec, which includes only a `sha1`, but maybe it'd be better to simplify the structure here and requires new SHA-512s from the migration. @@ -448,7 +463,7 @@ the event the "release" private key was compromised. published? Should the public live in a separate domain, or some sort of key store, so clients can fetch them with a higher degree of trust? The fear is that someone may compromise the root registry, modify extensions, - and then sign them with their own keys, which replace our own keys. + and then sign them with their own keys, which replace the PGXN keys. [PGXN]: https://pgxn.org "PostgreSQL Extension Network" [PGXN Manager]: https://manager.pgxn.org @@ -476,4 +491,7 @@ the event the "release" private key was compromised. "RFC 7517 JSON Web Key (JWK): JWK Set Format" [JWK]: https://datatracker.ietf.org/doc/html/rfc7517 "RFC 7517 JSON Web Key (JWK)" - \ No newline at end of file + [General JWS JSON Serialization Syntax]: https://datatracker.ietf.org/doc/html/rfc7515#section-7.2.1 + "RFC 7515: General JWS JSON Serialization Syntax" + [Flattened JWS JSON Serialization Syntax]: https://datatracker.ietf.org/doc/html/rfc7515#section-7.2.2 + "RFC 7515: Flattened JWS JSON Serialization Syntax" From aae2b73b806fd5dccc860b2c277b29acc039b57d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Oct 2024 17:58:57 +0200 Subject: [PATCH 10/11] Remove v2 --- text/0005-release-certification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0005-release-certification.md b/text/0005-release-certification.md index d1828a5..8287f45 100644 --- a/text/0005-release-certification.md +++ b/text/0005-release-certification.md @@ -1,4 +1,4 @@ -{{#title PGXN RFC–5 — Release Certification v2}} +{{#title PGXN RFC–5 — Release Certification}} * **RFC:** 5 * **Title:** PGXN Release Certification * **Slug:** `release-certification` From cbc55498b6f774592bf70f288890c74baec73c10 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Oct 2024 18:35:28 +0200 Subject: [PATCH 11/11] Add fixes thanks to @pgguru --- text/0005-release-certification.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/text/0005-release-certification.md b/text/0005-release-certification.md index 8287f45..17cf282 100644 --- a/text/0005-release-certification.md +++ b/text/0005-release-certification.md @@ -212,10 +212,10 @@ The steps to publish a signed release on PGXN would be: The steps for a client to find, download, and verify a PGXN release would be: 1. Using a valid PGXN mirror, assemble and fetch the release list for the - the extension, `dist/pair.json`. + extension, `dist/pair.json`. 2. Use the release list to determine the best version to install and assemble the URI for its release `META.json`. The format is - `dist/{name}/{version}/META.json`; for the above example, that results in + `dist/{name}/{version}/META.json`; for the above example, which results in `dist/pair/0.1.7/META.json`. 3. Fetch the release `META.json` file, read in the `certs/pgxn` object, and use PGXN's current public key (downloaded as a [RFC 7517 JWK Set]) to @@ -236,8 +236,8 @@ of trust: 1. A root key pair is maintained by PGXN, with the private key kept offline. 2. A release key pair is generated and signed by the private root key, with the private key kept in an online vault accessible only to PGXN Manager. -3. The public keys for both keys are published by PGXN as a [RFC 7517 JWK - Set]. +3. The public keys for both key pairs are published by PGXN as a [RFC 7517 + JWK Set]. 4. PGXN Manager uses the private release key to sign releases as described above. The most important property in the signed payload is the list of digests. @@ -298,7 +298,7 @@ The [General JWS JSON Serialization Syntax] specifies these properties: > > * **header**: The "header" member **MUST** be present and contain the > value JWS Unprotected Header when the JWS Unprotected Header value is -> non- empty; otherwise, it **MUST** be absent. This value is represented +> non-empty; otherwise, it **MUST** be absent. This value is represented > as an unencoded JSON object, rather than as a string. These Header > Parameter values are not integrity protected. >