From 3bf3387ec250424535c78b101387d27c5f1d3950 Mon Sep 17 00:00:00 2001 From: Steve Lasker Date: Wed, 20 Apr 2022 14:20:24 -0700 Subject: [PATCH 01/17] Markdown headings, typos and lead-in for additional identities (#143) Signed-off-by: Steve Lasker --- trust-store-trust-policy-specification.md | 24 +++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/trust-store-trust-policy-specification.md b/trust-store-trust-policy-specification.md index 83b92845..1ca6b29e 100644 --- a/trust-store-trust-policy-specification.md +++ b/trust-store-trust-policy-specification.md @@ -11,14 +11,16 @@ The document consists of the following sections: Users who consume and execute the signed artifact from a registry need a mechanism to specify the trusted producers. This is where Trust Store is used. -Trust store allows users to specify two kinds of identities: +Trust store allows users to specify two kinds of identities, with additional identities coming in future releases: - **Certificates**: These are the signing certificates or certificates in the certificate chain of the signing certificate. - **Timestamping Certificates**: These are the timestamping certificates or certificates in the certificate chain of the timestamping certificate. +### Trust Store Schema + The trust store is represented as JSON data structure as shown below: -```json +```jsonc { "version": "1.0", "trustStores": { @@ -50,7 +52,7 @@ The trust store is represented as JSON data structure as shown below: } ``` -Property Description: +### Trust Store Properties - **`version`**(*string*): This REQUIRED property is the version of the trust store. The supported value is `1.0` @@ -100,17 +102,19 @@ Since revocation check requires network call and network call can fail because o - `fail-open`: If revocation endpoint is not reachable, consider artifact as not revoked. - `fail-close`: If revocation endpoint is not reachable, consider artifact as revoked. +### Trust Policy Schema + The trust policy is represented as JSON data structure as shown below: -```json +```jsonc { "version": "1.0", "trustPolicies": [ { "name": "verify-signature", "scopes": [ - "wabbit-networks.io/software/product1" - "wabbit-networks.io/software/product2" ], + "registry.wabbit-networks.io/software/net-monitor" + "registry.wabbit-networks.io/software/net-logger" ], "trustStores": [ "trust-store-name-1", "trust-store-name-2" ], "trustAnchors": [ "subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io" @@ -149,7 +153,7 @@ The trust policy is represented as JSON data structure as shown below: } ``` -Property descriptions +### Trust Policy Properties - **`version`**(*string*): This REQUIRED property is the version of the trust policy. The supported value is `1.0`. @@ -180,7 +184,7 @@ Property descriptions - **`timestampRevocation`**(*string*): This REQUIRED property specifies whether implementation should check for timestamping certificate and certificate-chain revocation status or not and what implementation must do if this revocation check fails. Supported values are `enforceWithFailOpen`, `enforceWithFailClose`, `warn` and `skip`. -Value descriptions +#### Value descriptions - **`enforce`**: This means implementation MUST perform validation and throw an error if validation fails. - **`enforceWithFailOpen`**: This means implementation MUST perform validation and if validation fails because the endpoint is not reachable, the implementation MUST log an error and MUST NOT fail the validation. @@ -243,7 +247,7 @@ These custom validation MUST have access to all the information available in the ### Prerequisites -- User has configured valid [trust store](#trust-store) and [trust policy](#trust-policy). +- User has configured a valid [trust store](#trust-store) and [trust policy](#trust-policy). ### Steps @@ -301,7 +305,7 @@ These custom validation MUST have access to all the information available in the ### Certificate Revocation Evaluation If the certificate revocation trust-store setting is set to `skip`, skip the below steps. -therwise, check for revocation status for certificate and certificate chain. +otherwise, check for revocation status for certificate and certificate chain. 1. If the revocation status of any of the certificates cannot be determined (revocation unavailable) and `signingIdentityRevocation` is set to either `enforceWithFailOpen` or `warn` then log a warning and skip the below steps. Otherwise, fail the signature validation and exit. From 80ec795efdd4e68b8294be5dabd42502020ef123 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Tue, 26 Apr 2022 17:37:12 +0200 Subject: [PATCH 02/17] json schema cleanup (#151) Signed-off-by: qmuntal --- specs/plugin-extensibility.md | 40 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/specs/plugin-extensibility.md b/specs/plugin-extensibility.md index 087056d7..ad253677 100644 --- a/specs/plugin-extensibility.md +++ b/specs/plugin-extensibility.md @@ -49,18 +49,18 @@ Notation will invoke plugins as executable, pass parameters using command line a * Notation will invoke the plugin executable for each command (e.g. sign, verify), pass inputs through `stdin` and get output through `stdout` and `stderr`. * The command will be passed as the first argument to the plugin e.g. `notary-{plugin-name} `. A JSON request is passed using `stdin`. The plugin is expected to return a JSON response through `stdout` with a `0` exit code for successful response, and a non-zero exit code with a JSON error response in `stderr` for error response. Each command defines its request, response and error contract. To avoid any additional content like debug or info level logging from dependencies and inbuilt libraries, the plugin implementation should redirect any output to `stdout` on initialization, and only send the JSON response away from `stdout` when the command execution completes. E.g. For golang, set [`os.Stdout`](https://pkg.go.dev/os#pkg-variables) to point to a log file. -* Every request JSON will contain a `contract-version` top level attribute whose value will indicate the plugin contract version. Contract version is revised when there are changes to command request/response, new plugin commands are introduced, and supported through Notation. +* Every request JSON will contain a `contractVersion` top level attribute whose value will indicate the plugin contract version. Contract version is revised when there are changes to command request/response, new plugin commands are introduced, and supported through Notation. * For an error response, every command returns a non-zero exit code 1, with an OPTIONAL JSON error response in `stderr`. It is recommended to return an error response to help user troubleshoot the error. There is no need to send different exit codes for different error conditions, as Notation (which will call the plugin and parse the response) will use `error-code` in the error response to interpret different error conditions if needed. Notation will attempt to parse the error response in `stderr` when exit code is 1, else treat it as a general error for any other non-zero exit codes. An implementation can ```jsonc { // Each command defines expected error codes - "error-code" : "", + "errorCode" : "", // Plugin defined error message. - "error-message" : "User friendly error message" + "errorMessage" : "User friendly error message", // Optional plugin defined additional key value pairs related to the error. - "error-metadata" : { + "errorMetadata" : { "key1" : "value1", "key2" : "value2" } @@ -83,16 +83,16 @@ All response attributes are required. { // Plugin name that matches its install dir // e.g. "com.example.nv2plugin". - "plugin-name" : "", + "name" : "", // Plugin friendly name. - "plugin-friendly-name" : "", + "friendlyName" : "", // Plugin publisher controlled version. - "plugin-version" : "", + "version" : "", // Plugin webpage for support or documentation. - "plugin-url" : "", + "url" : "", // List of contract versions supported by the plugin, one per major version - "supported-contract-versions" : [ ], + "supportedContractVersions" : [ ], // Currently one of // SIGNATURE_GENERATOR or @@ -146,13 +146,13 @@ This interface targets plugins that integrate with providers of basic cryptograp ```jsonc { - "contract-version" : , + "contractVersion" : "", // Complete key definition from // /notation/config.json /signingKeys/keys with matching key name - "keyDefinition" : , + "keyDefinition" : "", - "payload" : + "payload" : "" } ``` @@ -175,9 +175,9 @@ All response attributes are required. ```jsonc { // The same key id as passed in the request. - "keyId" : , - "signature" : , - "signingAlgorithm" : + "keyId" : "", + "signature" : "", + "signingAlgorithm" : "", "certificateChain": ["Base64(DER(leafCert))","Base64(DER(intermediateCACert))","Base64(DER(rootCert))"] } ``` @@ -226,12 +226,12 @@ All request attributes are required. ```jsonc { - "contract-version" : , + "contractVersion" : "", // Complete key definition from /notation/config.JSON /signingKeys/keys with matching key name - "keyDefinition" : , + "keyDefinition" : "", - "payload" : , + "payload" : "", // The type of payload - currently a descriptor "payloadType" : "application/vnd.oci.descriptor.v1+json", @@ -250,8 +250,8 @@ All response attributes are required. ```jsonc { - "signatureEnvelope": , - "signatureEnvelopeType" : "application/vnd.cncf.notary.v2.jws.v1" + "signatureEnvelope": "", + "signatureEnvelopeType" : "application/vnd.cncf.notary.v2.jws.v1", // Annotations to be appended to Signature Manifest annotations "annotations" : { From 2d3c8ddd9e8a1f6bf7feb1cac7228b05b0ec9ed7 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Wed, 27 Apr 2022 20:19:25 +0200 Subject: [PATCH 03/17] Expand plugin spec with installation details (#150) * Expand plugin spec with installation details * Update specs/plugin-extensibility.md * rename discover to get-plugin-metadata * rename friendlyName to description Signed-off-by: qmuntal Co-authored-by: Milind Gokarn --- specs/plugin-extensibility.md | 60 +++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/specs/plugin-extensibility.md b/specs/plugin-extensibility.md index ad253677..af3bbd33 100644 --- a/specs/plugin-extensibility.md +++ b/specs/plugin-extensibility.md @@ -14,7 +14,7 @@ Keys and associated certificates used for signing artifacts using Notary could b * A plugin publisher MUST be able to distribute and patch a Notation plugin independently of Notation’s release cycle. * The plugin mechanism MUST work across commonly used OS platforms like Linux, Windows and macOS. -* The plugin interface contract MUST be versioned. This version is different from Notation's release version. It will be used to accomodate for additional capabilities and contract changes post initial release of Notation. +* The plugin interface contract MUST be versioned. This version is different from Notation's release version. It will be used to accommodate for additional capabilities and contract changes post initial release of Notation. * A plugin MAY implement a subset of capabilities (features) available in plugin contract. E.g A plugin may implement signing feature, but not verification. * Notation and plugins MAY be updated independently in an environment. * Notation MUST work with a plugin that implements a matching or lower minor version of the plugin contract. Notation SHALL NOT support using a plugin with higher version of plugin contract. @@ -22,10 +22,42 @@ Keys and associated certificates used for signing artifacts using Notary could b Notation will invoke plugins as executable, pass parameters using command line arguments, and use standard IO streams to pass request/response payloads. This mechanism is used as Go language (used to develop [Notation library](https://github.com/notaryproject/notation-go-lib)) does not have a [in built support](https://github.com/golang/go/issues/19282) to load and execute plugins that works across OS platforms. Other mechanisms like gRPC require every plugin to be implemented as a service/daemon. -### Plugin installation and config +### Plugin lifecycle management + +#### Installation + +Plugin publisher will provide instructions to download and install the plugin. Plugins intended for public distribution should also include instructions for users to verify the authenticity of the plugin. + +**Open Item** : [Plugin install paths](https://github.com/notaryproject/notation/issues/167) + +To enumerate all available plugins the following paths are scanned: +* Unix-like OSes: + * `$HOME/.notation/plugins` +* On Windows: + * `%USERPROFILE%\.notation\plugins` + +Each plugin executable and dependencies are installed under directory `~/.notation/plugins/{plugin-name}` with an executable under that directory `~/.notation/plugins/{plugin-name}/notation-{plugin-name}`. + +Any directory found inside `~/.notation/plugins` is considered potential plugin "candidates". Anything found which is not a directory is ignored and is not considered as a plugin candidate. + +To be considered a valid plugin a candidate must pass each of these "plugin candidate tests": + +* The directory must contain an executable named `notation-{plugin-name}`. +* The executable MUST be a regular file, symlinks are not supported. Implementation MUST validate that the executable is a regular file, before executing it, and fail it it does not meet this condition. +* On Windows, executables must have a `.exe` suffix. +* Must, where relevant, have appropriate OS "execute" permissions (e.g. Unix x bit set) for the current user. +* Must actually be executed successfully and when executed with the subcommand `get-plugin-metadata` must produce a valid JSON metadata (and nothing else) on its standard output (schema to be discussed later). + +#### Commands + +* List + +`notation plugin list` + +List all valid plugins. + +### Plugin configuration -* Plugin publisher will provide instructions to download and install the plugin. Plugins intended for public distribution should also include instructions for users to verify the authenticity of the plugin. -* Each plugin executable and dependencies are installed under directory `~/.notation/plugins/{plugin-name}` with an executable under that directory `~/.notation/plugins/{plugin-name}/notation-{plugin-name}`. The executable can be a shim which calls plugin dependecies installed elsewhere on the file system. * To use a plugin for signing, the user associates the plugin as part of registering a signing key. E.g. * `notation key add --name "mysigningkey" --id "keyid" --plugin "com.example.nv2plugin"` * In the example, the command registers a signing key in `/notation/config.json`, where `mysigningkey` is a friendly key name to refer during signing operation from the CLI, `id` is an key identifier known to plugin that is used for signing, and the value of `plugin` specifies it's using a plugin located at `~/.notation/plugins/com.example.nv2plugin/notation-com.example.nv2plugin`. @@ -69,10 +101,10 @@ Notation will invoke plugins as executable, pass parameters using command line a ### Plugin metadata -* Every plugin MUST implement a metadata discovery command called `discover`. -* Notation will invoke this command as part of signing and verification workflows to discover the capabilities of the plugin and subsequently invoke commands corresponding to the capabilities. Notation will invoke the `discover` command every time a signing or verification workflow is invoked to discover metadata. Notation will not cache the response of `discover` command as it involves invalidating cache when a plugin is updated, detecting a plugin update can be non trivial. The `discover` command is expected to return only static data, and plugin implementors should avoid making remote calls in this command. +* Every plugin MUST implement a metadata discovery command called `get-plugin-metadata`. +* Notation will invoke this command as part of signing and verification workflows to discover the capabilities of the plugin and subsequently invoke commands corresponding to the capabilities. Notation will invoke the `get-plugin-metadata` command every time a signing or verification workflow is invoked to discover metadata. Notation will not cache the response of `get-plugin-metadata` command as it involves invalidating cache when a plugin is updated, detecting a plugin update can be non trivial. The `get-plugin-metadata` command is expected to return only static data, and plugin implementors should avoid making remote calls in this command. -***discover*** +***get-plugin-metadata*** *Request* - None @@ -84,8 +116,8 @@ All response attributes are required. // Plugin name that matches its install dir // e.g. "com.example.nv2plugin". "name" : "", - // Plugin friendly name. - "friendlyName" : "", + // Plugin description. + "description" : "description>", // Plugin publisher controlled version. "version" : "", // Plugin webpage for support or documentation. @@ -105,9 +137,9 @@ All response attributes are required. *plugin-name* - Plugin name uses reverse domain name notation to avoid plugin name collisions. -*supported-contract-versions* - The list of contract versions supported by the plugin. Currently this list must include only one version, per major version. Post initial release, Notation may add new features through plugins, in the form of new commands (e.g. tsa-sign for timestamping), or additional request and response parameters. Notation will publish updates to plugin interface along with appropriate contract version update. Backwards compatible changes (changes for which older version of plugin continue to work with versions of Notation using newer contract version) like new optional parameters on existing contracts, and new commands will be supported through minor version contract updates, breaking changes through major version updates. Plugin `discover` command returns the contract version a plugin supports. Notation will evaluate the minimum plugin version required to satisfy a user's request, and reject the request if the plugin does not support the required version. +*supported-contract-versions* - The list of contract versions supported by the plugin. Currently this list must include only one version, per major version. Post initial release, Notation may add new features through plugins, in the form of new commands (e.g. tsa-sign for timestamping), or additional request and response parameters. Notation will publish updates to plugin interface along with appropriate contract version update. Backwards compatible changes (changes for which older version of plugin continue to work with versions of Notation using newer contract version) like new optional parameters on existing contracts, and new commands will be supported through minor version contract updates, breaking changes through major version updates. Plugin `get-plugin-metadata` command returns the contract version a plugin supports. Notation will evaluate the minimum plugin version required to satisfy a user's request, and reject the request if the plugin does not support the required version. -*capabilities* - Non empty list of features supported by a plugin. Each capability such as `SIGNATURE_ENVELOPE_GENERATOR` requires one of more commands to be implemented by the plugin. When new features are available for plugins to implement, an implementation may choose to not implement it, and therefore will not include the feature in capabililies. Notation will evaluate the capability required to satisfy a user’s request, and reject the request if the plugin does not support the required capability. +*capabilities* - Non empty list of features supported by a plugin. Each capability such as `SIGNATURE_ENVELOPE_GENERATOR` requires one of more commands to be implemented by the plugin. When new features are available for plugins to implement, an implementation may choose to not implement it, and therefore will not include the feature in capabilities. Notation will evaluate the capability required to satisfy a user’s request, and reject the request if the plugin does not support the required capability. ## Signing interfaces @@ -127,7 +159,7 @@ This interface targets plugins that integrate with providers of basic cryptograp 1. Pull the image manifest using `oci-artifact` url, and construct a descriptor 1. Append any user provided metadata and Notary metadata as descriptor annotations. 1. Determine if the registered key uses a plugin -1. Execute the plugin with `discover` command +1. Execute the plugin with `get-plugin-metadata` command 1. If plugin supports capability `SIGNATURE_GENERATOR` 1. Generate the payload to be signed. For JWS this includes [JWS](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#supported-signature-envelopes) payload with `subject` claim as descriptor, additional JWS claims, and protected headers. 2. Execute the plugin with `generate-signature` command, set `request.keyDefinition` to key definition corresponding to `keyName`, `request.payload` to the generated payload. @@ -205,7 +237,7 @@ This interface targets plugins that in addition to signature generation want to 1. Pull the image manifest using `image` url, and construct a descriptor 1. Append any user provided metadata and Notary metadata as descriptor annotations. 1. Determine if the registered key uses a plugin -1. Execute the plugin with `discover` command +1. Execute the plugin with `get-plugin-metadata` command 1. If plugin supports capability `SIGNATURE_ENVELOPE_GENERATOR` 1. Execute the plugin with `generate-envelope` command, set `request.keyDefinition` to key definition (from `/notation/config.json`) corresponding to `keyName`, `request.payload` to base64 encoded descriptor, `request.payloadType` to `application/vnd.oci.descriptor.v1+json` and `request.signatureEnvelopeType` to a pre-defined type (default to `application/vnd.cncf.notary.v2.jws.v1`). 1. `response.signatureEnvelope` contains the base64 encoded signature envelope, value of `response.signatureEnvelopeType` MUST match request.signatureEnvelopeType. @@ -283,4 +315,4 @@ TBD [#135](https://github.com/notaryproject/notaryproject/issues/135) * [Issue #151](https://github.com/notaryproject/notation/issues/151) - Add Notation command line options to pass raw signature generated by existing crypto tools. * What standard providers should be supported? * Do we need to support file based plugin configuration, or pass-through plugin configuration passed as is from Notation CLI to the plugin? -* Suport for chaining plugins. It allows us to seperate out and compose things like signing, TSA integration, push to transparency log. \ No newline at end of file +* Support for chaining plugins. It allows us to separate out and compose things like signing, TSA integration, push to transparency log. \ No newline at end of file From 7076f759996f7383cf3514181b2cda8661614a6e Mon Sep 17 00:00:00 2001 From: Milind Gokarn Date: Fri, 29 Apr 2022 17:12:44 -0700 Subject: [PATCH 04/17] Simplify trust store and trust policy. (#149) * Draft of trust store and policy updates. * Added more details for signature verification levels. * Updated signature verification process based on trust policy changes. * Updated identities constraints section. * Changed trust store location to $XDG_CONFIG_HOME/notation/trust-store * Minor update. * Updated the draft revocation check section, and made a note that custom verification level is not implemented in RC1. Signed-off-by: Milind Gokarn --- trust-store-trust-policy-specification.md | 438 +++++++++++----------- 1 file changed, 217 insertions(+), 221 deletions(-) diff --git a/trust-store-trust-policy-specification.md b/trust-store-trust-policy-specification.md index 1ca6b29e..aa5df66f 100644 --- a/trust-store-trust-policy-specification.md +++ b/trust-store-trust-policy-specification.md @@ -1,154 +1,145 @@ # Trust Store and Trust Policy Specification -This document describes how Notary v2 signatures are evaluated for trust. -The document consists of the following sections: +Notary v2 currently supports X.509 based PKI and identities, and uses a trust store and trust policy to determine if a signed artifact is considered authentic. -- **[Trust Store](#trust-store)**: Defines set of signing identity that user trusts. -- **[Trust Policy](#trust-policy)**: Defines how the artifact is evaluated for trust. +The document consists of the following sections: -## Trust Store +- **[Trust Store](#trust-store)**: Contains a set of trusted identities through which trust is derived for the rest of the system. For X.509 PKI, the trust store typically contains a set of root certificates. +- **[Trust Policy](#trust-policy)**: A policy language which indicates which identities are trusted to produce artifacts. Both trust store and trust policy need to be configured by users/administrators before artifact signature can be evaluated. +- **[Signature Verification](#signature-verification)**: Describes how signatures are evaluated using the policy, to determine if a signed artifact is authentic. This section is meant for implementors of Notary v2 standards. -Users who consume and execute the signed artifact from a registry need a mechanism to specify the trusted producers. -This is where Trust Store is used. +Other types of identities and trust models may be supported in future, which may introduce other constructs/policy elements to support signature evaluation. -Trust store allows users to specify two kinds of identities, with additional identities coming in future releases: +## Scenario -- **Certificates**: These are the signing certificates or certificates in the certificate chain of the signing certificate. -- **Timestamping Certificates**: These are the timestamping certificates or certificates in the certificate chain of the timestamping certificate. +All examples use the actors defined in Notary v2 [scenario](https://github.com/notaryproject/notaryproject/blob/main/scenarios.md#scenario-0-build-publish-consume-enforce-policy-deploy) -### Trust Store Schema +- Wabbit Networks company builds, signs and distributes their `net-monitor` software though public registries. +- ACME Rockets consumes the `net-monitor` software from a public registry importing the artifacts and reference artifacts (signatures, SBoMs) into their private registry. The private registry also contains additional artifacts that ACME Rockets themselves sign. -The trust store is represented as JSON data structure as shown below: +## Trust Store -```jsonc -{ - "version": "1.0", - "trustStores": { - "trust-store-name-1": { - "identities": { - "x509Certs": [ - "-----BEGIN CERTIFICATE-----\ncertificate1\n-----END CERTIFICATE-----\n", - "-----BEGIN CERTIFICATE-----\ncertificate2\n----END CERTIFICATE-----\n" - ], - "tsaX509Certs": [ - "-----BEGIN CERTIFICATE-----\ntsaCertificate1\n-----END CERTIFICATE-----\n", - "-----BEGIN CERTIFICATE-----\ntsaCertificate2\n-----END CERTIFICATE-----\n" - ] - } - }, - "trust-store-name-2": { - "identities": { - "x509Certs": [ - "-----BEGIN CERTIFICATE-----\ncertificate1\n-----END CERTIFICATE-----\n", - "-----BEGIN CERTIFICATE-----\ncertificate2\n----END CERTIFICATE-----\n" - ], - "tsaX509Certs": [ - "-----BEGIN CERTIFICATE-----\ntsaCertificate2\n-----END CERTIFICATE-----\n", - "-----BEGIN CERTIFICATE-----\ntsaCertificate3\n----END CERTIFICATE-----\n" - ] - } - } - } -} +Contains a set of trusted identities through which trust is derived for the rest of the system. For X.509 PKI, the trust store typically contains root certificates. + +- The Notary v2 trust store consists of multiple named collections of certificates, called named stores. +- Following certificate formats are supported - Files with extension .pem, .crt and .cer, the files are expected to contain certificate(s) in DER (binary) format or PEM format (base-64 encoded DER). +- The trust store is a directory location, under which each sub directory is considered a named store, that contains zero or more certificates. The name of this sub directory is used to reference the specific store in trust policy. +- Symlinks are not supported for the named store directories or certificate files. Implementation MUST validate that the named store directory or certificate files are not symlinks, and fail if it does not meet this condition. +- Certificates in a trust store are root certificates. Placing intermediate certificates in the trust store is not recommended this is a form of certificate pinning that can break signature verification unexpectedly anytime the intermediate certificate is rotated. + +Notary v2 uses following directory structure to represent the trust store. The example shows named stores `acme-rockets` and `wabbit-networks`, which are subseqently references in the trust policy. Without this reference, presence of a named store and certificates in it does not confer trust automatically to the named store. The trust store is configured ahead of verification time, by an out of band mechanism that is beyond the scope of this document. Different entities and organizations have their own processes and policies to configure and distribute trust stores. + +```text +$XDG_CONFIG_HOME/notation/trust-store + /x509 + /ca + /acme-rockets + cert1.pem + cert2.pem + /sub-dir # sub directory is ignored + cert-3.pem # certs under sub directory is ignored + /wabbit-networks + cert3.crt + /tsa + /publicly-trusted-tsa + tsa-cert1.pem ``` -### Trust Store Properties - -- **`version`**(*string*): This REQUIRED property is the version of the trust store. - The supported value is `1.0` -- **`trustStores`**(*object*): This REQUIRED property represents the parent node containing multiple trust stores. - Each trust store is identified by the key associated with it like 'trust-store-name-1', 'trust-store-name-2'. - - **`identities`**(*object*): This REQUIRED property represents the collection of different types of identities. - There are two types of identifies that Notary v2 supports: x509 certificates and x509 timestamping certificates. - - **`x509Certs`**(*array of strings*): This REQUIRED property specifies a list of x509 certificates in PEM format. - The collection MUST contain at least one certificate. - - **`tsaX509Certs`**(*array of strings*): This OPTIONAL property specifies a list of x509 timestamping certificates in PEM format. - If the `tsaX509Certs` key is present then collection MUST contain at least one timestamping certificate. +The Trust store currently supports two kinds of identities, additional identities may be supported in future : -## Trust Policy - -Users who consume and execute the signed artifact from a registry need a mechanism to specify how the artifacts should be evaluated for trust, this is where a trust policy is used. -Trust policy allows users to control the artifact's integrity, expiry, and revocation aspect of signature evaluation. - -### Artifact Integrity - -The artifact MUST be signed and has not been altered. - -### Artifact Expiry - -Trust policy allows users to define how the system should behave when the artifact's signature is expired or signing identity is expired or timestamping identity is expired. - -If artifact expiry validations are enforced the implementation MUST perform the following validations: - -1. If signature expiry is present then signature MUST NOT be expired. -1. If signing identity is certificate and - 1. The timestamp signature is not present then the signing certificate and the certificate chain MUST NOT be expired. - 1. The timestamp signature is present then the timestamp signature MUST be valid. - At the time of timestamping, signing certificate (including certificate chain) MUST NOT be expired. - Also, the timestamping certificate and certificate chain MUST NOT be expired. +- **Certificates**: The `x509/ca` trust store contains named stores that contain Certificate Authority (CA) root certificates. +- **Timestamping Certificates**: The `x509/tsa` trust store contains named stores with Time Stamping Authority (TSA) root certificates. **NOTE** TSA based timestamping will not be available in `notation` RC1. -### Artifact Revocation +Any additional sub directories under names stores and certificates in it are ignored. **NOTE**: Implementation SHOULD warn if it finds sub directories with certificates under a named store, to help diagnose misconfigured store. -Trust policy also allows users to control how the system should behave when signing identity or timestamping identity is revoked +## Trust Policy -If revocation validations are enforced implementation MUST perform the following validations: +Users who consume signed artifact from a registry use the trust policy to specify trusted identities which will sign the artifacts, and level of signature verification to use. -1. If signing identity is a certificate then signing certificate and certificate chain MUST NOT be revoked. -1. If the timestamp signature is present then the timestamping certificate and certificate chain MUST NOT be revoked. +### Trust Policy Schema -The implementation MUST support both [OCSP](https://datatracker.ietf.org/doc/html/rfc6960) and [CRL](https://datatracker.ietf.org/doc/html/rfc5280) based revocations. -Since revocation check requires network call and network call can fail because of a variety of reasons such as revocation endpoint is unavailable, network connectivity issue, DDoS attack, etc the implementation MUST support both `fail-open` or `fail-close` use cases. +The trust policy is a JSON document, here are some examples. -- `fail-open`: If revocation endpoint is not reachable, consider artifact as not revoked. -- `fail-close`: If revocation endpoint is not reachable, consider artifact as revoked. +Trust policy for a simple scenario where ACME Rockets uses only artifacts signed by their CI/CD. Any third party artifacts also are vetted and signed by ACME Rockets. -### Trust Policy Schema +```jsonc +{ + "version": "1.0", + "trustPolicies": [ + { + # Policy for all artifacts, from any registry location. + "name": "wabbit-networks-images", + "registryScopes": [ "*" ], + "signatureVerification" : "audit", + "trustedIdentities": { + "trustStore": "ca:acme-rockets", + "identities": [ + "x509.subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io, OU=Finance, CN=SecureBuilder" + ] + } + } + ] +} +``` -The trust policy is represented as JSON data structure as shown below: +Trust policy for the scenario where ACME Rockets uses artifacts signed by themselves, and signed and unsigned artifacts from Wabbit Networks: ```jsonc { "version": "1.0", "trustPolicies": [ { - "name": "verify-signature", - "scopes": [ - "registry.wabbit-networks.io/software/net-monitor" - "registry.wabbit-networks.io/software/net-logger" ], - "trustStores": [ "trust-store-name-1", "trust-store-name-2" ], - "trustAnchors": [ - "subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io" - ], - "expiryValidations": { - "signatureExpiry": "enforce | warn", - "signingIdentityExpiry": "enforce | warn", - "timestampExpiry": "enforce | warn" - }, - "revocationValidations": { - "signingIdentityRevocation": "enforceWithFailOpen | enforceWithFailClose | warn | skip", - "timestampRevocation": "enforceWithFailOpen | enforceWithFailClose | warn | skip" + # Policy for set of artifacts signed by Wabbit Networks + # that are pulled from ACME Rockets repository + "name": "wabbit-networks-images", + "registryScopes": [ + "registry.acme-rockets.io/software/net-monitor" + "registry.acme-rockets.io/software/net-logger" ], + "signatureVerification" : "strict", + "trustedIdentities": { + "trustStore": "ca:acme-rockets", + "identities": [ + "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Security Tools" + ] } }, { - "name": "skip-signature-verification", - "scopes": [ "wabbit-networks.io/software/unsigned/productA" ], - "skipSignatureVerification": true, + # Exception policy for a single unsigned artifact pulled from + # Wabbit Networks repository + "name": "unsigned-image", + "registryScopes": [ "registry.wabbit-networks.io/software/unsigned/net-utils" ], + "signatureVerification": "skip", }, { - "name": "global-trust-policy", - "scopes": [ "*" ], - "trustStores": [ "trust-store-name-1", "trust-store-name-2" ], - "expiryValidations": { - "signatureExpiry": "enforce | warn", - "signingIdentityExpiry": "enforce | warn", - "timestampExpiry": "enforce | warn" + # Policy for with custom verification policy + "name": "use-expired-image", + "registryScopes": [ "registry.acme-rockets.io/software/legacy/metrics" ], + "signatureVerification": + { + "level" : "strict", + "override" : + { + "expiry" : "skip" + } }, - "revocationValidations": { - "signingIdentityRevocation": "enforceWithFailOpen | enforceWithFailClose | warn | skip", - "timestampRevocation": "enforceWithFailOpen | enforceWithFailClose | warn | skip" + "trustedIdentities": { + "trustStore": "ca:acme-rockets", + "identities": ["*"] + } + }, + { + # Policy for all other artifacts signed by ACME Rockets + # from any registry location + "name": "global-policy-for-all-other-images", + "registryScopes": [ "*" ], + "signatureVerification" : "audit", + "trustedIdentities": { + "trustStore": "ca:acme-rockets", + "identities": [ + "x509.subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io, OU=Finance, CN=SecureBuilder" + ] } } - ] } ``` @@ -158,41 +149,44 @@ The trust policy is represented as JSON data structure as shown below: - **`version`**(*string*): This REQUIRED property is the version of the trust policy. The supported value is `1.0`. - **`trustPolicies`**(*string-array of objects map*): This REQUIRED property represents a collection of trust policies. - - **`name`**(*string*): This REQUIRED propert represents the name of the trust policy. - - **`scopes`**(*array of strings*): This REQUIRED property determines which trust policy is applicable for the given artifact. + - **`name`**(*string*): This REQUIRED property represents the name of the trust policy. + - **`registryScopes`**(*array of strings*): This REQUIRED property determines which trust policy is applicable for the given artifact. The scope field supports filtering based on fully qualified repository URI `${registry-name}/${namespace}/${repository-name}`. - For more information, see [scopes constraints](#scope-constraints) section. - - **`skipSignatureVerification`**(*boolean*): This OPTIONAL property dictates whether Notary v2 should skip signature verification or not. - If set to `true` Notary v2 MUST NOT perform any signature validations including the custom validations performed using plugins. - This is required to support the gradual rollout of signature validation i.e the case when the user application has a mix of signed and unsigned artifacts. - When set to `false`, the following properties MUST be present `trustStores`, `expiryValidations`, `revocationValidations`. - The default value is `false`. - - **`trustStores`**(*array of strings*): This OPTIONAL property specifies a list of names of trust stores that the user trusts. - - **`trustAnchors`**(*array of strings*): This OPTIONAL property specifies a list of elements/attributes of the signing certificate's subject. - If present, the collection MUST contain at least one value. - For more information, see [trust anchors constraints](#trust-anchors-constraints) section. - - **`expiryValidations`**(*object*): This OPTIONAL property represents a collection of artifact expiry-related validations. - - **`signatureExpiry`**(*string*): This REQUIRED property specifies what implementation must do if the signature is expired. - Supported values are `enforce` and `warn`. - - **`signingIdentityExpiry`**(*string*): This REQUIRED property specifies what implementation must do if signing identity(certificate and certificate-chain) is expired. - Supported values are `enforce` and `warn`. - - **`timestampExpiry`**(*string*): This REQUIRED property specifies what implementation must do if timestamping certificate and certificate-chain are expired. - Supported values are `enforce` and `warn`. - - **`revocationValidations`**(*object*): This OPTIONAL property represents collection of artifact revocation related validations. - - **`signingIdentityRevocation`**(*string*): This REQUIRED property specifies whether implementation should check for signing identity(certificate and certificate-chain) revocation status or not and what implementation must do if this revocation check fails. - Supported values are `enforceWithFailOpen`, `enforceWithFailClose`, `warn` and `skip`. - - **`timestampRevocation`**(*string*): This REQUIRED property specifies whether implementation should check for timestamping certificate and certificate-chain revocation status or not and what implementation must do if this revocation check fails. - Supported values are `enforceWithFailOpen`, `enforceWithFailClose`, `warn` and `skip`. - -#### Value descriptions - -- **`enforce`**: This means implementation MUST perform validation and throw an error if validation fails. -- **`enforceWithFailOpen`**: This means implementation MUST perform validation and if validation fails because the endpoint is not reachable, the implementation MUST log an error and MUST NOT fail the validation. -- **`enforceWithFailClose`**: This means implementation MUST perform validation and if validation fails because the endpoint is not reachable, the implementation MUST throw an error and MUST fail the validation. -- **`warn`**: This means implementation MUST perform the validation and if validation fails(because of any reason) the implementation MUST log an error and MUST NOT fail validation. -- **`skip`**: This means implementation MUST NOT perform the validation. - -#### Scopes Constraints + For more information, see [registry scopes constraints](#registry-scopes-constraints) section. + - **`signatureVerification`**(*string*): This REQUIRED property dictates how signature verification is performed. Supported values are `strict`, `permissive`, `audit` and `skip`. Detailed explaination of each value is present [here](#signatureverification-details). A custom level can be defined by referencing a supported level value, and overriding individual validation checks. NOTE: Custom signature verification level will not be supported in `notation` RC1. + - **`trustedIdentities`**(*object*): This REQUIRED property specifies a set of identities that the user trusts. For X.509 PKI, trusted identities is specified with a combination of trust store and trust anchors. + - **`trustStore`**(*string*): This REQUIRED property specifies a named trust store. Uses the format `{trust-store-type}:{named-store}`. Currently supported values for `trust-store-type` are `ca` and `tsa`. **NOTE**: When support for publicly trusted TSA is available, `tsa:publicly-trusted-tsa` is the default value, and implied without explictly specifying it. If a custom TSA is used the format `ca:acme-rockets,tsa:acme-tsa` is supported to specify it. + - **`identities`**(*array of strings*): This REQUIRED property specifies a list of elements/attributes of the signing certificate's subject. For more information, see [identities constraints](#identities-constraints) section. A value `*` is supported if user trusts any identity (signing certificate) issued by the CA(s) in `trustStore`. + +#### Signature Verification details + +Notary v2 defines the following signature verification levels, to provide different levels of enforcement for different scenarios. Signature verification is a multi step process performs the following validation checks - integrity (artifact is unaltered, signature is not corrupted), authenticity (the signature is really from the identity that claims to have signed it), trusted timestamping (the signature was generated when the key/certificate were unexpired), expiry (an optional check if the artifact specifies an expiry time), revocation check (is the signing identity still trusted at the present time). Based on the signature verification level, each of these validation checks is `enforced` or `logged`. If a validation check is `enforced`, a failure is treated as critical failure, and causes the overall signature verification to fail. If a validation check is `logged`, a failure causes details to be logged, and the next validation check is evaluated till all checks succeed or a critical failure is encountered. NOTE: Implementations may change the ordering of these checks based on efficiency, but all validation checks MUST to be performed till the first critical failure is encountered, or all checks succeed, for signature verification process to be considered complete. + +- `strict` : Signature verification is performed at `strict` level, which enforces all of the signature verification checks. If any of these checks fails, the signature verification fails. This is the recommended level in environments where a signature verification failure does not have high impact to other concerns (like application availability). It is recommended that build and development environments where images are initially injested, or at high assurance at deploy time use `strict` level. +- `permissive` : The `permissive` level enforces most signature verification checks, but will only logs failures for revocation and expiry. The `permissive` level is recommended to be used if signature verification is done at deploy time or runtime, and the user only needs integrity and authenticity guarantees. +- `audit` : The `audit` level only enforces integrity check if a signature is present. Failure of all other checks are only logged. +- `skip` : The `skip` level does not fetch signatures for artifacts and does not perform any signature verification. This is useful when an application uses multiple artifacts, and has a mix of signed and unsigned artifacts. Note that `skip` cannot be used with a global scope (`*`), the value of `registryScopes` MUST contain fully qualified registry URL(s). + +The following table shows the resultant behavior `enforced` (verification fails), or `logged` for each of the checks, based on signature verification level. + +|Signature Verification Level|Recommended Usage|Integrity|Authenticity|Trusted timestamp|Expiry|Revocation check| +|----------------------------|-----------------|---------|------------|-----------------|------|----------------| +|strict |Use at development, build and deploy time|enforced|enforced|enforced|enforced|enforced| +|permissive|Use at deploy time or runtime|enforced|enforced|logged|logged|logged| +|audit |Use when adopting signed images, without breaking existing workflows|enforced|logged|logged|logged|logged| +|skip |Use to exclude verification for unsigned images|skipped|skipped|skipped|skipped|skipped| + +**Integrity** : Guarantees that the artifact wasn't altered after it was signed, or the signature isn't corrupted. All signature verification levels always enforce integrity. + +**Authenticity** : Guarantees that the artifact was signed by an identity trusted by the verifier. Its definition does not include revocation, which is when a trusted identity is subsequently untrusted because of a compromise. + +**Trusted timestamp** : Guarantees that the signature was generated when the certificate was valid. It also allows a verifier to determine if a signature be treated as valid when a certificate is revoked, if the certificate was revoked after the signature was generated. In the absence of a trusted timestamp, signatures are considered invalid after certificate expires, and all signatures are considered revoked when a certificate is revoked. **NOTE**: `notation` RC1 will generate trusted timestamp using a TSA when the signature is generated, but will not support verification of TSA countersignatures. + +**Expiry** : This is an optional feature that guarantees that artifact is within “best by use” date indicated in the signature. Notary v2 allows users to include an optional expiry time when they generate a signature. The expiry time is not set by default and requires explicit configuration by users at the time of signature generation. The artifact is considered expired when the current time is greater than or equal to expiry time, users performing verification can either configure their trust policies to fail the verification or even accept the artifact with expiry date in the past using policy. This is an advanced feature that allows implementing controls for user defined semantics like deprecation for older artifacts, or block older artifacts in a production environment. Users should only include an expiry time in the signed artifact after considering the behavior they expect for consumers of the artifact after it expires. Users can choose to consume an artifact even after the expiry time based on their specific needs. + +**Revocation check** : Guarantees that the signing identity is still trusted at signature verification time. Events such as key or system compromise can make a signing identity that was previously trusted, to be subsequently untrusted. This guarantee typically requires a verification-time call to an external system, which may not be consistently reliable. The `permissive` verification level only logs failures of revocation check and does not enforce it. If a particular revocation mechanism is reliable, use `strict` verification level instead. **NOTE** `notation` RC1 will not support revocation check. + +#### Registry Scopes Constraints - Each trust policy MUST contain scope property and the scope collection MUST contain at least one value. - The scope MUST contain one of the following: @@ -202,6 +196,9 @@ The trust policy is represented as JSON data structure as shown below: The scope with `*` value is called global scope. The trust policy with global scope applies to all the artifacts. There can only be one trust policy that uses a global scope. + +#### Selecting a trust policy based on artifact URI + - For a given artifact there MUST be only one applicable trust policy, except for trust policy with global scope. - For a given artifact, if there is no applicable trust policy then Notary v2 MUST consider the artifact as untrusted and fail signature verification. - The scope MUST NOT support reference expansion i.e. URIs must be fully qualified. @@ -212,26 +209,26 @@ The trust policy is represented as JSON data structure as shown below: 1. *Global*: If there exists a trust policy with global scope then use that policy for signature evaluation. Otherwise, fail the signature verification. -### Trust Anchors Constraints +### Identities Constraints -A distinguished name (usually just shortened to "DN") uniquely identifies an entry and in the case of the certificate's subject, DN uniquely identifies the requestor/holder of the certificate. +This section defines how to specify `identities` for X.509 certificates using its distinguished name. A distinguished name (usually just shortened to "DN") uniquely identifies the requestor/holder of the certificate. The DN is comprised of zero or more comma-separated components called relative distinguished names, or RDNs. -For example, the DN `C=US, ST=WA, O=wabbit-network.io, OU=org1`"` has four RDNs. +For example, the DN `C=US, ST=WA, O=wabbit-network.io, OU=org1` has four RDNs. The RDN consists of an attribute type name followed by an equal sign and the string representation of the corresponding attribute value. -- Trust anchor MUST support a full and partial list of all the attribute types present in [subject DN](https://www.rfc-editor.org/rfc/rfc5280.html#section-4.1.2.6) of x509 certificate. +- The `identities` list items MUST support a full and partial list of all the attribute types present in [subject DN](https://www.rfc-editor.org/rfc/rfc5280.html#section-4.1.2.6) of x509 certificate. Alternatively, it supports a single item with value `*`, to indicate that any certificate that chains to the associated trust store (`trustedIdentities.trustStore`) is allowed. - If the subject DN of the signing certificate is used in the trust anchor, then it MUST meet the following requirements: - - The value of `trustAnchors` MUST begin with `subject:` followed by comma-separated one or more RDNs. - For example, `subject: C=${country}, ST=${state}, L=${locallity}, O={organization}, OU=${organization-unit}, CN=${common-name}`. - - Trust anchor MUST contain country (CN), state Or province (ST), and organization (O) RDNs. + - The value of each `identities` list item MUST begin with `x509.subject:` followed by comma-separated one or more RDNs. + For example, `x509.subject: C=${country}, ST=${state}, L=${locallity}, O={organization}, OU=${organization-unit}, CN=${common-name}`. + - Each identity in `identities` list MUST contain country (CN), state Or province (ST), and organization (O) RDNs. All other RDNs are optional. - The minimal possible trust anchor is `subject: C=${country}, ST=${state}, O={organization}`, - - Trust anchor MUST NOT have overlapping values. - Trust anchors are considered overlapping if there exists a certificate for which multiple trust anchors evaluate true. - For example, the following two trust anchors are overlapping: - - `subject: C=US, ST=WA, O=wabbit-network.io, OU=org1` - - `subject: C=US, ST=WA, O=wabbit-network.io` - - In some special cases trust anchor MUST escape one or more characters in an RDN. + The minimal possible value is `x509.subject: C=${country}, ST=${state}, O={organization}`, + - `identities` list items MUST NOT have overlapping values, + they are considered overlapping if there exists a certificate for which multiple DNs evaluate true. In such case the policy is considered invalid, and will fail at signature verification time when the policy is validated. + For example, the following two identity values are overlapping: + - `x509.subject: C=US, ST=WA, O=wabbit-network.io, OU=org1` + - `x509.subject: C=US, ST=WA, O=wabbit-network.io` + - In some special cases values in `identities` list MUST escape one or more characters in an RDN. Those cases are: - If a value starts or ends with a space, then that space character MUST be escaped as `\`. - All occurrences of the comma character (`,`) MUST be escaped as `\,`. @@ -240,76 +237,74 @@ The RDN consists of an attribute type name followed by an equal sign and the str ### Extended Validation -The implementation must allow the user to execute custom validations. -These custom validation MUST have access to all the information available in the signature envelope like payload, signed attributes, unsigned attributes, and signature. +The implementation must allow the user to execute custom validations. These custom validation MUST have access to all the information available in the signature envelope like payload, signed attributes, unsigned attributes, and signature. + +TODO: Update this section after [verification plugin spec](https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#verification-extensibility) is ready. -## Signature Evaluation +## Signature Verification ### Prerequisites - User has configured a valid [trust store](#trust-store) and [trust policy](#trust-policy). +- The artifact's fully qualified repository URI, and associated signature envelope is available. ### Steps -1. **Validate that the signature envelope format is supported.** - 1. Parse the signature envelope content based on the signature envelope type specified in the `[descriptors].descriptor.mediaType` attribute of the signature artifact manifest. - 1. Validate that the content type indicated by the `cty` property value of protected headers in the signature envelope is supported. -1. **Validate the signature envelope integrity.** +1. **Identify applicable trust policy** + 1. For the given artifact URI [determine the applicable trust policy](#selecting-a-trust-policy-based-on-artifact-uri) using `registryScopes`. + 1. If an applicable trust policy for the artifact URI cannot be found, fail signature verification. +1. **Proceed based on signature verification level** + 1. If `signatureVerification` level is set to `skip` in the trust policy, return success. + 1. For all other `signatureVerification` levels, `strict`, `permissive` and `audit`, perform each of the validation defined in the next sections - `integrity`, `authenticity`, `trusted timestamp`, `expiry` and `revocation`. + 1. The `signatureVerification` level defines if each validation is `enforced` or `logged` + 1. `enforced` - validation failures are treated as critical, causes the overall signature verification to fail and exit. Subsequent validations are not processed. + 1. `logged` - validation failure is logged and the next validation step is processed. + 1. A signature verification is considered successful when all validation steps are completed without critical failure. +1. **Validate Integrity.** + 1. Validate that signature envelope can be parsed sucessfully based on the signature envelope type specified in the `blobs[0].mediaType` attribute of the signature artifact manifest. + 1. Validate that the content type indicated by the `content type` signed attribute in the signature envelope is supported. 1. Get the signing certificate from the parsed [signature envelope](https://github.com/notaryproject/notaryproject/blob/7b7d283038/signature-specification.md#signature-envelope). - 1. Get the signing algorithm(hash+encryption) from the signing certificate and validate that the signing algorithm satisfies [algorithm requirements](./signature-specification.md#signature-algorithm-requirements) + 1. Determine the signing algorithm(hash+encryption) from the signing certificate and validate that the signing algorithm satisfies [algorithm requirements](./signature-specification.md#signature-algorithm-requirements) 1. Using the public key of the signing certificate and signing algorithm identified in the previous step, validate the integrity of the signature envelope. -1. **Validate the signature against trust policy and trust store.** - 1. Using the `scope` configured in trust policies, get the applicable trust policy. - (Implementations might have this value precomputed, added it for completeness) - 1. For the applicable trust policy, **validate trust-store:** - 1. Validate that the signature envelope contains a complete certificate chain that starts from a code signing certificate and terminates with the root certificate. - Also, validate that code signing certificate satisfies [certificate requirements](./signature-specification.md#certificate-requirements). - 1. For each of the trust-stores configured in applicable trust-policy perform the following steps. - 1. Validate that certificate and certificate-chain lead to a trusted certificate configured in the `x509Certs` field of trust-store. - 1. If the above verification succeeds then continue to the next step else iterate over the next trust store. - If all of the trust stores have been evaluated then fail the signature validation and exit. - 1. For the applicable trust policy, **validate trust anchors** (if present): - 1. If trust anchors are present, validate that the value of subject attributes configured in `trustAnchors` matches with the value of corresponding attributes in the signing certificate’s subject. - If trust anchors are not present continue to step 4. - 1. If the above verification succeeds then continue to the next step. - Otherwise, fail the signature validation and exit. - 1. **Validate trust policy:** - 1. If signature expiry is present in the signature envelope, using the local machine’s current time(in UTC) check whether the signature is expired or not. - If the signature is not expired, continue to the next step. - Otherwise, if `signatureExpiry` is set to `Enforce` then fail the signature validation and exit else log a warning and continue to the next step. - 1. Check for the timestamp signature in the signature envelope. - 1. If the timestamp exists, continue with the next step. - Otherwise, store the local machine's current time(in UTC) in variables `timeStampLowerLimit` and `timeStampUpperLimit` and continue with step 3.3.c. - 1. Validate that the timestamp hash in `TSTInfo.messageImprint` matches the hash of the signature to which the timestamp was applied. - 1. Validate that the timestamp signing certificate satisfies [certificate requirements](./signature-specification.md#certificate-requirements). - 1. Validate that the timestamp signing algorithm satisfies [algorithm requirements](./signature-specification.md#signature-algorithm-requirements). - 1. Validate the `signing-certificate`([RFC-2634](https://tools.ietf.org/html/rfc2634)) or `signing-certificate-v2`([RFC-5126](https://tools.ietf.org/html/rfc5126#section-5.7.3.2)) attribute of timestamp CMS. - 1. Check whether timestamping certificate and certificate chain are valid (not expired) or not. - If timestamping certificate and certificate-chain are not expired, continue to the next step. - Otherwise, if `timestampExpiry` in trust-policy is configured to `Enforce` then fail the signature validation and exit, else log a warning and continue to the next step. - 1. Validate that timestamp certificate and certificate chain leads to a trusted TSA certificate configured in trust policy. - 1. Validate timestamp certificate and certificate chain revocation status using [certificate revocation evaluation](#certificate-revocation-evaluation) section as per `timestampRevocation` setting in trust-policy - 1. Retrieve the timestamp's time from `TSTInfo.genTime`. - 1. Retrieve the timestamp's accuracy. - If the accuracy is explicitly specified in `TSTInfo.accuracy`, use that value. - If the accuracy is not explicitly specified and `TSTInfo.policy` is the baseline time-stamp policy([RFC-3628](https://tools.ietf.org/html/rfc3628#section-5.2)), use accuracy of 1 second. - Otherwise, use an accuracy of 0. - 1. Calculate the timestamp range using the lower and upper limits per [RFC-3161 section 2.4.2](https://tools.ietf.org/html/rfc3161#section-2.4.2) and store the limits as `timeStampLowerLimit` and `timeStampUpperLimit` variables respectively. - 1. Check that the time range from `timeStampLowerLimit` to `timeStampUpperLimit` timestamp is entirely within the certificate's validity period. - If the time range is entirely within the signing certificate and certificate chain's validity period, continue to the next step. - Otherwise, If `signingIdentityExpiry` in trust-policy is configured to `Enforce` then fail the signature validation and exit else log a warning and continue to the next step. - 1. Validate signing identity(certificate and certificate chain) revocation status using [certificate revocation evaluation](#certificate-revocation-evaluation) section as per `signingIdentityRevocation` setting in trust-policy. - 1. Perform extended validation using the applicable(if any) plugin. - 1. If you have reached this step then treat the OCI artifact signature as a valid signature. +1. **Validate Authenticity.** + 1. For the applicable trust policy, **validate trusted identities:** + 1. Validate that the signature envelope contains a complete certificate chain that starts from a code signing certificate and terminates with a root certificate. Also, validate that code signing certificate satisfies [certificate requirements](./signature-specification.md#certificate-requirements). + 1. For the `trustedIdentities.trustStore` configured in applicable trust-policy perform the following steps. + 1. Validate that certificate and certificate-chain lead to a trusted certificate present in the named store. + 1. If all the certificates in the named store have been evaluated without a match, then fail this step. + 1. If `trustedIdentities.Identities` is `*`, any signing certificate issued by a CA in `trustStore` is allowed, skip to the next validation (Validate Expiry). + 1. Else validate if the X.509 subject (`x509.subject`) in the `trustedIdentities` list matches with the value of corresponding attributes in the signing certificate’s subject, refer [this section](#trusted-identities-constraints) for details. If a match is not found, fail this step. +1. **Validate Expiry:** + 1. If an `expiry time` signed attribute is present in the signature envelope, check if the local machine’s current time(in UTC) is greater than `expiry time`. If yes, fail this step. +1. **Validate Trusted Timestamp:** + 1. Check for the timestamp signature in the signature envelope. + 1. If the timestamp exists, continue with the next step. + Otherwise, store the local machine's current time(in UTC) in variables `timeStampLowerLimit` and `timeStampUpperLimit` and continue with step 6.2. + 1. Validate that the timestamp hash in `TSTInfo.messageImprint` matches the hash of the signature to which the timestamp was applied. + 1. Validate that the timestamp signing certificate satisfies [certificate requirements](./signature-specification.md#certificate-requirements). + 1. Validate that the timestamp signing algorithm satisfies [algorithm requirements](./signature-specification.md#signature-algorithm-requirements). + 1. Validate the `signing-certificate`([RFC-2634](https://tools.ietf.org/html/rfc2634)) or `signing-certificate-v2`([RFC-5126](https://tools.ietf.org/html/rfc5126#section-5.7.3.2)) attribute of timestamp CMS. + 1. Validate that timestamp certificate and certificate chain leads to a trusted TSA certificate as per value configured in `trustStore`. + 1. Validate timestamp certificate and certificate chain revocation status using [certificate revocation evaluation](#certificate-revocation-evaluation) section. + 1. Retrieve the timestamp's time from `TSTInfo.genTime`. + 1. Retrieve the timestamp's accuracy. + If the accuracy is explicitly specified in `TSTInfo.accuracy`, use that value. + If the accuracy is not explicitly specified and `TSTInfo.policy` is the baseline time-stamp policy([RFC-3628](https://tools.ietf.org/html/rfc3628#section-5.2)), use accuracy of 1 second. + Otherwise, use an accuracy of 0. + 1. Calculate the timestamp range using the lower and upper limits per [RFC-3161 section 2.4.2](https://tools.ietf.org/html/rfc3161#section-2.4.2) and store the limits as `timeStampLowerLimit` and `timeStampUpperLimit` variables respectively. + 1. Check that the time range from `timeStampLowerLimit` to `timeStampUpperLimit` timestamp is entirely within the certificate's validity period.If the time range is entirely within the signing certificate and certificate chain's validity period, continue to the next step. Else fail this step. +1. **Validate Revocation Status:** + 1. Validate signing identity(certificate and certificate chain) revocation status using [certificate revocation evaluation](#certificate-revocation-evaluation) section as per `signingIdentityRevocation` setting in trust-policy. +1. Perform extended validation using the applicable (if any) plugin. +1. If all the steps are completed without critical failures then the signatures is successfully verified. ### Certificate Revocation Evaluation -If the certificate revocation trust-store setting is set to `skip`, skip the below steps. -otherwise, check for revocation status for certificate and certificate chain. +This section should be considered as **DRAFT** and will be updated after RC1 release. + +If the certificate revocation validation is set to `skip` using policy, skip the below steps, otherwise check for revocation status for certificate and certificate chain. -1. If the revocation status of any of the certificates cannot be determined (revocation unavailable) and `signingIdentityRevocation` is set to either `enforceWithFailOpen` or `warn` then log a warning and skip the below steps. - Otherwise, fail the signature validation and exit. -1. If any of the certificates are revoked and `signingIdentityRevocation` is set to either `enforceWithFailOpen` or `enforceWithFailClose` then fail signature validation and exit else log a warning. +- If the revocation status of any of the certificates is revoked or cannot be determined (revocation status unavailable), fail this step. This will fail the overall signature verification or log the error, depending on if revocations status check is `enforced` or `logged`, as per the signature verification level. Starting from Root to leaf certificate, for each certificate in the certificate chain, perform the following steps to check its revocation status: @@ -403,7 +398,8 @@ Signature verification workflow succeeds if verification succeeds for at least o **Q: Does Notary v2 support overriding of revocation endpoints to support signature verification in disconnected environments?** -**A:** Not natively supported but a user can configure `revocationValidations` to `skip` and then use extended validations to check for revocation. +**A:** TODO: Update after verification extensibility spec is ready. +Not natively supported but a user can configure `revocationValidations` to `skip` and then use extended validations to check for revocation. **Q: Why user needs to include a complete certificate chain (leading to root) in the signature?** From c2af0890a385f5f0f7d3472f807be7c243782f6d Mon Sep 17 00:00:00 2001 From: Milind Gokarn Date: Mon, 2 May 2022 17:34:27 -0700 Subject: [PATCH 05/17] Simplify trustedIdentities in trust policy. (#154) Signed-off-by: Milind Gokarn --- trust-store-trust-policy-specification.md | 81 +++++++++++------------ 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/trust-store-trust-policy-specification.md b/trust-store-trust-policy-specification.md index aa5df66f..b71c0ebc 100644 --- a/trust-store-trust-policy-specification.md +++ b/trust-store-trust-policy-specification.md @@ -67,16 +67,14 @@ Trust policy for a simple scenario where ACME Rockets uses only artifacts signed "version": "1.0", "trustPolicies": [ { - # Policy for all artifacts, from any registry location. + // Policy for all artifacts, from any registry location. "name": "wabbit-networks-images", "registryScopes": [ "*" ], "signatureVerification" : "audit", - "trustedIdentities": { - "trustStore": "ca:acme-rockets", - "identities": [ - "x509.subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io, OU=Finance, CN=SecureBuilder" - ] - } + "trustStore": "ca:acme-rockets", + "trustedIdentities": [ + "x509.subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io, OU=Finance, CN=SecureBuilder" + ] } ] } @@ -89,29 +87,28 @@ Trust policy for the scenario where ACME Rockets uses artifacts signed by themse "version": "1.0", "trustPolicies": [ { - # Policy for set of artifacts signed by Wabbit Networks - # that are pulled from ACME Rockets repository + // Policy for set of artifacts signed by Wabbit Networks + // that are pulled from ACME Rockets repository "name": "wabbit-networks-images", "registryScopes": [ - "registry.acme-rockets.io/software/net-monitor" - "registry.acme-rockets.io/software/net-logger" ], + "registry.acme-rockets.io/software/net-monitor", + "registry.acme-rockets.io/software/net-logger" + ], "signatureVerification" : "strict", - "trustedIdentities": { - "trustStore": "ca:acme-rockets", - "identities": [ - "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Security Tools" - ] - } + "trustStore": "ca:acme-rockets", + "trustedIdentities": [ + "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Security Tools" + ] }, { - # Exception policy for a single unsigned artifact pulled from - # Wabbit Networks repository + // Exception policy for a single unsigned artifact pulled from + // Wabbit Networks repository "name": "unsigned-image", "registryScopes": [ "registry.wabbit-networks.io/software/unsigned/net-utils" ], "signatureVerification": "skip", }, { - # Policy for with custom verification policy + // Policy for with custom verification policy "name": "use-expired-image", "registryScopes": [ "registry.acme-rockets.io/software/legacy/metrics" ], "signatureVerification": @@ -122,23 +119,19 @@ Trust policy for the scenario where ACME Rockets uses artifacts signed by themse "expiry" : "skip" } }, - "trustedIdentities": { - "trustStore": "ca:acme-rockets", - "identities": ["*"] - } + "trustStore": "ca:acme-rockets", + "trustedIdentities": ["*"] }, { - # Policy for all other artifacts signed by ACME Rockets - # from any registry location + // Policy for all other artifacts signed by ACME Rockets + // from any registry location "name": "global-policy-for-all-other-images", "registryScopes": [ "*" ], "signatureVerification" : "audit", - "trustedIdentities": { - "trustStore": "ca:acme-rockets", - "identities": [ - "x509.subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io, OU=Finance, CN=SecureBuilder" - ] - } + "trustStore": "ca:acme-rockets", + "trustedIdentities": [ + "x509.subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io, OU=Finance, CN=SecureBuilder" + ] } ] } @@ -154,9 +147,8 @@ Trust policy for the scenario where ACME Rockets uses artifacts signed by themse The scope field supports filtering based on fully qualified repository URI `${registry-name}/${namespace}/${repository-name}`. For more information, see [registry scopes constraints](#registry-scopes-constraints) section. - **`signatureVerification`**(*string*): This REQUIRED property dictates how signature verification is performed. Supported values are `strict`, `permissive`, `audit` and `skip`. Detailed explaination of each value is present [here](#signatureverification-details). A custom level can be defined by referencing a supported level value, and overriding individual validation checks. NOTE: Custom signature verification level will not be supported in `notation` RC1. - - **`trustedIdentities`**(*object*): This REQUIRED property specifies a set of identities that the user trusts. For X.509 PKI, trusted identities is specified with a combination of trust store and trust anchors. - - **`trustStore`**(*string*): This REQUIRED property specifies a named trust store. Uses the format `{trust-store-type}:{named-store}`. Currently supported values for `trust-store-type` are `ca` and `tsa`. **NOTE**: When support for publicly trusted TSA is available, `tsa:publicly-trusted-tsa` is the default value, and implied without explictly specifying it. If a custom TSA is used the format `ca:acme-rockets,tsa:acme-tsa` is supported to specify it. - - **`identities`**(*array of strings*): This REQUIRED property specifies a list of elements/attributes of the signing certificate's subject. For more information, see [identities constraints](#identities-constraints) section. A value `*` is supported if user trusts any identity (signing certificate) issued by the CA(s) in `trustStore`. + - **`trustStore`**(*string*): This REQUIRED property specifies a named trust store. Uses the format `{trust-store-type}:{named-store}`. Currently supported values for `trust-store-type` are `ca` and `tsa`. **NOTE**: When support for publicly trusted TSA is available, `tsa:publicly-trusted-tsa` is the default value, and implied without explictly specifying it. If a custom TSA is used the format `ca:acme-rockets,tsa:acme-tsa` is supported to specify it. + - **`trustedIdentities`**(*array of strings*): This REQUIRED property specifies a set of identities that the user trusts. For X.509 PKI, it supports list of elements/attributes of the signing certificate's subject. For more information, see [identities constraints](#trusted-identities-constraints) section. A value `*` is supported if user trusts any identity (signing certificate) issued by the CA(s) in `trustStore`. #### Signature Verification details @@ -209,26 +201,27 @@ The following table shows the resultant behavior `enforced` (verification fails) 1. *Global*: If there exists a trust policy with global scope then use that policy for signature evaluation. Otherwise, fail the signature verification. -### Identities Constraints +### Trusted Identities Constraints -This section defines how to specify `identities` for X.509 certificates using its distinguished name. A distinguished name (usually just shortened to "DN") uniquely identifies the requestor/holder of the certificate. +This section defines how to specify `trustedIdentities` for X.509 certificates using its distinguished name. A distinguished name (usually just shortened to "DN") uniquely identifies the requestor/holder of the certificate. The DN is comprised of zero or more comma-separated components called relative distinguished names, or RDNs. For example, the DN `C=US, ST=WA, O=wabbit-network.io, OU=org1` has four RDNs. The RDN consists of an attribute type name followed by an equal sign and the string representation of the corresponding attribute value. -- The `identities` list items MUST support a full and partial list of all the attribute types present in [subject DN](https://www.rfc-editor.org/rfc/rfc5280.html#section-4.1.2.6) of x509 certificate. Alternatively, it supports a single item with value `*`, to indicate that any certificate that chains to the associated trust store (`trustedIdentities.trustStore`) is allowed. +- The `trustedIdentities` list items MUST support a full and partial list of all the attribute types present in [subject DN](https://www.rfc-editor.org/rfc/rfc5280.html#section-4.1.2.6) of x509 certificate. Alternatively, it supports a single item with value `*`, to indicate that any certificate that chains to the associated trust store (`trustStore`) is allowed. - If the subject DN of the signing certificate is used in the trust anchor, then it MUST meet the following requirements: - - The value of each `identities` list item MUST begin with `x509.subject:` followed by comma-separated one or more RDNs. + - The value of each `trustedIdentities` list item, if it begins with `x509.subject:`, MUST be followed by comma-separated one or more RDNs. + Other types of trusted identities may be supported, by using an alternate prefix, or a different format. For example, `x509.subject: C=${country}, ST=${state}, L=${locallity}, O={organization}, OU=${organization-unit}, CN=${common-name}`. - Each identity in `identities` list MUST contain country (CN), state Or province (ST), and organization (O) RDNs. All other RDNs are optional. The minimal possible value is `x509.subject: C=${country}, ST=${state}, O={organization}`, - - `identities` list items MUST NOT have overlapping values, + - `trustedIdentities` list items MUST NOT have overlapping values, they are considered overlapping if there exists a certificate for which multiple DNs evaluate true. In such case the policy is considered invalid, and will fail at signature verification time when the policy is validated. For example, the following two identity values are overlapping: - `x509.subject: C=US, ST=WA, O=wabbit-network.io, OU=org1` - `x509.subject: C=US, ST=WA, O=wabbit-network.io` - - In some special cases values in `identities` list MUST escape one or more characters in an RDN. + - In some special cases values in `trustedIdentities` list MUST escape one or more characters in an RDN. Those cases are: - If a value starts or ends with a space, then that space character MUST be escaped as `\`. - All occurrences of the comma character (`,`) MUST be escaped as `\,`. @@ -267,12 +260,12 @@ TODO: Update this section after [verification plugin spec](https://github.com/no 1. Determine the signing algorithm(hash+encryption) from the signing certificate and validate that the signing algorithm satisfies [algorithm requirements](./signature-specification.md#signature-algorithm-requirements) 1. Using the public key of the signing certificate and signing algorithm identified in the previous step, validate the integrity of the signature envelope. 1. **Validate Authenticity.** - 1. For the applicable trust policy, **validate trusted identities:** + 1. For the applicable trust policy, **validate trust store and identities:** 1. Validate that the signature envelope contains a complete certificate chain that starts from a code signing certificate and terminates with a root certificate. Also, validate that code signing certificate satisfies [certificate requirements](./signature-specification.md#certificate-requirements). - 1. For the `trustedIdentities.trustStore` configured in applicable trust-policy perform the following steps. + 1. For the `trustStore` configured in applicable trust-policy perform the following steps. 1. Validate that certificate and certificate-chain lead to a trusted certificate present in the named store. 1. If all the certificates in the named store have been evaluated without a match, then fail this step. - 1. If `trustedIdentities.Identities` is `*`, any signing certificate issued by a CA in `trustStore` is allowed, skip to the next validation (Validate Expiry). + 1. If `trustedIdentities` is `*`, any signing certificate issued by a CA in `trustStore` is allowed, skip to the next validation (Validate Expiry). 1. Else validate if the X.509 subject (`x509.subject`) in the `trustedIdentities` list matches with the value of corresponding attributes in the signing certificate’s subject, refer [this section](#trusted-identities-constraints) for details. If a match is not found, fail this step. 1. **Validate Expiry:** 1. If an `expiry time` signed attribute is present in the signature envelope, check if the local machine’s current time(in UTC) is greater than `expiry time`. If yes, fail this step. From cf6113ce44c25f89de67d62feebba67e2be7505b Mon Sep 17 00:00:00 2001 From: Milind Gokarn Date: Mon, 16 May 2022 12:52:04 -0700 Subject: [PATCH 06/17] Added describe-key command and pass-through plugin config (#155) * Added describe-key command and pass-through plugin config Signed-off-by: Milind Gokarn * `generate-signature` request now specifies hash algorithm to use. Signed-off-by: Milind Gokarn * Removed `keyName` from plugin requests. Signed-off-by: Milind Gokarn * Update `pluginConfig` CLI argument Signed-off-by: Milind Gokarn * In `generate-signature.request.payload`, `payload` is always base64 encoded. Signed-off-by: Milind Gokarn --- signature-specification.md | 26 ++--- specs/plugin-extensibility.md | 191 +++++++++++++++++++++++++--------- 2 files changed, 157 insertions(+), 60 deletions(-) diff --git a/signature-specification.md b/signature-specification.md index cd90df4e..a378b712 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -225,18 +225,20 @@ Since Notary v2 restricts one signature per signature envelope, the compliant si **Implementation Constraints**: Notary v2 implementation MUST enforce the following constraints on signature generation and verification: 1. `alg` header value MUST NOT be `none` or any symmetric-key algorithm such as `HMAC`. -1. `alg` header value MUST be same as that of signature algorithm identified using signing certificate's public key algorithm and size. -1. `alg` header values for various signature algorithms: - | Signature Algorithm | `alg` Param Value | - | ------------------------------- | ----------------- | - | RSASSA-PSS with SHA-256 | PS256 | - | RSASSA-PSS with SHA-384 | PS384 | - | RSASSA-PSS with SHA-512 | PS512 | - | ECDSA on secp256r1 with SHA-256 | ES256 | - | ECDSA on secp384r1 with SHA-384 | ES384 | - | ECDSA on secp521r1 with SHA-512 | ES512 | -1. Signing certificate MUST be a valid codesigning certificate. -1. Only JWS JSON flattened format is supported. +2. `alg` header value MUST be same as that of signature algorithm identified using signing certificate's public key algorithm and size. +3. `alg` header values for various signature algorithms: + +| Signature Algorithm | `alg` Param Value | +| ------------------------------- | ----------------- | +| RSASSA-PSS with SHA-256 | PS256 | +| RSASSA-PSS with SHA-384 | PS384 | +| RSASSA-PSS with SHA-512 | PS512 | +| ECDSA on secp256r1 with SHA-256 | ES256 | +| ECDSA on secp384r1 with SHA-384 | ES384 | +| ECDSA on secp521r1 with SHA-512 | ES512 | + +4. Signing certificate MUST be a valid codesigning certificate. +5. Only JWS JSON flattened format is supported. See 'Signature Envelope' section. ## Signature Algorithm Requirements diff --git a/specs/plugin-extensibility.md b/specs/plugin-extensibility.md index af3bbd33..e378980e 100644 --- a/specs/plugin-extensibility.md +++ b/specs/plugin-extensibility.md @@ -32,13 +32,13 @@ Plugin publisher will provide instructions to download and install the plugin. P To enumerate all available plugins the following paths are scanned: * Unix-like OSes: - * `$HOME/.notation/plugins` + * `$HOME/notation/plugins` * On Windows: - * `%USERPROFILE%\.notation\plugins` + * `%USERPROFILE%\notation\plugins` -Each plugin executable and dependencies are installed under directory `~/.notation/plugins/{plugin-name}` with an executable under that directory `~/.notation/plugins/{plugin-name}/notation-{plugin-name}`. +Each plugin executable and dependencies are installed under directory `~/notation/plugins/{plugin-name}` with an executable under that directory `~/notation/plugins/{plugin-name}/notation-{plugin-name}`. -Any directory found inside `~/.notation/plugins` is considered potential plugin "candidates". Anything found which is not a directory is ignored and is not considered as a plugin candidate. +Any directory found inside `~/notation/plugins` is considered potential plugin "candidates". Anything found which is not a directory is ignored and is not considered as a plugin candidate. To be considered a valid plugin a candidate must pass each of these "plugin candidate tests": @@ -56,33 +56,53 @@ To be considered a valid plugin a candidate must pass each of these "plugin cand List all valid plugins. -### Plugin configuration +### Using a plugin for signing * To use a plugin for signing, the user associates the plugin as part of registering a signing key. E.g. * `notation key add --name "mysigningkey" --id "keyid" --plugin "com.example.nv2plugin"` - * In the example, the command registers a signing key in `/notation/config.json`, where `mysigningkey` is a friendly key name to refer during signing operation from the CLI, `id` is an key identifier known to plugin that is used for signing, and the value of `plugin` specifies it's using a plugin located at `~/.notation/plugins/com.example.nv2plugin/notation-com.example.nv2plugin`. + * In the example, the command registers a signing key in `/notation/config.json`, where `mysigningkey` is a friendly key name to refer during signing operation from the CLI, `id` is an key identifier known to plugin that is used for signing, and the value of `plugin` specifies it's using a plugin located at `~/notation/plugins/com.example.nv2plugin/notation-com.example.nv2plugin`. ```jsonc { "signingKeys": { "default": "", "keys": [ - { - "name": "mysigningkey", - "id" : "keyid", - "plugin": "com.example.nv2plugin" - } + { + "name": "mysigningkey", + "id" : "keyid", + "plugin": "com.example.nv2plugin", + // Optional configuration as required by the plugin + "pluginConfig" : { + "key1" : "value1", + "key 2" : "value 2" + } + } ] } } ``` +### Plugin configuration + +Plugins may require additional configuration to work correctly, that could be set out of band, or provided by notation when it invokes a plugin. To support plugin configuration through notation, the key configuration provides an optional `pluginConfig` map of of key value pairs, that is passed as-is by notation to the plugin. + +* To use this feature, plugin authors MUST define and document the set of plugin configuration keys-values, which is set by users when they associate a plugin with signing key. +* Plugin authors SHOULD NOT use plugin configuration to store sensitive configuration in plaintext, such as authentication keys used by the plugin etc. + +For the previous example, plugin config can be set using command line arguments as part of registering a key with the `notation key add` command. E.g. + +* `notation key add --name "mysigningkey" --id "keyid" --plugin "com.example.nv2plugin" --pluginConfig key1=value1,"key 2"="value 2"` + +Plugin config can be also set/overriden during signing with the `notation sign` command. Following example overrides value for `key 2` already set in `config.json` by previous command. + +* `notation sign $IMAGE --key "mysigningkey" --pluginConfig "key 2"=newValue2` + ### Plugin contract * Notation will invoke the plugin executable for each command (e.g. sign, verify), pass inputs through `stdin` and get output through `stdout` and `stderr`. * The command will be passed as the first argument to the plugin e.g. `notary-{plugin-name} `. A JSON request is passed using `stdin`. The plugin is expected to return a JSON response through `stdout` with a `0` exit code for successful response, and a non-zero exit code with a JSON error response in `stderr` for error response. Each command defines its request, response and error contract. To avoid any additional content like debug or info level logging from dependencies and inbuilt libraries, the plugin implementation should redirect any output to `stdout` on initialization, and only send the JSON response away from `stdout` when the command execution completes. E.g. For golang, set [`os.Stdout`](https://pkg.go.dev/os#pkg-variables) to point to a log file. * Every request JSON will contain a `contractVersion` top level attribute whose value will indicate the plugin contract version. Contract version is revised when there are changes to command request/response, new plugin commands are introduced, and supported through Notation. -* For an error response, every command returns a non-zero exit code 1, with an OPTIONAL JSON error response in `stderr`. It is recommended to return an error response to help user troubleshoot the error. There is no need to send different exit codes for different error conditions, as Notation (which will call the plugin and parse the response) will use `error-code` in the error response to interpret different error conditions if needed. Notation will attempt to parse the error response in `stderr` when exit code is 1, else treat it as a general error for any other non-zero exit codes. An implementation can +* For an error response, every command returns a non-zero exit code 1, with an OPTIONAL JSON error response in `stderr`. It is recommended to return an error response to help user troubleshoot the error. There is no need to send different exit codes for different error conditions, as Notation (which will call the plugin and parse the response) will use `error-code` in the error response to interpret different error conditions if needed. Notation will attempt to parse the error response in `stderr` when exit code is 1, else treat it as a general error for any other non-zero exit codes. ```jsonc { @@ -101,7 +121,7 @@ List all valid plugins. ### Plugin metadata -* Every plugin MUST implement a metadata discovery command called `get-plugin-metadata`. +* Every plugin MUST implement a metadata discovery command called `get-plugin-metadata`. * Notation will invoke this command as part of signing and verification workflows to discover the capabilities of the plugin and subsequently invoke commands corresponding to the capabilities. Notation will invoke the `get-plugin-metadata` command every time a signing or verification workflow is invoked to discover metadata. Notation will not cache the response of `get-plugin-metadata` command as it involves invalidating cache when a plugin is updated, detecting a plugin update can be non trivial. The `get-plugin-metadata` command is expected to return only static data, and plugin implementors should avoid making remote calls in this command. ***get-plugin-metadata*** @@ -155,24 +175,33 @@ This interface targets plugins that integrate with providers of basic cryptograp #### Signing workflow using plugin -1. Given a user request to sign `oci-artifact`, with `keyName` (the friendly key name) -1. Pull the image manifest using `oci-artifact` url, and construct a descriptor -1. Append any user provided metadata and Notary metadata as descriptor annotations. -1. Determine if the registered key uses a plugin -1. Execute the plugin with `get-plugin-metadata` command +1. Given a user request to sign `oci-artifact`, with signing key `keyName` (the friendly key name) +2. Pull the image manifest using `oci-artifact` url, and construct a descriptor +3. Append any user provided metadata and Notary metadata as descriptor annotations. +4. Determine if the registered key uses a plugin +5. Execute the plugin with `get-plugin-metadata` command 1. If plugin supports capability `SIGNATURE_GENERATOR` - 1. Generate the payload to be signed. For JWS this includes [JWS](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#supported-signature-envelopes) payload with `subject` claim as descriptor, additional JWS claims, and protected headers. - 2. Execute the plugin with `generate-signature` command, set `request.keyDefinition` to key definition corresponding to `keyName`, `request.payload` to the generated payload. - 3. Validate the generated signature, return an error if any of the checks fails. + 1. Execute the plugin with `describe-key` command, set `request.keyId` and the optional `request.pluginConfig` to corresponding values associated with signing key `keyName` in `config.json`. + 2. Generate the payload to be signed for [JWS](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#supported-signature-envelopes) envelope format. + 1. Create the JWS protected headers collection and set `alg` to value corresponding to `describe-key.response.keySpec` as per [signature algorithm selection](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection). + 2. Create the `JWSPayload` with appropriate private (`subject`) and public (`iat,exp`) claims. + 3. The *payload to sign* is then created as - `ASCII(BASE64URL(UTF8(ProtectedHeaders)) ‘.’ BASE64URL(JWSPayload))` + 3. Execute the plugin with `generate-signature` command. + 1. Set `request.keyId` and the optional `request.pluginConfig` to corresponding values associated with signing key `keyName` in `config.json`. + 2. Set `request.payload` as base64 encoded *payload to sign* (the JWS *payload to sign* is double encoded, this is a shortcoming of using plugin contract with JSON encoding). + 3. Set `keySpec` to value returned by `describe-key` command in `response.keySpec`, and `hashAlgorithm` to hash algorithm corresponding to the key spec, as per [signature algorithm selection](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection). The algorithm specified in `hashAlgorithm` MUST be used by the plugin to hash the payload (`request.payload`) as part of signature generation. + 4. Validate the generated signature, return an error if any of the checks fails. 1. Check if `response.signingAlgorithm` is one of [supported signing algorithms](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection). - 2. Check that the plugin did not modify `request.payload` before generating the signature, and the signature is valid for the given payload. Verify the hash of the `request.payload` against `response.signature`, using the public key of signing certificate (leaf certificate) in `response.certificateChain` along with the `response.signingAlgorithm`. This step does not include certificate chain validation (certificate chain leads to a trusted root configured in Notation), or revocation check. + 2. Check that the plugin did not modify `request.payload` before generating the signature, and the signature is valid for the given payload. Verify the hash of the `request.payload` against `response.signature`, using the public key of signing certificate (leaf certificate) in `response.certificateChain` along with the `response.signingAlgorithm`. This step does not include certificate chain validation (certificate chain leads to a trusted root configured in Notation's Trust Store), or revocation check. 3. Check that the `response.certificateChain` conforms to [Certificate Requirements](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#certificate-requirements). - 4. Assemble the JWS Signature envelope using `response.signature`, `response.signingAlgorithm` and `response.certificateChain`. Notation may also generate and include timestamp signature in this step. - 5. Generate a signature manifest for the given signature envelope. - 2. Else if plugin supports capability `SIGNATURE_ENVELOPE_GENERATOR` *(covered in next section)* + 5. Assemble the JWS Signature envelope using `response.signature`, `response.signingAlgorithm` and `response.certificateChain`. Notation may also generate and include timestamp signature in this step. + 6. Generate a signature manifest for the given signature envelope. + 2. Else if, plugin supports capability `SIGNATURE_ENVELOPE_GENERATOR` *(covered in next section)* 3. Return an error -***generate-signature*** +#### describe-key + +This command is used to get metadata for a given key. *Request* @@ -180,28 +209,80 @@ This interface targets plugins that integrate with providers of basic cryptograp { "contractVersion" : "", - // Complete key definition from - // /notation/config.json /signingKeys/keys with matching key name - "keyDefinition" : "", + // Key id associated with signing key (keyName) + // in config.json /signingKeys/keys + "keyId": "", - "payload" : "" + // Optional plugin configuration, map of string-string + "pluginConfig" : { } } ``` -*keyDefinition* : Required field that contains the complete key definition, which includes friendly name (`name`), key identifier (`id`) and any additional custom fields required by the plugin. -For example, after registering a key `mysigningkey` and plugin, the command `notation sign $IMAGE --key "mysigningkey"` will subsequently invoke plugin with `request.keyDefinition` set to following value. +*keyId* : Required field that has the key identifier (`keyId`) associated with signing key `keyName` in `config.json`. + +*pluginConfig* : Optional field for plugin configuration. For details, see [Plugin Configuration](#plugin-configuration) section. + +*Response* ```jsonc -{ - "name": "mysigningkey", - "id" : "keyid", - "plugin": "com.example.nv2plugin" +{ + // The same key id as passed in the request. + "keyId" : "", + "keySpec" : "" } ``` -*payload* : Required field that contains payload to be signed. This is currently the JWSPayload that includes the subject descriptor and other JWS claims. +*keySpec* : One of following [supported key types](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection) - `RSA_2048`, `RSA_3072`, `RSA_4096`, `EC_256`, `EC_384`, `EC_512`. + +NOTE: This command can also be used as part of `notation key describe {key-name}` which will include the following output + +* Summary of key definition from `config.json` +* If the key has an associated plugin + * Output of plugin `discover` command + * Output of `describe-key` command for the specific key + +#### generate-signature + +This command is used to generate the raw signature for a given payload. + +*Request* + +```jsonc +{ + "contractVersion" : "", + + // Key id associated with signing key (keyName) + // in config.json /signingKeys/keys + "keyId": "", + + // Optional plugin configuration, map of string-string + "pluginConfig" : { }, + + // The key spec for the given key id + "keySpec" : "", + + // Hash algorithm associated with the key spec, plugin must + // hash the payload using this hash algorithm + "hashAlgorithm" : "SHA_256" | "SHA_384" | "SHA_512", + + // Payload to sign, this is base64 encoded + "payload" : "", + +} +``` + +*keyId* : Required field that has the key identifier (`keyId`) associated with signing key `keyName` in `config.json`. + +*pluginConfig* : Optional field for plugin configuration. For details, see [Plugin Configuration section](#plugin-configuration). + +*keySpec* : Required field that has one of following [supported key types](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection) - `RSA_2048`, `RSA_3072`, `RSA_4096`, `EC_256`, `EC_384`, `EC_512`. Specifies the key type and size for the key. + +*hashAlgorithm* : Required field that specifies Hash algorithm corresponding to the signature algorithm determined by `keySpec` for the key. + +*payload* : Required field that contains base64 encoded payload to be signed. For JWS, the *payload to sign* is base64 encoded (a second time) before sending to the plugin. *Response* + All response attributes are required. ```jsonc @@ -218,7 +299,7 @@ All response attributes are required. *certificateChain* : Ordered list of certificates starting with leaf certificate and ending with root certificate. -*Error codes* +#### Error codes for describe-key and generate-signature * VALIDATION_ERROR - Any of the required request fields was empty, or a value was malformed/invalid. Includes condition where the key referenced by `keyId` was not found. * UNSUPPORTED_CONTRACT_VERSION - The contract version used in the request is unsupported. @@ -239,8 +320,8 @@ This interface targets plugins that in addition to signature generation want to 1. Determine if the registered key uses a plugin 1. Execute the plugin with `get-plugin-metadata` command 1. If plugin supports capability `SIGNATURE_ENVELOPE_GENERATOR` - 1. Execute the plugin with `generate-envelope` command, set `request.keyDefinition` to key definition (from `/notation/config.json`) corresponding to `keyName`, `request.payload` to base64 encoded descriptor, `request.payloadType` to `application/vnd.oci.descriptor.v1+json` and `request.signatureEnvelopeType` to a pre-defined type (default to `application/vnd.cncf.notary.v2.jws.v1`). - 1. `response.signatureEnvelope` contains the base64 encoded signature envelope, value of `response.signatureEnvelopeType` MUST match request.signatureEnvelopeType. + 1. Execute the plugin with `generate-envelope` command. Set `request.keyId` and the optional `request.pluginConfig` to corresponding values associated with signing key `keyName` in `config.json`. Set `request.payload` to base64 encoded descriptor, `request.payloadType` to `application/vnd.oci.descriptor.v1+json` and `request.signatureEnvelopeType` to a pre-defined type (default to `application/vnd.cncf.notary.v2.jws.v1`). + 1. `response.signatureEnvelope` contains the base64 encoded signature envelope, value of `response.signatureEnvelopeType` MUST match `request.signatureEnvelopeType`. 1. Validate the generated signature, return an error if of the checks fails. 1. Check if `response.signatureEnvelopeType` is a supported envelope type and `response.signatureEnvelope`'s format matches `response.signatureEnvelopeType`. 1. Check if the signing algorithm in the signature envelope is one of [supported signing algorithms](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection). @@ -251,7 +332,9 @@ This interface targets plugins that in addition to signature generation want to 1. Else if plugin supports capability `SIGNATURE_GENERATOR` *(covered in previous section)* 1. Return an error -**generate-envelope** +#### generate-envelope + +This command is used to generate the complete signature envelope for a given payload. *Request* All request attributes are required. @@ -260,8 +343,12 @@ All request attributes are required. { "contractVersion" : "", - // Complete key definition from /notation/config.JSON /signingKeys/keys with matching key name - "keyDefinition" : "", + // Key id associated with signing key (keyName) + // in config.json /signingKeys/keys + "keyId": "", + + // Optional plugin configuration, map of string-string + "pluginConfig" : { }, "payload" : "", @@ -273,13 +360,16 @@ All request attributes are required. } ``` +*keyId* : Required field that has the key identifier (`keyId`) associated with signing key `keyName` in `config.json`. + +*pluginConfig* : Optional field for plugin configuration. For details, see [Plugin Configuration section](#plugin-configuration). + *signatureEnvelopeType* - defines the type of signature envelope expected from the plugin. As Notation clients need to be updated in order to parse and verify new signature formats, the default signature format can only be changed with new major version releases of Notation. Users however can opt into using an updated signature format supported by Notation, by passing an optional parameter. e.g. `notation sign $IMAGE --key {key-name} --signatureFormat {some-new-format}` *Response* All response attributes are required. - ```jsonc { "signatureEnvelope": "", @@ -293,7 +383,7 @@ All response attributes are required. } ``` -*Error codes* +#### Error codes for generate-envelope * VALIDATION_ERROR - Any of the required request fields was empty, or a value was malformed/invalid. Includes condition where the key referenced by `keyId` was not found, payload type or signature envelope type in the request is unsupported by the plugin. * UNSUPPORTED_CONTRACT_VERSION - The contract version used in the request is unsupported. @@ -310,9 +400,14 @@ TBD [notaryproject/roadmap#27](https://github.com/notaryproject/roadmap/issues/2 TBD [#135](https://github.com/notaryproject/notaryproject/issues/135) +## FAQ + +**Q: Will Notation generate timestamp signature for Signature Envelope Generator plugin or its responsibility of plugin publisher?** + +**A :** If the envelope generated by a Signature Envelope Generator plugin contains timestamp signature, Notation will not append additional timestamp signature, else it will generate the timestamp signature and append it to the envelope as an unsigned attribute. + ## Open Items -* Will Notation generate timestamp signature for Signature Envelope Generator plugin or its responsibility of plugin publisher? + * [Issue #151](https://github.com/notaryproject/notation/issues/151) - Add Notation command line options to pass raw signature generated by existing crypto tools. * What standard providers should be supported? -* Do we need to support file based plugin configuration, or pass-through plugin configuration passed as is from Notation CLI to the plugin? -* Support for chaining plugins. It allows us to separate out and compose things like signing, TSA integration, push to transparency log. \ No newline at end of file +* Support for chaining plugins. It allows us to separate out and compose things like signing, TSA integration, push to transparency log. From 9cfd6a91b1dfad48886af17edf7cc8235556f550 Mon Sep 17 00:00:00 2001 From: Milind Gokarn Date: Thu, 2 Jun 2022 09:41:44 -0700 Subject: [PATCH 07/17] Signature spec updates (#158) * Updates to signature specification (#147) * Moves signed attributes out of payload * Defines new signed attributes and marks them as critical/informational * Introduces signature scheme to support different trust models * Baseline for signature spec update. * Updated JWS signature envelope payload and headers. * Addressed PR feedback. Updated EC512 to EC521. * Renamed `io.cncf.notary.timestamptoken` to `io.cncf.notary.timestampSignature` * RFC 3339 time formatting * Define Base64Url encoding for JWS Signed-off-by: Milind Gokarn Co-authored-by: Shiwei Zhang Co-authored-by: Steve Lasker --- signature-envelope-jws.md | 170 ++++++++++++++++++++++++ signature-specification.md | 262 +++++++++++++------------------------ 2 files changed, 258 insertions(+), 174 deletions(-) create mode 100644 signature-envelope-jws.md diff --git a/signature-envelope-jws.md b/signature-envelope-jws.md new file mode 100644 index 00000000..222d4a47 --- /dev/null +++ b/signature-envelope-jws.md @@ -0,0 +1,170 @@ +# JWS Signature Envelope + +This specification implements the [Notary v2 Signature specification](signature-specification.md) using JSON Web Signature (JWS). JWS ([RFC7515](https://datatracker.ietf.org/doc/html/rfc7515)) is a JSON based envelope format for digital signatures over any type of payload (e.g. JSON, binary). JWS is a Notary v2 supported signature format and specifically uses the *JWS JSON Serialization* representation. + +## Storage + +A JWS signature envelope will be stored in an OCI registry as a blob, and referenced in the signature manifest as a blob with `mediaType` of `"application/jose+json"`. + +Signature Manifest Example + +```jsonc +{ + "artifactType": "application/vnd.cncf.notary.signature", + "blobs": [ + { + "mediaType": "application/jose+json", + "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", + "size": 32654 + } + ], + "subject": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", + "size": 16724 + }, + "annotations": { + "io.cncf.notary.x509chain.thumbprint#S256": + "[\"B7A69A70992AE4F9FF103EBE04A2C3BA6C777E439253CE36562E6E98375068C3\",\"932EB6F5598435D4EF23F97B0B5ACB515FAE2B8D8FAC046AB813DDC419DD5E89\"]" + } +} +``` + +## JWS Payload + +The JWS envelope contains a [Notary v2 Payload](./signature-specification.md#payload). + +Example of Notary v2 payload + +```jsonc +{ + "targetArtifact": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", + "size": 16724, + "annotations": { + "io.wabbit-networks.buildId": "123" // user defined metadata + } + } +} +``` + +## Protected Headers + +The JWS envelope for Notary v2 uses following headers + +- Registered headers - `alg`, `cty`, and `crit` +- [Public headers](https://datatracker.ietf.org/doc/html/rfc7515#section-4.2) with collision resistant names - `io.cncf.notary.signingTime`, `io.cncf.notary.expiry` + +Example + +```jsonc +{ + "alg": "PS384", + "cty": "application/vnd.cncf.notary.payload.v1+json", + "io.cncf.notary.signingTime": "2022-04-06T07:01:20Z", + "io.cncf.notary.expiry": "2022-10-06T07:01:20Z", + "crit":["io.cncf.notary.expiry"] +} +``` + +- **[`alg`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.1)**(*string*): This REQUIRED header defines which signing algorithm was used to generate the signature. JWS specification defines `alg` as a required header, that MUST be present and MUST be understood and processed by verifier. The signature algorithm of the signing key (first certificate in `x5c`) is the source of truth, and during signing the value of `alg` MUST be set corresponding to signature algorithm of the signing key using [this mapping](#supported-alg-header-values) that lists the Notary v2 allowed subset of `alg` values supported by JWS. Similarly verifier of the signature MUST match `alg` with signature algorithm of the signing key to mitigate algorithm substitution attacks. +- **[`cty`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.10)**(*string*): The REQUIRED header content-type is used to declare the media type of the secured content (the payload). The supported value is `application/vnd.cncf.notary.payload.v1+json`. +- **`io.cncf.notary.signingTime`**(*string*): This REQUIRED header specifies the time at which the signature was generated. This is an untrusted timestamp, and therefore not used in trust decisions. Its value is a [RFC 3339][rfc3339] formatted date time, the optional fractional second ([time-secfrac][rfc3339][[1](https://datatracker.ietf.org/doc/html/rfc3339#section-5.3)]) SHOULD NOT be used. +- **`io.cncf.notary.expiry`**(*string*): This OPTIONAL header provides a “best by use” time for the artifact, as defined by the signer. Its value is a [RFC 3339][rfc3339] formatted date time, the optional fractional second ([time-secfrac][rfc3339][[1](https://datatracker.ietf.org/doc/html/rfc3339#section-5.3)]) SHOULD NOT be used. +- **[`crit`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.11)**(*array of strings*): This OPTIONAL header lists the headers that implementation MUST understand and process. It MUST only contain headers apart from registered headers (e.g. `alg`, `cty`) in JWS specification, therefore this header is only present when the optional `io.cncf.notary.expiry` header is present in the protected headers collection. + If present, the value MUST be `["io.cncf.notary.expiry"]`. + +## Unprotected Headers + +Notary v2 supports following unprotected headers: `timestamp`, `x5c` and `io.cncf.notary.signingAgent` + +```jsonc +{ + "x5c": ["", "", ""], + "io.cncf.notary.timestampSignature": "", + "io.cncf.notary.signingAgent": "notation/1.0.0" +} +``` + +- **[`x5c`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6)** (*array of strings*): This REQUIRED header contains the ordered list of X.509 certificate or certificate chain([RFC5280](https://datatracker.ietf.org/doc/html/rfc5280)) corresponding to the key used to digitally sign the JWS. The certificate chain is represented as a JSON array of certificate value strings, each string in the array is a base64-encoded DER certificate value. The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate, followed by the intermediate and root certificates in the correct order. Refer [*Certificate Chain* unsigned attribute](signature-specification.md#unsigned-attributes) for more details. +- **`io.cncf.notary.timestampSignature`** (*string*): This OPTIONAL header is used to store countersignature that provides trusted signing time. Only [RFC3161]([rfc3161](https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2)) compliant `TimeStampToken` are supported. + - **TODO** Define the opaque datum (hash of envelope) that is sent to TSA, and how TSA response (time stamp token) is represented in this header. +- **`io.cncf.notary.signingAgent`**(*string*): This OPTIONAL header provides the identifier of a client (e.g. Notation) that produced the signature. E.g. “notation/1.0.0”. Refer [*Signing Agent* unsigned attribute](signature-specification.md#unsigned-attributes) for more details. + +## Signature + +In JWS signature is calculated by combining JWSPayload and protected headers. +The process is described below: + +### Create the *JWS Signing Input* + +1. Compute the Base64Url value of ProtectedHeaders, this is the value of `protected` property in the signature envelope. +1. Compute the Base64Url value of JWSPayload, this is the value of `payload` property in the signature envelope. +1. Build *JWS Signing Input* to be signed by concatenating the values generated in step 1 and step 2 using '.' +`ASCII(BASE64URL(UTF8(ProtectedHeaders)) ‘.’ BASE64URL(JWSPayload))` + +Base64Url encoding used by JWS (*Base64url Encoding* in [RFC 7515 section 2][jws-terminology]) is a URL safe Base64 encoding as defined in [RFC 4648][rfc4648], with all trailing '=' characters omitted and without the inclusion of any additional characters. + +### Generate the signature + +1. Compute the signature on the *JWS Signing Input* constructed in the previous step by using the signature algorithm of the signing key, which MUST match the corresponding protected header `alg`. +2. Compute the Base64Url value of the signature produced in the previous step. + This is the value of the `signature` property in the signature envelope. + +## Signature Envelope + +The final signature envelope comprises of Payload, ProtectedHeaders, UnprotectedHeaders, and Signature, no additional top level fields are supported. + +Since Notary v2 restricts one signature per signature envelope, the compliant signature envelope MUST be in flattened JWS JSON format. + +```jsonc +{ + "payload": "", + "protected": "", + "header": { + "io.cncf.notary.timestamp": "", + "x5c": ["", "", ""] + }, + "signature": "Base64Url( sign( ASCII( . )))" +} +``` + +## Implementation Constraints + +### Supported `alg` header values + +Notary v2 implementation MUST enforce the following constraints on signature generation and verification: + +1. `alg` header value MUST NOT be `none` or any symmetric-key algorithm such as `HMAC`. +1. `alg` header value MUST be same as that of signature algorithm identified using signing certificate's public key algorithm and size. +1. `alg` header values for various signature algorithms is a subset of values supported by [JWS][jws-alg-values]. + +**Mapping of Notary v2 approved algorithms to JWS `alg` header values** + + | Signature Algorithm | `alg` Header Value| + | ------------------------------- | ----------------- | + | RSASSA-PSS with SHA-256 | PS256 | + | RSASSA-PSS with SHA-384 | PS384 | + | RSASSA-PSS with SHA-512 | PS512 | + | ECDSA on secp256r1 with SHA-256 | ES256 | + | ECDSA on secp384r1 with SHA-384 | ES384 | + | ECDSA on secp521r1 with SHA-512 | ES512 | + +1. Signing certificate MUST be a valid codesigning certificate. +1. Only JWS JSON flattened format is supported. + +## FAQ + +**Q:** Why JWT is not used as the signature envelope format? + +**A:** JWT uses JWS compact serialization which do not support unsigned attributes. Notary v2 signature requires support for unsigned attributes. Instead we use the *JWS JSON Serialization* representation, which supports unsigned attributes. + +**Q:** Why JWT `exp` and `iat` claims are not used? + +**A:** Unlike JWT which always contains a JSON payload, Notary v2 envelope can support payloads other than JSON, like binary. Reusing the JWT payload structure and claims, limits the Notary v2 JWS envelope to only support JSON payload, which is undesirable. Also, reusing JWT claims requires following same claim semantics as defined in JWT specifications. The [`exp`](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4) claim requires that verifier MUST reject the signature if current time equals or is greater than `exp`, where as Notary v2 allows verification policy to define how expiry is handled. + +[jws-alg-values]: https://datatracker.ietf.org/doc/html/rfc7518#section-3.1 +[rfc3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 +[rfc4648]: https://datatracker.ietf.org/doc/html/rfc4648#section-5 +[jws-terminology]: https://datatracker.ietf.org/doc/html/rfc7515#section-2 diff --git a/signature-specification.md b/signature-specification.md index a378b712..ff8939e5 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -1,33 +1,24 @@ # Signature Specification -This document describes how Notary v2 signatures are created and stored. -The document has the following sections: +This document provides the following details for Notary v2 signatures: -- **[Storage](#storage)**: Describes how signatures are stored in OCI registry. -- **[Signature Envelope](#signature-envelope)**: Describes how signatures are created. +- **[Storage](#storage)**: Describes how signatures are stored and retrieved from an OCI registry. +- **[Signature Envelope](#signature-envelope)**: Describes the structure of a Notary v2 signature. ## Storage This section describes how Notary v2 signatures are stored in the OCI Distribution conformant registry. -Notary v2 uses [ORAS artifact manifest](https://github.com/oras-project/artifacts-spec/blob/main/artifact-manifest.md) to store the signature in the repository. +Notary v2 uses [ORAS artifact manifest][oras-artifact-manifest] to store the signature in the repository. The media type of the signature manifest is `application/vnd.cncf.oras.artifact.manifest.v1+json`. The signature manifest has an artifact type that specifies it's a Notary V2 signature, a reference to the manifest of the artifact being signed, a blob referencing the signature, and a collection of annotations. ![Signature storage inside registry](media/signature-specification.svg) -- **`artifactType`** (*string*): This REQUIRED property references the Notary version of the signature: `application/vnd.cncf.notary.v2.signature`. -- **`blobs`** (*array of objects*): This REQUIRED property contains collection of only one [artifact descriptor](https://github.com/oras-project/artifacts-spec/blob/main/descriptor.md) referencing signature envelope. - - **`mediaType`** (*string*): This REQUIRED property contains media type of signature envelope blob. The supported value is `application/jose+json` -- **`subject`** (*descriptor*): A REQUIRED artifact descriptor referencing the signed manifest, including, but not limited to image manifest, image index, oras-artifact manifest. -- **`annotations`** (*string-string map*): This REQUIRED property contains metadata for the artifact manifest. - It is being used to store information about the signature. - Keys using the `io.cncf.notary` namespace are reserved for use in Notary and MUST NOT be used by other specifications. - - **`io.cncf.notary.x509certs.fingerprint.sha256`**: A REQUIRED annotation whose value contains the list of SHA-256 fingerprint of signing certificate and certificate chain used for signature generation. - The list of fingerprints is present as a JSON array string. +Signature Manifest Example -```json +```jsonc { - "artifactType": "application/vnd.cncf.notary.v2.signature", + "artifactType": "application/vnd.cncf.notary.signature", "blobs": [ { "mediaType": "application/jose+json", @@ -41,14 +32,26 @@ The signature manifest has an artifact type that specifies it's a Notary V2 sign "size": 16724 }, "annotations": { - "io.cncf.notary.x509certs.fingerprint.sha256": "[\"B7A69A70992AE4F9FF103EBE04A2C3BA6C777E439253CE36562E6E98375068C3\" \"932EB6F5598435D4EF23F97B0B5ACB515FAE2B8D8FAC046AB813DDC419DD5E89\"]" + "io.cncf.notary.x509chain.thumbprint#S256": + "[\"B7A69A70992AE4F9FF103EBE04A2C3BA6C777E439253CE36562E6E98375068C3\",\"932EB6F5598435D4EF23F97B0B5ACB515FAE2B8D8FAC046AB813DDC419DD5E89\"]" } } ``` +- **`artifactType`** (*string*): This REQUIRED property references the Notary version of the signature: `application/vnd.cncf.notary.signature`. +- **`blobs`** (*array of objects*): This REQUIRED property contains collection of only one [artifact descriptor][artifact-descriptor] referencing signature envelope. + - **`mediaType`** (*string*): This REQUIRED property contains media type of signature envelope blob. Following values are supported + - `application/jose+json` +- **`subject`** (*descriptor*): A REQUIRED artifact descriptor referencing the signed manifest, including, but not limited to image manifest, image index, oras-artifact manifest. +- **`annotations`** (*string-string map*): This REQUIRED property contains metadata for the artifact manifest. + It is being used to store information about the signature. + Keys using the `io.cncf.notary` namespace are reserved for use in Notary and MUST NOT be used by other specifications. + - **`io.cncf.notary.x509chain.thumbprint#S256`**: A REQUIRED annotation whose value contains the list of SHA-256 fingerprint of signing certificate and certificate chain (including root) used for signature generation. The annotation name contains the hash algorithm as a suffix (`#S256`) and can be extended to support other hashing algorithms in future. + The list of fingerprints is present as a JSON array string, corresponding to ordered certificates in [*Certificate Chain* unsigned attribute](#unsigned-attributes) in the signature envelope. + ### Signature Discovery -The client should be able to discover all the signatures belonging to an artifact (such as image manifest) by using [ORAS Manifest Referrers API](https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md). +The client should be able to discover all the signatures belonging to an artifact (such as image manifest) by using [ORAS Manifest Referrers API][oras-artifacts-referrers]. ORAS Manifest Referrers API returns a paginated list of all artifacts belonging to a target artifact (such as container images, SBoMs). The implementation can filter Notary signature artifacts by either using ORAS Manifest Referrers API or using custom logic on the client. Each Notary signature artifact refers to a signature envelope blob. @@ -56,190 +59,84 @@ Each Notary signature artifact refers to a signature envelope blob. ### Signature Filtering An OCI artifact can have multiple signatures, Notary v2 uses annotations of the signature artifact to filter relevant signatures based on the applicable trust policy. -The Notary v2 signature artifact's `io.cncf.notary.x509certs.fingerprint.sha256` annotations key MUST contain the list of SHA-256 fingerprints of certificate and certificate chain used for signing. +The Notary v2 signature artifact's `io.cncf.notary.x509chain.thumbprint#S256` annotations key MUST contain the list of SHA-256 fingerprints of certificate and certificate chain used for signing. ## Signature Envelope The Signature Envelope is a standard data structure for creating a signed message. A signature envelope consists of the following components: -- Payload `m`: The data that is integrity protected - e.g. descriptor of the artifact being signed. -- Signed attributes `v`: The signature metadata that is integrity protected - e.g. signature expiration time, signing time, etc. -- Unsigned attributes `u`: This OPTIONAL property represents signature metadata that is not integrity protected - e.g. timestamp, certificates, etc. +- Payload/Message `m`: The data that is integrity protected - e.g. descriptor of the artifact being signed. +- Signed attributes `v`: The signature metadata that is integrity protected - e.g. signature expiration time, creation time, etc. +- Unsigned attributes `u`: These attributes are not signed by the signing key that generates the signature. We anticipate unsigned attributes contain content that may be signed by an different party e.g. Certificate chain signed by a CA, or TSA countersignature signed by the TSA. - Cryptographic signatures `s`: The digital signatures computed on payload and signed attributes. A signature envelope is `e = {m, v, u, s}` where `s` is signature. -Notary v2 supports [JWS JSON Serialization](https://datatracker.ietf.org/doc/html/rfc7515) as signature envelope format with some additional constraints but makes provisions to support additional signature envelope format. +This specification defines the set of signed and unsigned attributes that make up a valid The Notary v2 signature. This specification aims to be be agnostic of signature envelope format (e.g. COSE, JWS), details of encoding the envelope in a specific signature envelope format are covered in in separate specs. + +Notary v2 supports the following envelope formats: + +- [JWS](./signature-envelope-jws.md) ### Payload -Notary v2 requires Payload to be the content **descriptor** of the subject manifest that is being signed. +Notary v2 payload is a JSON document with media type `application/vnd.cncf.notary.payload.v1+json` and has following properties. -1. Descriptor MUST contain `mediaType`, `digest`, `size` fields. -1. Descriptor MAY contain `annotations` and if present it MUST follow the [annotation rules](https://github.com/opencontainers/image-spec/blob/main/annotations.md#rules). Notary v2 uses annotations for storing both Notary specific and user defined signed attributes. The prefix `io.cncf.notary` in annotation keys is reserved for use in Notary v2 and MUST NOT be used outside this specification. -1. Descriptor MAY contain `artifactType` field for artifact manifests, or the `config.mediaType` for `oci.image` based manifests. +- `targetArtifact` : Required property whose value is the descriptor of the target artifact manifest that is being signed. Both [OCI descriptor][oci-descriptor] and [ORAS artifact descriptors][artifact-descriptor] are supported. + - Descriptor MUST contain `mediaType`, `digest`, `size` fields. + - Descriptor MAY contain `annotations` and if present it MUST follow the [annotation rules][annotation-rules]. Notary v2 uses annotations for storing both Notary specific and user defined metadata. The prefix `io.cncf.notary` in annotation keys is reserved for use in Notary v2 and MUST NOT be used outside this specification. + - Descriptor MAY contain `artifactType` field for artifact manifests, or the `config.mediaType` for `oci.image` based manifests. -Examples: +#### Examples ```jsonc { - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", - "size": 16724, - "annotations": { - "io.wabbit-networks.buildId": "123" // user defined signed attribute. - } + "targetArtifact": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", + "size": 16724, + "annotations": { + "io.wabbit-networks.buildId": "123" // user defined metadata + } + } } ``` -```json +```jsonc { - "mediaType": "sbom/example", - "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", - "size": 32654 + "targetArtifact": { + "mediaType": "sbom/example", + "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", + "size": 32654 + } } ``` ### Signed Attributes -Notary v2 requires the signature envelope to support the following signed attributes: +Signed attributes/claims are additional metadata apart from the payload, which are required to support the signature verification process. -- **Creation time**: The time at which the signature was generated. - Its value should be numeric representing the number of seconds (not milliseconds) since Epoch. -- **Expiration time**: The time after which signature shouldn't be considered valid. - Its value should be numeric representing the number of seconds since Epoch. - This is an OPTIONAL attribute. +- Any metadata that is used to verify the payload itself, and establish trust MUST be stored separately from the payload itself, as signed attributes or claims. Such metadata MUST NOT be stored/appended in the payload, as the payload is only parsed and processed once the signature has been verified and trust is established. +- Specific claims can be either required or optional. +- Claims that MUST be processed by a verifier MUST be marked as critical. Some claims may be optional and critical, i.e. they MUST be processed by a verifier only if they are present. +- Claims which are informational and do not influence signature verification MUST NOT be marked critical. -### Unsigned Attributes - -- **Certificates**: The ordered collection of X.509 certificates with a signing certificate as the first entry. -- **Timestamp**: The time stamp token generated for a given signature. - Only [RFC3161](https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2) compliant *TimeStampToken* are supported. - This is an OPTIONAL attribute. - -### Supported Signature Envelopes - -#### JWS JSON Serialization - -In JWS JSON Serialization ([RFC7515](https://datatracker.ietf.org/doc/html/rfc7515)), data is stored as either claims or headers (protected and unprotected). -Notary v2 uses JWS JSON Serialization for the signature envelope with some additional constraints on the structure of claims and headers. - -Unless explicitly specified as OPTIONAL, all fields are required. -Also, there shouldn’t be any additional fields other than ones specified in JWSPayload, ProtectedHeader, and UnprotectedHeader. - -**JWSPayload a.k.a. Claims**: -Notary v2 is using one private claim (`notary`) and two public claims (`iat` and `exp`). -An example of the claim is described below +Notary v2 requires the signature envelope to support the following signed attributes/claims. -```json -{ - "subject": { - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", - "size": 16724, - "annotations": { - "key1": "value1", - "key2": "value2", - ... - } - }, - "iat": 1234567891000, - "exp": 1234567891011 -} -``` - -The payload contains the subject manifest and other attributes that have to be integrity protected. +#### Standard attributes -- **`subject`**(*descriptor*): A REQUIRED top-level node consisting of the manifest that needs to be integrity protected. - Please refer [Payload](#payload) section for more details. -- **`iat`**(*number*): The REQUIRED property Issued-at(`iat`) identifies the time at which the signature was issued. -- **`exp`**(*number*): This OPTIONAL property contains the expiration time on or after which the signature must not be considered valid. +- **Signing time**: A REQUIRED claim that indicates the time at which the signature was generated. Though this claim is signed by the signing key, it’s considered unauthenticated as a signer can modify local time and manipulate this claim. More details [here](#signing-time). +- **Expiry** (critical): An OPTIONAL claim that provides a “best by use” time for the artifact, as defined by the signer. More details [here](#expiry). +- **Content type** (critical): A REQUIRED claim that indicates the content type of the [payload](#payload). The supported value is `application/vnd.cncf.notary.payload.v1+json`. Other payload types MAY be supported in future. -To leverage JWS claims validation functionality already provided by libraries, we have defined `iat`, `exp` as top-level nodes. - -**ProtectedHeaders**: Notary v2 supports only three protected headers: `alg`, `cty`, and `crit`. - -```json -{ - "alg": "RS256", - "cty": "application/vnd.cncf.notary.v2.jws.v1", - "crit":["cty"] -} -``` - -- **`alg`**(*string*): This REQUIRED property defines which algorithm was used to generate the signature. - JWS needs an algorithm(`alg`) to be present in the header, so we have added it as a protected header. -- **`cty`**(*string*): The REQUIRED property content-type(cty) is used to declare the media type of the secured content(the payload). - This will be used to version different variations of JWS signature. - The supported value is `application/vnd.cncf.notary.v2.jws.v1`. -- **`crit`**(*array of strings*): This REQUIRED property lists the headers that implementation MUST understand and process. - The value MUST be `["cty"]`. - -**UnprotectedHeaders**: Notary v2 supports only two unprotected headers: timestamp and x5c. - -```json -{ - "timestamp": "", - "x5c": ["", "", ""] -} -``` - -- **`timestamp`** (*string*): This OPTIONAL property is used to store time stamp token. - Only [RFC3161]([rfc3161](https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2)) compliant TimeStampToken are supported. -- **`x5c`** (*array of strings*): This REQUIRED property contains the list of X.509 certificate or certificate chain([RFC5280](https://datatracker.ietf.org/doc/html/rfc5280)) corresponding to the key used to digitally sign the JWS. - The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. - -- **`timestamp`** (*string*): This OPTIONAL property is used to store time stamp token. - Only [RFC3161]([rfc3161](https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2)) compliant TimeStampToken are supported. -- **`x5c`** (*array of strings*): This REQUIRED property contains the list of X.509 certificate or certificate chain([RFC5280](https://datatracker.ietf.org/doc/html/rfc5280)) corresponding to the key used to digitally sign the JWS. - The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. - -**Signature**: In JWS signature is calculated by combining JWSPayload and protected headers. -The process is described below: - -1. Compute the Base64Url value of ProtectedHeaders. -1. Compute the Base64Url value of JWSPayload. -1. Build message to be signed by concatenating the values generated in step 1 and step 2 using '.' -`ASCII(BASE64URL(UTF8(ProtectedHeaders)) ‘.’ BASE64URL(JWSPayload))` -1. Compute the signature on the message constructed in the previous step by using the signature algorithm defined in the corresponding header element: `alg`. -1. Compute the Base64Url value of the signature produced in the previous step. - This is the value of the signature property used in the signature envelope. - -**Signature Envelope**: The final signature envelope comprises of Claims, ProtectedHeaders, UnprotectedHeaders, and signature. - -Since Notary v2 restricts one signature per signature envelope, the compliant signature envelope MUST be in flattened JWS JSON format. - -```json -{ - "payload": "", - "protected": "", - "header": { - "timestamp": "", - "x5c": ["", "", ""] - }, - "signature": "Base64Url( sign( ASCII( . )))" -} -``` - -**Implementation Constraints**: Notary v2 implementation MUST enforce the following constraints on signature generation and verification: - -1. `alg` header value MUST NOT be `none` or any symmetric-key algorithm such as `HMAC`. -2. `alg` header value MUST be same as that of signature algorithm identified using signing certificate's public key algorithm and size. -3. `alg` header values for various signature algorithms: +### Unsigned Attributes -| Signature Algorithm | `alg` Param Value | -| ------------------------------- | ----------------- | -| RSASSA-PSS with SHA-256 | PS256 | -| RSASSA-PSS with SHA-384 | PS384 | -| RSASSA-PSS with SHA-512 | PS512 | -| ECDSA on secp256r1 with SHA-256 | ES256 | -| ECDSA on secp384r1 with SHA-384 | ES384 | -| ECDSA on secp521r1 with SHA-512 | ES512 | +These attributes are considered unsigned with respect to the signing key that generates the signature. These attributes are typically signed by a third party (e.g. CA, TSA). -4. Signing certificate MUST be a valid codesigning certificate. -5. Only JWS JSON flattened format is supported. - See 'Signature Envelope' section. +- **Certificate Chain**: This is a REQUIRED attribute that contains the ordered list of X.509 public certificates associated with the signing key used to generate the signature. The ordered list starts with the signing certificates, any intermediate certificates and ends with the root certificate. The certificate chain MUST be authenticated against a trust store as part of signature validation. Specific requirements for the certificates in the chain are provided [here](#certificate-requirements). +- **Timestamp signature** : An OPTIONAL counter signature which provides [trusted timestamp](#signing-time)e.g. Time Stamp Authority (TSA) generated timestamp signature. Only [RFC3161](ietf-rfc3161) compliant TimeStampToken are currently supported. +- **Signing Agent**: An OPTIONAL claim that provides the identifier of the software (e.g. Notation) that produced the signature on behalf of the user. It is an opaque string set by the software that produces the signature. It's intended primarily for diagnostic and troubleshooting purposes, this attribute is unsigned, the verifier MUST NOT validate formatting, or fail validation based on the content of this claim. The suggested format is one or more tokens of the form `{id}/{version}` containing identifier and version of the software, seperated by spaces. E.g. “notation/1.0.0”, “notation/1.0.0 com.example.nv2plugin/0.8”. ## Signature Algorithm Requirements @@ -265,7 +162,7 @@ The signing certificate's public key algorithm and size MUST be used to determin | RSA | 4096 | RSASSA-PSS with SHA-512 | | EC | 256 | ECDSA on secp256r1 with SHA-256 | | EC | 384 | ECDSA on secp384r1 with SHA-384 | -| EC | 512 | ECDSA on secp521r1 with SHA-512 | +| EC | 521 | ECDSA on secp521r1 with SHA-512 | ### Certificate Requirements @@ -292,12 +189,29 @@ The **timestamping certificate** MUST meet the following minimum requirements: ## FAQ -**Q: How will Notary v2 support multiple signature envelope format?** +**Q: How will Notary v2 support multiple signature envelope formats?** -**A:** The idea is to use `mediaType` of artifact manifest's blob to identify the signature envelope type (like JWS, CMS, DSSE, etc). +**A:** The `mediaType` of artifact manifest's blob identifies the signature envelope type. The client implementation can use the aforementioned `mediaType` to parse the signature envelope. -**Q: How will Notary v2 handle non-backward compatible changes to signature format?** +**Q: How will Notary v2 support multiple payload formats?** + +**A:** The Signature envelope MUST have a versioning mechanism to support multiple payload formats. +For [JWS JSON serialization](./signature-envelope-jwt.md) signature envelope, versioning is achieved by the `cty` field in ProtectedHeaders. + +## Appendix + +### Signing time + +The signing time denotes the time at which the signature was generated. A X509 certificate has a defined [validity](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5) during which it can be used to generate signatures. The signing time must be greater than or equal to certificate's `notBefore` attribute, and signing time must be less than or equal to certificate's `notAfter` attribute. Signatures generated after the certificate expires are considered invalid. A trusted timestamp, like TSA countersignature, allows a verifier to determine if the signature was generated when the certificate was valid. It also allows a verifier to determine if a signature be treated as valid when a certificate is revoked, if the certificate was revoked after the signature was generated. In the absence of a trusted timestamp, signatures are considered invalid after certificate expires, and all signatures are considered revoked when a certificate is revoked. + +### Expiry + +This is an optional feature that provides a “best by use” time for the artifact, as defined by the signer. Notary v2 allows users to include an optional expiry time when they generate a signature. The expiry time is not set by default and requires explicit configuration by users at the time of signature generation. The artifact is considered expired when the current time is greater than or equal to expiry time, users performing verification can either configure their trust policies to fail the verification or even accept the artifact with expiry date in the past using policy. This is an advanced feature that allows implementing controls for user defined semantics like deprecation for older artifacts, or block older artifacts in a production environment. Users should only include an expiry time in the signed artifact after considering the behavior they expect for consumers of the artifact after it expires. Users can choose to consume an artifact even after the expiry time based on their specific needs. -**A:** The Signature envelope MUST have a versioning mechanism to support non-backward compatible changes. -For [JWS JSON serialization](#jws-json-serialization) signature envelope it is achieved by `cty` field in ProtectedHeaders. +[annotation-rules]: https://github.com/opencontainers/image-spec/blob/main/annotations.md#rules +[oci-descriptor]: https://github.com/opencontainers/image-spec/blob/main/descriptor.md +[artifact-descriptor]: https://github.com/oras-project/artifacts-spec/blob/main/descriptor.md +[ietf-rfc3161]: https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2 +[oras-artifact-manifest]: https://github.com/oras-project/artifacts-spec/blob/main/artifact-manifest.md +[oras-artifacts-referrers]: https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md From a99fd700f03c51ab7d7e0ad30a681b5c1389544d Mon Sep 17 00:00:00 2001 From: Pritesh Bandi Date: Tue, 21 Jun 2022 15:25:17 -0700 Subject: [PATCH 08/17] Added requirement for codesigning and timestamping certificate (#162) Signed-off-by: Pritesh Bandi --- signature-specification.md | 56 ++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/signature-specification.md b/signature-specification.md index ff8939e5..beae7323 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -166,26 +166,42 @@ The signing certificate's public key algorithm and size MUST be used to determin ### Certificate Requirements -The **signing certificate** MUST meet the following minimum requirements: - -- The keyUsage extension MUST be present and MUST be marked critical. - The bit positions for digitalSignature MUST be set ([RFC-5280](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3)). -- The extKeyUsage extension MUST be present and its value MUST be id-kp-codeSigning ([RFC-5280](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.12)). -- If the basicConstraints extension is present, the cA field MUST be set false ([RFC-5280](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9)). -- The certificate MUST abide by the following key length restrictions: -- For RSA public key, the key length MUST be 2048 bits or higher. - - For ECDSA public key, the key length MUST be 256 bits or higher. - -The **timestamping certificate** MUST meet the following minimum requirements: - -- The keyUsage extension MUST be present and MUST be marked critical. - The bit positions for digitalSignature MUST be set ([RFC-5280](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3)). -- The extKeyUsage extension MUST be present and MUST be marked critical. - The value of extension MUST be id-kp-timeStamping ([RFC-5280](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.12)). -- If the basicConstraints extension is present, the cA field MUST be set false ([RFC-5280](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9)). -- The certificate MUST abide by the following key length restrictions: - - For RSA public key, the key length MUST be 2048 bits or higher. - - For ECDSA public key, the key length MUST be 256 bits or higher. +The codesigning and timestamping certificates MUST meet the following requirements. These requirements are validated both at signature generation time and signature verification time, and are applied to the certificate chain in the signature envelope. These validations are independent of certificate chain validation against a trust store. + +#### Root and Intermediate CA Certificates + +The CA certificates MUST meet the following requirements + +1. **[Basic Constraints:](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9)** +The `basicConstraints` extension MUST be present and MUST be marked as critical. The `cA` field MUST be set `true`. +The [`pathLenConstraint`](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9) field is OPTIONAL. If present, it MUST be verified against the depth of the chain below that CA certificate. (If value is null consider it as not present) +1. **[Key Usage:](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3)** +The `keyUsage` extension MUST be present and MUST be marked critical. Bit positions for `keyCertSign` MUST be set. + +#### Leaf Certificates + +The leaf or end certificates MUST meet the following requirements + +1. **[Basic Constraints:](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9)** +The `basicConstraints` extension is OPTIONAL and can OPTIONALLY be marked as critical. If present, the `cA` field MUST be set to `false`. +1. **[Key Usage:](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3)** +The `keyUsage` extension MUST be present and MUST be marked critical. Bit positions for `digitalSignature` MUST be set. The Bit positions for `keyCertSign` and `cRLSign` MUST NOT be set. +1. **[Extended Key Usage:](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.12)** The `extendedKeyUsage` extension is OPTIONAL and can OPTIONALLY be marked as critical. + - **For codesigning certificate:** If present, the value MUST contain `id-kp-codeSigning` and MUST NOT contain `anyExtendedKeyUsage`, `serverAuth`, `emailProtection` and `timeStamping`. + - **For timestamping certificate:** If present, the value MUST contain `id-kp-timeStamping` and MUST NOT contain `anyExtendedKeyUsage`, `serverAuth`, `emailProtection` and `codeSigning`. +1. **Key Length** The certificate MUST abide by the following key length restrictions: + - For RSA public key, the key length MUST be 2048 bits or higher. + - For ECDSA public key, the key length MUST be 256 bits or higher. + +#### Other requirements + +1. The certificates in the signature MUST be ordered list of X.509 certificate or certificate chain i.e. the certificate containing the public key used to digitally sign the payload must be the first certificate, followed by the intermediate and root certificates in the correct order. This also means + - The certificate MUST NOT chain to multiple parents/roots. + - The certificate chain MUST NOT contain a certificate that is unrelated to the certificate chain. +1. A valid certificate chain MUST contain a minimum of two certificates - a leaf and a root certificate. +1. Any certificate in the certificate chain MUST NOT use SHA1WithRSA and ECDSAWithSHA1 signatures. +1. Only Basic Constraints, Key Usage, and Extended Key Usage extensions of X.509 certificates are honored. For rest of the extensions, Notary MUST fail open i.e. they MUST NOT be evaluated or honored. +1. The certificates in the certificate chain MUST be valid at signing time. Notary MUST NOT enforce validity period nesting, i.e the validity period for a given certificate may not fall entirely within the validity period of that certificate's issuer certificate. ## FAQ From 68d168b512a9763eb95560ef171c22c78fa14447 Mon Sep 17 00:00:00 2001 From: Steve Lasker Date: Mon, 11 Jul 2022 01:35:12 -0700 Subject: [PATCH 09/17] Change targetArtifact to subject, consistent with the artifact spec (#169) Signed-off-by: Steve Lasker --- signature-envelope-jws.md | 2 +- signature-specification.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/signature-envelope-jws.md b/signature-envelope-jws.md index 222d4a47..fc37b99b 100644 --- a/signature-envelope-jws.md +++ b/signature-envelope-jws.md @@ -38,7 +38,7 @@ Example of Notary v2 payload ```jsonc { - "targetArtifact": { + "subject": { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", "size": 16724, diff --git a/signature-specification.md b/signature-specification.md index beae7323..a6ff0afe 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -83,7 +83,7 @@ Notary v2 supports the following envelope formats: Notary v2 payload is a JSON document with media type `application/vnd.cncf.notary.payload.v1+json` and has following properties. -- `targetArtifact` : Required property whose value is the descriptor of the target artifact manifest that is being signed. Both [OCI descriptor][oci-descriptor] and [ORAS artifact descriptors][artifact-descriptor] are supported. +- `subject` : Required property whose value is the descriptor of the target artifact manifest that is being signed. Both [OCI descriptor][oci-descriptor] and [ORAS artifact descriptors][artifact-descriptor] are supported. - Descriptor MUST contain `mediaType`, `digest`, `size` fields. - Descriptor MAY contain `annotations` and if present it MUST follow the [annotation rules][annotation-rules]. Notary v2 uses annotations for storing both Notary specific and user defined metadata. The prefix `io.cncf.notary` in annotation keys is reserved for use in Notary v2 and MUST NOT be used outside this specification. - Descriptor MAY contain `artifactType` field for artifact manifests, or the `config.mediaType` for `oci.image` based manifests. @@ -92,7 +92,7 @@ Notary v2 payload is a JSON document with media type `application/vnd.cncf.notar ```jsonc { - "targetArtifact": { + "subject": { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", "size": 16724, @@ -105,7 +105,7 @@ Notary v2 payload is a JSON document with media type `application/vnd.cncf.notar ```jsonc { - "targetArtifact": { + "subject": { "mediaType": "sbom/example", "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", "size": 32654 From 0a6ce3bf127e01ddcdbca1ad4176ffc88bd012a7 Mon Sep 17 00:00:00 2001 From: Steve Lasker Date: Mon, 11 Jul 2022 16:20:54 -0700 Subject: [PATCH 10/17] Typo fix (#170) Merging as this is just a typo fix, with at least one set of :eyes: Signed-off-by: Steve Lasker --- trust-store-trust-policy-specification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trust-store-trust-policy-specification.md b/trust-store-trust-policy-specification.md index b71c0ebc..85068334 100644 --- a/trust-store-trust-policy-specification.md +++ b/trust-store-trust-policy-specification.md @@ -147,7 +147,7 @@ Trust policy for the scenario where ACME Rockets uses artifacts signed by themse The scope field supports filtering based on fully qualified repository URI `${registry-name}/${namespace}/${repository-name}`. For more information, see [registry scopes constraints](#registry-scopes-constraints) section. - **`signatureVerification`**(*string*): This REQUIRED property dictates how signature verification is performed. Supported values are `strict`, `permissive`, `audit` and `skip`. Detailed explaination of each value is present [here](#signatureverification-details). A custom level can be defined by referencing a supported level value, and overriding individual validation checks. NOTE: Custom signature verification level will not be supported in `notation` RC1. - - **`trustStore`**(*string*): This REQUIRED property specifies a named trust store. Uses the format `{trust-store-type}:{named-store}`. Currently supported values for `trust-store-type` are `ca` and `tsa`. **NOTE**: When support for publicly trusted TSA is available, `tsa:publicly-trusted-tsa` is the default value, and implied without explictly specifying it. If a custom TSA is used the format `ca:acme-rockets,tsa:acme-tsa` is supported to specify it. + - **`trustStore`**(*string*): This REQUIRED property specifies a named trust store. Uses the format `{trust-store-type}:{named-store}`. Currently supported values for `trust-store-type` are `ca` and `tsa`. **NOTE**: When support for publicly trusted TSA is available, `tsa:publicly-trusted-tsa` is the default value, and implied without explicitly specifying it. If a custom TSA is used the format `ca:acme-rockets,tsa:acme-tsa` is supported to specify it. - **`trustedIdentities`**(*array of strings*): This REQUIRED property specifies a set of identities that the user trusts. For X.509 PKI, it supports list of elements/attributes of the signing certificate's subject. For more information, see [identities constraints](#trusted-identities-constraints) section. A value `*` is supported if user trusts any identity (signing certificate) issued by the CA(s) in `trustStore`. #### Signature Verification details From 278977e3a433884042a90ce9d89fc0ed294a4376 Mon Sep 17 00:00:00 2001 From: Sajay Antony <1821104+sajayantony@users.noreply.github.com> Date: Tue, 12 Jul 2022 16:05:08 -0700 Subject: [PATCH 11/17] Fix notation-go-lib location (#173) Signed-off-by: Sajay Antony --- specs/plugin-extensibility.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/plugin-extensibility.md b/specs/plugin-extensibility.md index e378980e..4b922ded 100644 --- a/specs/plugin-extensibility.md +++ b/specs/plugin-extensibility.md @@ -20,7 +20,7 @@ Keys and associated certificates used for signing artifacts using Notary could b * Notation MUST work with a plugin that implements a matching or lower minor version of the plugin contract. Notation SHALL NOT support using a plugin with higher version of plugin contract. * A plugin MUST support a single plugin contract version, per major version. -Notation will invoke plugins as executable, pass parameters using command line arguments, and use standard IO streams to pass request/response payloads. This mechanism is used as Go language (used to develop [Notation library](https://github.com/notaryproject/notation-go-lib)) does not have a [in built support](https://github.com/golang/go/issues/19282) to load and execute plugins that works across OS platforms. Other mechanisms like gRPC require every plugin to be implemented as a service/daemon. +Notation will invoke plugins as executable, pass parameters using command line arguments, and use standard IO streams to pass request/response payloads. This mechanism is used as Go language (used to develop [Notation library](https://github.com/notaryproject/notation-go)) does not have a [in built support](https://github.com/golang/go/issues/19282) to load and execute plugins that works across OS platforms. Other mechanisms like gRPC require every plugin to be implemented as a service/daemon. ### Plugin lifecycle management From b226ac8d79ae4c3647d01801379bd241fe863429 Mon Sep 17 00:00:00 2001 From: Steve Lasker Date: Tue, 12 Jul 2022 17:20:09 -0700 Subject: [PATCH 12/17] Add mediaType per oras-project/artifacts-spec #78 (#168) Signed-off-by: Steve Lasker --- signature-envelope-jws.md | 1 + signature-specification.md | 1 + 2 files changed, 2 insertions(+) diff --git a/signature-envelope-jws.md b/signature-envelope-jws.md index fc37b99b..cc61df90 100644 --- a/signature-envelope-jws.md +++ b/signature-envelope-jws.md @@ -10,6 +10,7 @@ Signature Manifest Example ```jsonc { + "mediaType": "application/vnd.cncf.oras.artifact.manifest.v1+json", "artifactType": "application/vnd.cncf.notary.signature", "blobs": [ { diff --git a/signature-specification.md b/signature-specification.md index a6ff0afe..0651ee13 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -18,6 +18,7 @@ Signature Manifest Example ```jsonc { + "mediaType": "application/vnd.cncf.oras.artifact.manifest.v1+json", "artifactType": "application/vnd.cncf.notary.signature", "blobs": [ { From 814a8cbf55d4b6398dbc0d80e5d44c84abff5a17 Mon Sep 17 00:00:00 2001 From: Milind Gokarn Date: Wed, 20 Jul 2022 10:22:35 -0700 Subject: [PATCH 13/17] Support for Signing scheme (#175) * Support for Signing scheme * Added line breaks on sentences and fixes from review. * Renamed supported Signing Schemes * Add authority audit details. Signed-off-by: Milind Gokarn --- signature-envelope-jws.md | 39 ++++++++++++--- signature-specification.md | 6 ++- signing-scheme.md | 99 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 signing-scheme.md diff --git a/signature-envelope-jws.md b/signature-envelope-jws.md index cc61df90..18ce30fe 100644 --- a/signature-envelope-jws.md +++ b/signature-envelope-jws.md @@ -55,26 +55,51 @@ Example of Notary v2 payload The JWS envelope for Notary v2 uses following headers - Registered headers - `alg`, `cty`, and `crit` -- [Public headers](https://datatracker.ietf.org/doc/html/rfc7515#section-4.2) with collision resistant names - `io.cncf.notary.signingTime`, `io.cncf.notary.expiry` +- [Public headers](https://datatracker.ietf.org/doc/html/rfc7515#section-4.2) with collision resistant names. + - `io.cncf.notary.signingScheme` + - `io.cncf.notary.signingTime` + - `io.cncf.notary.authenticSigningTime` + - `io.cncf.notary.expiry` -Example +Example with Signing Scheme `notary.x509` ```jsonc { "alg": "PS384", "cty": "application/vnd.cncf.notary.payload.v1+json", + "io.cncf.notary.signingScheme": "notary.x509", "io.cncf.notary.signingTime": "2022-04-06T07:01:20Z", "io.cncf.notary.expiry": "2022-10-06T07:01:20Z", - "crit":["io.cncf.notary.expiry"] + "crit":[ + "io.cncf.notary.signingScheme", + "io.cncf.notary.expiry" + ] +} +``` + +Example with Signing Scheme `notary.x509.signingAuthority` + +```jsonc +{ + "alg": "PS384", + "cty": "application/vnd.cncf.notary.payload.v1+json", + "io.cncf.notary.signingScheme": "notary.x509.signingAuthority", + "io.cncf.notary.authenticSigningTime": "2022-04-06T07:01:20Z", + "io.cncf.notary.expiry": "2022-10-06T07:01:20Z", + "crit":[ + "io.cncf.notary.signingScheme", + "io.cncf.notary.authenticSigningTime", + "io.cncf.notary.expiry"] } ``` - **[`alg`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.1)**(*string*): This REQUIRED header defines which signing algorithm was used to generate the signature. JWS specification defines `alg` as a required header, that MUST be present and MUST be understood and processed by verifier. The signature algorithm of the signing key (first certificate in `x5c`) is the source of truth, and during signing the value of `alg` MUST be set corresponding to signature algorithm of the signing key using [this mapping](#supported-alg-header-values) that lists the Notary v2 allowed subset of `alg` values supported by JWS. Similarly verifier of the signature MUST match `alg` with signature algorithm of the signing key to mitigate algorithm substitution attacks. - **[`cty`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.10)**(*string*): The REQUIRED header content-type is used to declare the media type of the secured content (the payload). The supported value is `application/vnd.cncf.notary.payload.v1+json`. -- **`io.cncf.notary.signingTime`**(*string*): This REQUIRED header specifies the time at which the signature was generated. This is an untrusted timestamp, and therefore not used in trust decisions. Its value is a [RFC 3339][rfc3339] formatted date time, the optional fractional second ([time-secfrac][rfc3339][[1](https://datatracker.ietf.org/doc/html/rfc3339#section-5.3)]) SHOULD NOT be used. -- **`io.cncf.notary.expiry`**(*string*): This OPTIONAL header provides a “best by use” time for the artifact, as defined by the signer. Its value is a [RFC 3339][rfc3339] formatted date time, the optional fractional second ([time-secfrac][rfc3339][[1](https://datatracker.ietf.org/doc/html/rfc3339#section-5.3)]) SHOULD NOT be used. -- **[`crit`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.11)**(*array of strings*): This OPTIONAL header lists the headers that implementation MUST understand and process. It MUST only contain headers apart from registered headers (e.g. `alg`, `cty`) in JWS specification, therefore this header is only present when the optional `io.cncf.notary.expiry` header is present in the protected headers collection. - If present, the value MUST be `["io.cncf.notary.expiry"]`. +- **`io.cncf.notary.signingScheme`**(*string*)(critical): This REQUIRED header specifies the [Notary v2 Signing Scheme](./signing-scheme.md) used by the signature. Supported values are `notary.x509` and `notary.x509.signingAuthority`. +- **`io.cncf.notary.signingTime`**(*string*): This header specifies the time at which the signature was generated. This is an untrusted timestamp, and therefore not used in trust decisions. Its value is a [RFC 3339][rfc3339] formatted date time, the optional fractional second ([time-secfrac][rfc3339][[1](https://datatracker.ietf.org/doc/html/rfc3339#section-5.3)]) SHOULD NOT be used. This claim is REQUIRED and only valid when signing scheme is `notary.x509`. +- **`io.cncf.notary.authenticSigningTime`**(*string*)(critical): This header specifies the authenticated time at which the signature was generated. Its value is a [RFC 3339][rfc3339] formatted date time, the optional fractional second ([time-secfrac][rfc3339][[1](https://datatracker.ietf.org/doc/html/rfc3339#section-5.3)]) SHOULD NOT be used. This claim is REQUIRED and only valid when signing scheme is `notary.x509.signingAuthority` . +- **`io.cncf.notary.expiry`**(*string*)(critical): This OPTIONAL header provides a “best by use” time for the artifact, as defined by the signer. Its value is a [RFC 3339][rfc3339] formatted date time, the optional fractional second ([time-secfrac][rfc3339][[1](https://datatracker.ietf.org/doc/html/rfc3339#section-5.3)]) SHOULD NOT be used. +- **[`crit`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.11)**(*array of strings*): This REQUIRED (optional as per JWS spec, but required in Notary v2 JWS signature) header lists the headers that implementation MUST understand and process. It MUST only contain headers apart from registered headers (e.g. `alg`, `cty`) in JWS specification. This header MUST contain `io.cncf.notary.signingScheme` which is a required critical header, and optionally contain `io.cncf.notary.authenticSigningTime` and `io.cncf.notary.expiry` if these critical headers are present in the signature. ## Unprotected Headers diff --git a/signature-specification.md b/signature-specification.md index 0651ee13..11bebdff 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -127,9 +127,11 @@ Notary v2 requires the signature envelope to support the following signed attrib #### Standard attributes -- **Signing time**: A REQUIRED claim that indicates the time at which the signature was generated. Though this claim is signed by the signing key, it’s considered unauthenticated as a signer can modify local time and manipulate this claim. More details [here](#signing-time). +- **Signing Scheme** (critical): A REQUIRED claim that defines the [Notary v2 Signing Scheme](./signing-scheme.md) used by the signature. This attribute dictates the rest of signature schema - the set of signed and unsigned attributes to be included in the signature. Supported values are `notary.x509` and `notary.x509.signingAuthority`. +- **Signing Time**: A claim that indicates the time at which the signature was generated. Though this claim is signed by the signing key, it’s considered unauthenticated as a signer can modify local time and manipulate this claim. More details [here](#signing-time). This claim is REQUIRED and only valid when signing scheme is `notary.x509` . +- **Authentic Signing Time** (critical): The authenticated time at which the signature was generated. This claim is REQUIRED and only valid when signing scheme is `notary.x509.signingAuthority` . More details [here](#signing-time). - **Expiry** (critical): An OPTIONAL claim that provides a “best by use” time for the artifact, as defined by the signer. More details [here](#expiry). -- **Content type** (critical): A REQUIRED claim that indicates the content type of the [payload](#payload). The supported value is `application/vnd.cncf.notary.payload.v1+json`. Other payload types MAY be supported in future. +- **Content Type** (critical): A REQUIRED claim that indicates the content type of the [payload](#payload). The supported value is `application/vnd.cncf.notary.payload.v1+json`. Other payload types MAY be supported in future. ### Unsigned Attributes diff --git a/signing-scheme.md b/signing-scheme.md new file mode 100644 index 00000000..b7db5c64 --- /dev/null +++ b/signing-scheme.md @@ -0,0 +1,99 @@ +# Signing Scheme + +Signatures are primarily used to provide a consumer of an artifact the following guarantees - integrity (the signed artifact was not tampered after signing it), and authenticity (the artifact was indeed signed by the entity who claims to have signed it). +X.509 PKI based identities are commonly used to for signing artifacts. +It has well established standards for issuing and representing identities (CAs and certificates) which sign artifacts, mechanism to establish authenticity using trust stores, and support for managing key lifetimes and rotation, and revocation of identities. +There is interest in the community to support other ways to establish integrity and authenticity, based on other systems and techniques (Notary TUF, ledger based). +These approaches also can provide different feature sets than ones traditionally provided by standard X.509 PKI based signing. +E.g. TUF is geared for software update systems and provides a freshness guarantee. + +Notary v2 will initially support X.509 PKI identity based signing, but provide the flexibility for additional systems through an abstraction called Signing Scheme. +The Signing Scheme covers aspects of signature generation and verification, and formalizes the feature set (guarantees) provided by the signature produced using a signing scheme. +Generally it covers the following aspects, but can be extended to other aspects as required by newer systems. + +*Signature creation* + +1. Supported entities that can generate signatures - typically an end user generates the signature, other models can be supported using signature scheme. +1. Representation of identities - e.g. X.509 certificates are used to represent end users and entities which verify the end user’s authenticity (CAs). +1. Signature schema - Defines the signed and unsigned attributes to be included in a signature envelope, and which attributes are required, optional and critical. + +*Signature verification* + +1. Mechanism to establish trust in end users and other entities (like CAs). +1. Set of guarantees available to the verifier apart from integrity and authenticity. + +## Signing Scheme + +Notary v2 currently defines the following Signing Schemes. + +`notary.x509` - Defines a signing scheme that uses the traditional signing workflow in which an end user generates signatures using X.509 certificates. + +`notary.x509.signingAuthority` - Defines a signing scheme in which a `signing authority` generates signatures on behalf of an end user (the signature requestor) using X.509 certificates. +A trusted signing authority is defined as a third party service that is trusted both by the end user (the signature requestor) and verifying entity to generate signatures. +Authorities trusted by users should have mechanisms for validating, logging, monitoring, and auditing access to their systems and publish incidence response plans. +A certificate authority (CA) will need to demonstrate only validated entities were issued an X.509 certificate that chains to their root, and a time stamp authority (TSA) will need to demonstrate the time-stamp signing keys were only used within their service. +Similarly, a signing authority (SA) will need to demonstrate signing keys were only used within their service and only validated entities were allowed to generate signatures using the service. + +* A signature envelope can only specify one Signing Scheme +* When Notary supports an additional Signing Scheme + * Existing signed artifacts MUST be resigned if they need to be verified using the new signing scheme defined verification process. + * Existing clients used by verifying entity MUST be updated to newer versions that support verifying signatures that use the new signing scheme, otherwise the signatures with newer signing schemes which are unknown to existing clients will fail signature verification. Signatures that use older signing schemes which are known to existing clients will continue to verify correctly. + * The Notary verification policy language in *trustpolicy.json* MAY have breaking changes to support newer concepts/configuration elements introduced by the new signing scheme. + The breaking changes are addressed by introducing new major version in the versioned *trustpolicy.json* . + +## Signature Creation + +The signature envelope contains `Signing Scheme` as an required and critical attribute. +This attribute dictates the rest of signature schema - the set of signed and unsigned attributes to be included in the signature. + +Both `notary.x509` and `notary.x509.signingAuthority` signing schemes use the similar signature schema (set of signed and unsigned attributes) with the following differences. + +* `notary.x509` MUST use a countersignature from trusted source to determine authentic signing time (timestamp). +This is supported through the the Timestamp signature unsigned attribute in the signature envelope. Currently Notary V2 uses a [RFC3161](ietf-rfc3161) compliant TSA signature for this purpose. + +* `notary.x509.signingAuthority` MUST use a timestamp attribute that is generated by a signing service as part of the original signature itself to determine authenticated signing time (timestamp). +This is supported through the *Authentic Signing time* attribute in the signature envelope. + +## Signature Verification + +Signature verification requires that a `Signing Scheme` attribute is present in the signature and it’s treated as critical i.e. the attribute MUST be understood and processed by the verifier. +For the JWS signature format, the attribute name is `io.cncf.notary.signingScheme` , with supported values `notary.x509` and `notary.x509.signingAuthority`. +Any other value will fail signature verification i.e when Notary supports an additional Signing Scheme, clients (like *notation*) MUST be updated to a version that supports the new signing scheme. + +### Trust Stores + +Each *Signing Scheme* defines the set of trust store types (e.g. CA) that it uses for signature verification. + +`notary.x509` + +* Uses trusts store types Certificate Authority (CA) and Timestamping Authority (TSA) during signature verification. +The signature is verified against the trust store of type CA, and the *Timestamp signature* is verified against the trust store of type TSA (to determine the signing time). +* For signature verification to be successful + * The verifying entity’s trust store MUST contain the trusted root certificates under named trust stores of type CA (`{CONFIG}/notation/truststore/x509/ca`) and TSA(`{CONFIG}/notation/truststore/x509/tsa`) + * The named trust stores MUST be specified in *trustpolicy.json*. E.g. *trustPolicy.trustStores* with value of `ca:acme-rockets,tsa:acme-tsa`. + +`notary.x509.signingAuthority` + +* Uses trusts store type Signing Authority during signature verification. +The signature is verified against the trust store of type Signing Authority. +The signing time is determined using the *Authentic Signing time* attribute in the signature envelope, and does not rely on a seperate TSA generated Timestamp signature. +* For signature verification to be successful + * The verifying entity’s trust store MUST contain the trusted root certificates under named trust stores of type CA (`{CONFIG}/notation/truststore/x509/signingAuthority`). + * The named trust stores MUST be specified in *trustpolicy.json*. E.g. *trustPolicy.trustStores* with value of `signingAuthority:foobar` . + +## FAQ + +*Q:* What is the relationship of Signing Scheme with Signature Envelope format? + +*A:* Signing Scheme aims to be agnostic of the Signature Envelope format. +A given signing scheme can be implemented through any signature envelope format (such as JWS or COSE) as long as it can support the required signature schema used by the signing scheme. + +*Q:* Why is the trust store used for Signing Authority (`x509/signingAuthority`) distinct from trust store for Certificate Authority (`x509/ca`), why can’t they share the same trust store? + +*A:* Signing Authority is a different type of trusted entity as compared to Certificate Authority (CA) or Timestamping Authority (TSA). +A CA is trusted for verifying the identity of a signing entity (end user) and issuing it a certificate, whereas a TSA is trusted to generate authentic timestamp. +In contrast, an SA is trusted to generate signatures on behalf of an end user (signature requestor) and also to generate authentic timestamp as part of the signature. +If we use a shared trust store for CA and SA, a verifying entity does not have the ability to differentiate between CA and SA when the verifying entity configures trusted roots in the trust store. +This has implication such as an end user with CA issued certificate can masquerade themselves as an SA by generating a signature with signing scheme `notary.x509.signingAuthority`. + +[ietf-rfc3161]: https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2 From 8a09eb9ef7be82cfe92aae0e7d2169a1520e7972 Mon Sep 17 00:00:00 2001 From: Milind Gokarn Date: Thu, 21 Jul 2022 16:26:30 -0700 Subject: [PATCH 14/17] Support multiple trust stores and custom verification level (#164) * Support multiple trust stores and custom verification level * Simplified specifying custom verification level. * Fix custom verification level example * Changes `signatureVerification` to be an object instead of multi-typed attribute. - Updated example policies. Signed-off-by: Milind Gokarn --- signature-envelope-jws.md | 2 +- signature-specification.md | 4 +- trust-store-trust-policy-specification.md | 120 ++++++++++++++++------ 3 files changed, 89 insertions(+), 37 deletions(-) diff --git a/signature-envelope-jws.md b/signature-envelope-jws.md index 18ce30fe..3ee2031b 100644 --- a/signature-envelope-jws.md +++ b/signature-envelope-jws.md @@ -114,7 +114,7 @@ Notary v2 supports following unprotected headers: `timestamp`, `x5c` and `io.cnc ``` - **[`x5c`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6)** (*array of strings*): This REQUIRED header contains the ordered list of X.509 certificate or certificate chain([RFC5280](https://datatracker.ietf.org/doc/html/rfc5280)) corresponding to the key used to digitally sign the JWS. The certificate chain is represented as a JSON array of certificate value strings, each string in the array is a base64-encoded DER certificate value. The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate, followed by the intermediate and root certificates in the correct order. Refer [*Certificate Chain* unsigned attribute](signature-specification.md#unsigned-attributes) for more details. -- **`io.cncf.notary.timestampSignature`** (*string*): This OPTIONAL header is used to store countersignature that provides trusted signing time. Only [RFC3161]([rfc3161](https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2)) compliant `TimeStampToken` are supported. +- **`io.cncf.notary.timestampSignature`** (*string*): This OPTIONAL header is used to store countersignature that provides authentic signing time. Only [RFC3161]([rfc3161](https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2)) compliant `TimeStampToken` are supported. - **TODO** Define the opaque datum (hash of envelope) that is sent to TSA, and how TSA response (time stamp token) is represented in this header. - **`io.cncf.notary.signingAgent`**(*string*): This OPTIONAL header provides the identifier of a client (e.g. Notation) that produced the signature. E.g. “notation/1.0.0”. Refer [*Signing Agent* unsigned attribute](signature-specification.md#unsigned-attributes) for more details. diff --git a/signature-specification.md b/signature-specification.md index 11bebdff..b27dd534 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -138,7 +138,7 @@ Notary v2 requires the signature envelope to support the following signed attrib These attributes are considered unsigned with respect to the signing key that generates the signature. These attributes are typically signed by a third party (e.g. CA, TSA). - **Certificate Chain**: This is a REQUIRED attribute that contains the ordered list of X.509 public certificates associated with the signing key used to generate the signature. The ordered list starts with the signing certificates, any intermediate certificates and ends with the root certificate. The certificate chain MUST be authenticated against a trust store as part of signature validation. Specific requirements for the certificates in the chain are provided [here](#certificate-requirements). -- **Timestamp signature** : An OPTIONAL counter signature which provides [trusted timestamp](#signing-time)e.g. Time Stamp Authority (TSA) generated timestamp signature. Only [RFC3161](ietf-rfc3161) compliant TimeStampToken are currently supported. +- **Timestamp signature** : An OPTIONAL counter signature which provides [authentic timestamp](#signing-time)e.g. Time Stamp Authority (TSA) generated timestamp signature. Only [RFC3161](ietf-rfc3161) compliant TimeStampToken are currently supported. - **Signing Agent**: An OPTIONAL claim that provides the identifier of the software (e.g. Notation) that produced the signature on behalf of the user. It is an opaque string set by the software that produces the signature. It's intended primarily for diagnostic and troubleshooting purposes, this attribute is unsigned, the verifier MUST NOT validate formatting, or fail validation based on the content of this claim. The suggested format is one or more tokens of the form `{id}/{version}` containing identifier and version of the software, seperated by spaces. E.g. “notation/1.0.0”, “notation/1.0.0 com.example.nv2plugin/0.8”. ## Signature Algorithm Requirements @@ -222,7 +222,7 @@ For [JWS JSON serialization](./signature-envelope-jwt.md) signature envelope, ve ### Signing time -The signing time denotes the time at which the signature was generated. A X509 certificate has a defined [validity](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5) during which it can be used to generate signatures. The signing time must be greater than or equal to certificate's `notBefore` attribute, and signing time must be less than or equal to certificate's `notAfter` attribute. Signatures generated after the certificate expires are considered invalid. A trusted timestamp, like TSA countersignature, allows a verifier to determine if the signature was generated when the certificate was valid. It also allows a verifier to determine if a signature be treated as valid when a certificate is revoked, if the certificate was revoked after the signature was generated. In the absence of a trusted timestamp, signatures are considered invalid after certificate expires, and all signatures are considered revoked when a certificate is revoked. +The signing time denotes the time at which the signature was generated. A X509 certificate has a defined [validity](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5) during which it can be used to generate signatures. The signing time must be greater than or equal to certificate's `notBefore` attribute, and signing time must be less than or equal to certificate's `notAfter` attribute. Signatures generated after the certificate expires are considered invalid. An authentic timestamp, like TSA countersignature, allows a verifier to determine if the signature was generated when the certificate was valid. It also allows a verifier to determine if a signature be treated as valid when a certificate is revoked, if the certificate was revoked after the signature was generated. In the absence of an authentic timestamp, signatures are considered invalid after certificate expires, and all signatures are considered revoked when a certificate is revoked. ### Expiry diff --git a/trust-store-trust-policy-specification.md b/trust-store-trust-policy-specification.md index 85068334..af8625f5 100644 --- a/trust-store-trust-policy-specification.md +++ b/trust-store-trust-policy-specification.md @@ -38,6 +38,8 @@ $XDG_CONFIG_HOME/notation/trust-store cert2.pem /sub-dir # sub directory is ignored cert-3.pem # certs under sub directory is ignored + /acme-rockets-ca2 + cert1.pem /wabbit-networks cert3.crt /tsa @@ -68,11 +70,13 @@ Trust policy for a simple scenario where ACME Rockets uses only artifacts signed "trustPolicies": [ { // Policy for all artifacts, from any registry location. - "name": "wabbit-networks-images", - "registryScopes": [ "*" ], - "signatureVerification" : "audit", - "trustStore": "ca:acme-rockets", - "trustedIdentities": [ + "name": "wabbit-networks-images", // Name of the policy. + "registryScopes": [ "*" ], // The registry artifacts to which the policy applies. + "signatureVerification": { // The level of verification - strict, permissive, audit, skip. + "level" : "audit" + }, + "trustStores": ["ca:acme-rockets"], // The trust stores that contains the X.509 trusted roots. + "trustedIdentities": [ // Identities that are trusted to sign the artifact. "x509.subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io, OU=Finance, CN=SecureBuilder" ] } @@ -80,7 +84,7 @@ Trust policy for a simple scenario where ACME Rockets uses only artifacts signed } ``` -Trust policy for the scenario where ACME Rockets uses artifacts signed by themselves, and signed and unsigned artifacts from Wabbit Networks: +Trust policy for the scenario where ACME Rockets uses some artifacts signed by Wabbit Networks and some signed by ACME Rockets. ```jsonc { @@ -94,8 +98,10 @@ Trust policy for the scenario where ACME Rockets uses artifacts signed by themse "registry.acme-rockets.io/software/net-monitor", "registry.acme-rockets.io/software/net-logger" ], - "signatureVerification" : "strict", - "trustStore": "ca:acme-rockets", + "signatureVerification": { + "level" : "strict" + }, + "trustStores": ["wabbit-networks"], "trustedIdentities": [ "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Security Tools" ] @@ -105,30 +111,34 @@ Trust policy for the scenario where ACME Rockets uses artifacts signed by themse // Wabbit Networks repository "name": "unsigned-image", "registryScopes": [ "registry.wabbit-networks.io/software/unsigned/net-utils" ], - "signatureVerification": "skip", + "signatureVerification": { + "level" : "skip" + } }, { - // Policy for with custom verification policy + // Policy that uses custom verification level to relax the strict verification. + // It logs expiry and skips recovocation check for a specific artifact. "name": "use-expired-image", "registryScopes": [ "registry.acme-rockets.io/software/legacy/metrics" ], - "signatureVerification": - { + "signatureVerification": { "level" : "strict", - "override" : - { - "expiry" : "skip" + "override" : { + "expiry" : "log", + "revocation" : "skip" } }, - "trustStore": "ca:acme-rockets", + "trustStores": ["ca:acme-rockets"], "trustedIdentities": ["*"] }, { // Policy for all other artifacts signed by ACME Rockets - // from any registry location + // from any registry location. The policy also specified multiple trust stores. "name": "global-policy-for-all-other-images", "registryScopes": [ "*" ], - "signatureVerification" : "audit", - "trustStore": "ca:acme-rockets", + "signatureVerification": { + "level" : "audit" + }, + "trustStores": ["ca:acme-rockets", "ca:acme-rockets-ca2"], "trustedIdentities": [ "x509.subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io, OU=Finance, CN=SecureBuilder" ] @@ -146,37 +156,79 @@ Trust policy for the scenario where ACME Rockets uses artifacts signed by themse - **`registryScopes`**(*array of strings*): This REQUIRED property determines which trust policy is applicable for the given artifact. The scope field supports filtering based on fully qualified repository URI `${registry-name}/${namespace}/${repository-name}`. For more information, see [registry scopes constraints](#registry-scopes-constraints) section. - - **`signatureVerification`**(*string*): This REQUIRED property dictates how signature verification is performed. Supported values are `strict`, `permissive`, `audit` and `skip`. Detailed explaination of each value is present [here](#signatureverification-details). A custom level can be defined by referencing a supported level value, and overriding individual validation checks. NOTE: Custom signature verification level will not be supported in `notation` RC1. - - **`trustStore`**(*string*): This REQUIRED property specifies a named trust store. Uses the format `{trust-store-type}:{named-store}`. Currently supported values for `trust-store-type` are `ca` and `tsa`. **NOTE**: When support for publicly trusted TSA is available, `tsa:publicly-trusted-tsa` is the default value, and implied without explicitly specifying it. If a custom TSA is used the format `ca:acme-rockets,tsa:acme-tsa` is supported to specify it. + - **`signatureVerification`**(*object*): This REQUIRED property dictates how signature verification is performed. + An *object* that specifies a predefined verification level, with an option to override Notary v2 defined verification level if user wants to specify a [custom verification level](#custom-verification-level). + - **`level` **(*string*): A REQUIRED property that specifies the verification level, supported values are `strict`, `permissive`, `audit` and `skip`. Detailed explaination of each level is present [here](#signatureverification-details). + - **`override` **(*map of string-string*): This OPTIONAL map is used to specify a [custom verification level](#custom-verification-level). + - **`trustStores`**(*array of string*): This REQUIRED property specifies a set of one or more named trust stores, each of which contain the trusted roots against which signatures are verified. Each named trust store uses the format `{trust-store-type}:{named-store}`. Currently supported values for `trust-store-type` are `ca` and `tsa`. + - **NOTE**: When support for publicly trusted TSA is available, `tsa:publicly-trusted-tsa` is the default value, and implied without explictly specifying it. If a custom TSA is used the format `ca:acme-rockets,tsa:acme-tsa` is supported to specify it. - **`trustedIdentities`**(*array of strings*): This REQUIRED property specifies a set of identities that the user trusts. For X.509 PKI, it supports list of elements/attributes of the signing certificate's subject. For more information, see [identities constraints](#trusted-identities-constraints) section. A value `*` is supported if user trusts any identity (signing certificate) issued by the CA(s) in `trustStore`. #### Signature Verification details -Notary v2 defines the following signature verification levels, to provide different levels of enforcement for different scenarios. Signature verification is a multi step process performs the following validation checks - integrity (artifact is unaltered, signature is not corrupted), authenticity (the signature is really from the identity that claims to have signed it), trusted timestamping (the signature was generated when the key/certificate were unexpired), expiry (an optional check if the artifact specifies an expiry time), revocation check (is the signing identity still trusted at the present time). Based on the signature verification level, each of these validation checks is `enforced` or `logged`. If a validation check is `enforced`, a failure is treated as critical failure, and causes the overall signature verification to fail. If a validation check is `logged`, a failure causes details to be logged, and the next validation check is evaluated till all checks succeed or a critical failure is encountered. NOTE: Implementations may change the ordering of these checks based on efficiency, but all validation checks MUST to be performed till the first critical failure is encountered, or all checks succeed, for signature verification process to be considered complete. - -- `strict` : Signature verification is performed at `strict` level, which enforces all of the signature verification checks. If any of these checks fails, the signature verification fails. This is the recommended level in environments where a signature verification failure does not have high impact to other concerns (like application availability). It is recommended that build and development environments where images are initially injested, or at high assurance at deploy time use `strict` level. -- `permissive` : The `permissive` level enforces most signature verification checks, but will only logs failures for revocation and expiry. The `permissive` level is recommended to be used if signature verification is done at deploy time or runtime, and the user only needs integrity and authenticity guarantees. -- `audit` : The `audit` level only enforces integrity check if a signature is present. Failure of all other checks are only logged. + - Signature verification is a multi step process performs the following validations + - integrity (artifact is unaltered, signature is not corrupted) + - authenticity (the signature is really from the identity that claims to have signed it) + - trusted timestamping (the signature was generated when the key/certificate were unexpired) + - expiry (an optional check if the artifact specifies an expiry time) + - revocation check (is the signing identity still trusted at the present time). + - Based on the signature verification level, each of these validations is *enforced* or *logged*. + - If a validation is *enforced*, a failure is treated as critical failure, and causes the overall signature verification to fail. + - If a validation is *logged*, a failure causes details to be logged, and the next validation is evaluated till all validations succeed or a critical failure is encountered. +- Implementations may change the ordering of these validations based on efficiency, but all validation MUST be performed till the first critical failure is encountered, or all validation succeed, for the overall signature verification process to be considered complete. + + Notary v2 defines the following signature verification levels to provide different levels of enforcement for different scenarios. + +- `strict` : Signature verification is performed at `strict` level, which enforces all validations. If any of these validations fail, the signature verification fails. This is the recommended level in environments where a signature verification failure does not have high impact to other concerns (like application availability). It is recommended that build and development environments where images are initially injested, or for high assurance at deploy time use `strict` level. +- `permissive` : The `permissive` level enforces most validations, but will only logs failures for revocation and expiry. The `permissive` level is recommended to be used if signature verification is done at deploy time or runtime, and the user only needs integrity and authenticity guarantees. +- `audit` : The `audit` level only enforces signature integrity if a signature is present. Failure of all other validations are only logged. - `skip` : The `skip` level does not fetch signatures for artifacts and does not perform any signature verification. This is useful when an application uses multiple artifacts, and has a mix of signed and unsigned artifacts. Note that `skip` cannot be used with a global scope (`*`), the value of `registryScopes` MUST contain fully qualified registry URL(s). -The following table shows the resultant behavior `enforced` (verification fails), or `logged` for each of the checks, based on signature verification level. +The following table shows the resultant validation action, either *enforced* (verification fails), or *logged* for each of the checks, based on signature verification level. -|Signature Verification Level|Recommended Usage|Integrity|Authenticity|Trusted timestamp|Expiry|Revocation check| +|Signature Verification Level|Recommended Usage|||Validations||| |----------------------------|-----------------|---------|------------|-----------------|------|----------------| -|strict |Use at development, build and deploy time|enforced|enforced|enforced|enforced|enforced| -|permissive|Use at deploy time or runtime|enforced|enforced|logged|logged|logged| -|audit |Use when adopting signed images, without breaking existing workflows|enforced|logged|logged|logged|logged| -|skip |Use to exclude verification for unsigned images|skipped|skipped|skipped|skipped|skipped| +|||*Integrity*|*Authenticity*|*Authentic timestamp*|*Expiry*|*Revocation check*| +|*strict* |Use at development, build and deploy time|enforced|enforced|enforced|enforced|enforced| +|*permissive*|Use at deploy time or runtime|enforced|enforced|logged|logged|logged| +|*audit* |Use when adopting signed images, without breaking existing workflows|enforced|logged|logged|logged|logged| +|*skip* |Use to exclude verification for unsigned images|skipped|skipped|skipped|skipped|skipped| **Integrity** : Guarantees that the artifact wasn't altered after it was signed, or the signature isn't corrupted. All signature verification levels always enforce integrity. **Authenticity** : Guarantees that the artifact was signed by an identity trusted by the verifier. Its definition does not include revocation, which is when a trusted identity is subsequently untrusted because of a compromise. -**Trusted timestamp** : Guarantees that the signature was generated when the certificate was valid. It also allows a verifier to determine if a signature be treated as valid when a certificate is revoked, if the certificate was revoked after the signature was generated. In the absence of a trusted timestamp, signatures are considered invalid after certificate expires, and all signatures are considered revoked when a certificate is revoked. **NOTE**: `notation` RC1 will generate trusted timestamp using a TSA when the signature is generated, but will not support verification of TSA countersignatures. +**Authentic timestamp** : Guarantees that the signature was generated when the certificate was valid. It also allows a verifier to determine if a signature must be treated as valid when a certificate is revoked, if the certificate was revoked after the signature was generated. In the absence of a authentic timestamp, signatures are considered invalid after certificate expires, and all signatures are considered revoked when a certificate is revoked. + +- **NOTE**: `notation` RC1 will generate trusted timestamp using a TSA when the signature is generated, but will not support verification of TSA countersignatures. Related issue - [#59](https://github.com/notaryproject/roadmap/issues/59). **Expiry** : This is an optional feature that guarantees that artifact is within “best by use” date indicated in the signature. Notary v2 allows users to include an optional expiry time when they generate a signature. The expiry time is not set by default and requires explicit configuration by users at the time of signature generation. The artifact is considered expired when the current time is greater than or equal to expiry time, users performing verification can either configure their trust policies to fail the verification or even accept the artifact with expiry date in the past using policy. This is an advanced feature that allows implementing controls for user defined semantics like deprecation for older artifacts, or block older artifacts in a production environment. Users should only include an expiry time in the signed artifact after considering the behavior they expect for consumers of the artifact after it expires. Users can choose to consume an artifact even after the expiry time based on their specific needs. -**Revocation check** : Guarantees that the signing identity is still trusted at signature verification time. Events such as key or system compromise can make a signing identity that was previously trusted, to be subsequently untrusted. This guarantee typically requires a verification-time call to an external system, which may not be consistently reliable. The `permissive` verification level only logs failures of revocation check and does not enforce it. If a particular revocation mechanism is reliable, use `strict` verification level instead. **NOTE** `notation` RC1 will not support revocation check. +**Revocation check** : Guarantees that the signing identity is still trusted at signature verification time. Events such as key or system compromise can make a signing identity that was previously trusted, to be subsequently untrusted. This guarantee typically requires a verification-time call to an external system, which may not be consistently reliable. The `permissive` verification level only logs failures of revocation check and does not enforce it. If a particular revocation mechanism is reliable, use `strict` verification level instead. + +- **NOTE** : `notation` RC1 will not have in built support for Revocation Check feature, see [Key Management - Rescinding Signatures (CRL)](https://github.com/notaryproject/notaryproject/issues/72) for details. When this feature is subsequently implemented, the `strict` verification level will **enforce** this validation, and will fail signatures which would previously pass signature verification when revocation checks were not supported. This is considered a **breaking change**, but is the appropriate behavior for a `strict` verification level. + +#### Custom Verification Level + +Signature verification levels provide Notary v2 defined behavior for each validation e.g. `strict` will always *enforce* authenticity validation. For fine grained control over validations that occur during signature verification, users can define a custom level which overrides the behavior of an existing verification level. + +- To use this feature, the `level` property MUST be specified along with an OPTIONAL `override` map. +- Supported values for `level` are - `strict`, `permissive` and `audit`. A `skip` level cannot be customized. +- Supported keys for `override` map and their supported values are as follows. + - `integrity` validation cannot be overriden, and therefore cannot be specified as a key. + - `authenticity` - Supported values are `enforce` and `log`. + - `authenticTimestamp` - Supported values are `enforce` and `log`. + - `expiry` - Supported values are `enforce` and `log`. + - `revocation` - Supported values are `enforce`, `log`, and `skip`. + +```jsonc + "signatureVerification": { + "level" : "strict", + "override" : { + "expiry" : "log" + } + } +``` #### Registry Scopes Constraints From c4400ebb2da98bce89e66cc8fadeddae564e2b61 Mon Sep 17 00:00:00 2001 From: Milind Gokarn Date: Mon, 25 Jul 2022 10:56:20 -0700 Subject: [PATCH 15/17] Verification Plugin spec (#165) * Verification Plugin and Signing Scheme spec * Updated JWS envelope and other changes * Added guidelines for Notary v2 and plugin implementors. * Added details for Signature Portability * Updated wording for private artifacts Signed-off-by: Milind Gokarn --- signature-envelope-jws.md | 7 + signature-specification.md | 72 +++++++++++ specs/plugin-extensibility.md | 234 +++++++++++++++++++++++++++++----- 3 files changed, 281 insertions(+), 32 deletions(-) diff --git a/signature-envelope-jws.md b/signature-envelope-jws.md index 3ee2031b..f2eb4824 100644 --- a/signature-envelope-jws.md +++ b/signature-envelope-jws.md @@ -101,6 +101,13 @@ Example with Signing Scheme `notary.x509.signingAuthority` - **`io.cncf.notary.expiry`**(*string*)(critical): This OPTIONAL header provides a “best by use” time for the artifact, as defined by the signer. Its value is a [RFC 3339][rfc3339] formatted date time, the optional fractional second ([time-secfrac][rfc3339][[1](https://datatracker.ietf.org/doc/html/rfc3339#section-5.3)]) SHOULD NOT be used. - **[`crit`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.11)**(*array of strings*): This REQUIRED (optional as per JWS spec, but required in Notary v2 JWS signature) header lists the headers that implementation MUST understand and process. It MUST only contain headers apart from registered headers (e.g. `alg`, `cty`) in JWS specification. This header MUST contain `io.cncf.notary.signingScheme` which is a required critical header, and optionally contain `io.cncf.notary.authenticSigningTime` and `io.cncf.notary.expiry` if these critical headers are present in the signature. +## Extended Protected Headers for *Notation* Plugins + +See [Extended attributes for *Notation* Plugins](./signature-specification.md#extended-attributes-for-notation-plugins) for detailed description of these headers. + +- **`io.cncf.notary.verificationPlugin`**(*string*)(critical): An OPTIONAL header that specifies the name of the verification plugin that MAY be used to verify the signature. +- **`io.cncf.notary.verificationPluginMinVersion`**(*string*)(critical): An OPTIONAL header that specifies the minimum version of the verification plugin that MUST be used to verify the signature. + ## Unprotected Headers Notary v2 supports following unprotected headers: `timestamp`, `x5c` and `io.cncf.notary.signingAgent` diff --git a/signature-specification.md b/signature-specification.md index b27dd534..e1600e75 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -133,6 +133,30 @@ Notary v2 requires the signature envelope to support the following signed attrib - **Expiry** (critical): An OPTIONAL claim that provides a “best by use” time for the artifact, as defined by the signer. More details [here](#expiry). - **Content Type** (critical): A REQUIRED claim that indicates the content type of the [payload](#payload). The supported value is `application/vnd.cncf.notary.payload.v1+json`. Other payload types MAY be supported in future. +#### Extended attributes + +Implementations of Notary v2 signature spec MAY include additional signed attributes in the signature envelope. +These attributes MAY be marked critical, i.e. the attribute MUST be understood and processed by a verifier, unknown critical attributes MUST cause signature verification to fail. +Usage of extended signed attributes which are marked critical in signature will have implications on portability of the signature, these are discussed in [Signature Portability](#signature-portability) section. + +#### Extended attributes for *Notation* Plugins + +This section documents extended attributes used by Notary v2 reference implementation *Notation* to support plugins. +Plugins is a *Notation* concept that allows parts of signing and verification logic to be performed by an external provider. +*Signing plugins* allow *Notation* to be extended for integration with remote keys remote key management services and signing services, where as *verification plugins* allow for customization of verification logic. +Detailed specification for plugins can be found [here](https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#notation-extensibility-for-signing-and-verification). +These extended attributes are documented in this spec, as other Notary V2 implementations may encounter these attributes if they verify a signature that indicated it required a verification plugin for complete signature verification. + +- **Verification Plugin** (critical): An OPTIONAL attribute that specifies the name of the verification plugin that MAY be used to verify the signature e.g. “com.example.nv2plugin”. +[Notation plugin](https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#plugin-contract) aware implementations use this attribute to load and execute a *Notation* compliant plugin. +The plugin participates in the overall signature verification workflow and performs specific steps in it. +- **Verification Plugin Minimum Version** (critical): An OPTIONAL attribute that specifies the minimum version of the verification plugin that MUST be used to verify the signature. +A Notation plugin aware implementations MUST use this attribute to verify the signature with a plugin with matching or higher plugin version. +The plugin MUST use [Semantic Versioning](https://semver.org/) (SemVer) to use this feature i.e the `get-plugin-metadata` plugin command MUST return a SemVer compliant version in the response. +A use case for this feature is for a plugin publisher to address security bug in older plugin version, by setting the minimum version to the plugin version with fixes. + +See [Guidelines for Notary v2 Implementors](#guidelines-for-notary-v2-implementors) for options to handle these attributes during signature verification. + ### Unsigned Attributes These attributes are considered unsigned with respect to the signing key that generates the signature. These attributes are typically signed by a third party (e.g. CA, TSA). @@ -228,6 +252,54 @@ The signing time denotes the time at which the signature was generated. A X509 c This is an optional feature that provides a “best by use” time for the artifact, as defined by the signer. Notary v2 allows users to include an optional expiry time when they generate a signature. The expiry time is not set by default and requires explicit configuration by users at the time of signature generation. The artifact is considered expired when the current time is greater than or equal to expiry time, users performing verification can either configure their trust policies to fail the verification or even accept the artifact with expiry date in the past using policy. This is an advanced feature that allows implementing controls for user defined semantics like deprecation for older artifacts, or block older artifacts in a production environment. Users should only include an expiry time in the signed artifact after considering the behavior they expect for consumers of the artifact after it expires. Users can choose to consume an artifact even after the expiry time based on their specific needs. +### Signature Portability + +Portability of signatures is associated with the portability of associated artifacts which are being signed. +OCI artifacts are inherently location agnostic, artifacts can be pulled from and pushed to any OCI compliant registry to which a user has access. +The artifacts themselves can be classified as follow. + +1. *Public Artifacts* - Artifacts that are distributed publicly for broad consumption. +Artifacts distributed via public registries fall in this category. +E.g. Public images for software distributed by software vendors, and open source projects. +Signatures associated with these artifacts require broad portability. +1. *Private Artifacts* - Artifacts that are private to a user or organization, and may be shared with limited parties. +E.g. Images for containerized applications and services used within an organization, or shared with limited authorized parties. +Based on user requirements a private artifact can have different levels of portability, the signature’s portability should at least match the the artifact’s portability. + +*Notary v2 signature portability* is based on the following + +**Signature discovery** + +Notary v2 addressed signature discovery by storing signatures in the same registry (location) where an artifact is present. +This is supported through [ORAS artifact spec](https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md) which allows reference artifacts such as signatures, SBOMs to be associated with existing artifacts like Images. +Notary v2 allows multiple signatures to be associated with an artifact, and clients may automatically push signatures for an artifact to a destination registry when a signed artifact moves from one registry to other. + +**Verification requirements** + +Notary v2 supports a range of signed artifacts intended for public and private distribution. +Signatures generated by Notary v2 without extended signature attributes marked critical can be verified in any environment where Notation client or another Notary v2 standards compliant verification tool is available, without any additional dependencies. +It should be noted that revocations checks, which usually relies on an external mechanism such as CRL/OCSP may require the verification environment to have access to local network or public internet, to have access to the CRL/OCSP endpoint. + +Notary v2 also supports signatures generated using compliant signing plugins, which allow vendors to optionally provide additional features on top of Notary v2 standard features. +Verification of these signatures may require additional dependencies like Notary v2 compliant verification plugin, making these signatures more appropriate to use where broad portability may not be required for the associated signed artifact. +This allows users to implement security controls required for their organizations, that are not broadly applicable and may take time to standardize. +E.g. Integration with a signature transparency log as part of signature verification. + +Based on user’s requirements, a user can select appropriate signing mechanism that produces signatures with desired portability. +Notation signatures without any critical extended attributes do not impose any additional dependency requirements for verifiers as these can be validated with just the Notation client. +Whereas, Notation signatures that contain critical extended attributes will require additional dependencies for signature validation, either on Notary v2 compliant plugins or equivalent tooling which may not be available in all environments. +Similarly, Notary v2 compliant plugin vendors should be aware that usage of extended signed attributes which are marked critical in signature will have implications on portability of the signature. + +### Guidelines for Notary v2 Implementors + +Implementations of Notary v2, can choose to be [Notation plugin protocol](./specs/plugin-extensibility.md#plugin-contract) aware or not. If an implementation chooses to be plugin protocol aware, and it encounters the Verification Plugin and Verification Plugin minimum version attributes during signature verification, it MUST process these attributes. This involves finding the appropriate plugin and the version to use, and executing `verify-signature` plugin command with correct inputs and processing the plugin response, as per the [Verification Plugin interface](../specs/../notaryproject-specs/specs/plugin-extensibility.md#verification-extensibility). + +Alternatively, an implementation of Notary v2 can choose not to implement plugin protocol. + +- The implementation MUST itself perform equivalent verification logic that is usually performed by plugin specified in the signature. +- An implementation MUST fail signature verification if it cannot perform the equivalent verification logic, as skipping the plugin equivalent verification logic will cause incorrect and inconsistent signature verification behavior. +- An implementation MAY choose to support a set of known plugin’s verification logic and fail others. + [annotation-rules]: https://github.com/opencontainers/image-spec/blob/main/annotations.md#rules [oci-descriptor]: https://github.com/opencontainers/image-spec/blob/main/descriptor.md [artifact-descriptor]: https://github.com/oras-project/artifacts-spec/blob/main/descriptor.md diff --git a/specs/plugin-extensibility.md b/specs/plugin-extensibility.md index 4b922ded..c8129ac7 100644 --- a/specs/plugin-extensibility.md +++ b/specs/plugin-extensibility.md @@ -1,6 +1,6 @@ # Notation Extensibility for Signing and Verification -Keys and associated certificates used for signing artifacts using Notary could be available to users through varied solutions that provide secure key generation, storage and cryptographic operations. Some are well established with standards like [PIV](https://csrc.nist.gov/projects/piv/piv-standards-and-supporting-documentation) and [PKCS #11](https://docs.oasis-open.org/pkcs11/pkcs11-base/v3.0/os/pkcs11-base-v3.0-os.html) implemented by hardware tokens, smart cards. More recent options which use varied authentication and API protocols are remote key management services and signing services by third party vendors and cloud service providers. Notation will support a few built-in integrations with standard providers, and will provide plugin interfaces for users, and vendors to implement their own integrations with the solutions they use. This allows a plugin publisher to implement, test, release and patch their solutions independent of Notation’s development and release cycle. This document provides specification for the plugin model, and interfaces to implement. This specification aims to work both for [existing](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md) and future signature formats adopted by Notary. +Keys and associated certificates used for signing artifacts using Notary could be available to users through varied solutions that provide secure key generation, storage and cryptographic operations. Some are well established with standards like [PIV](https://csrc.nist.gov/projects/piv/piv-standards-and-supporting-documentation) and [PKCS #11](https://docs.oasis-open.org/pkcs11/pkcs11-base/v3.0/os/pkcs11-base-v3.0-os.html) implemented by hardware tokens, smart cards. More recent options which use varied authentication and API protocols are remote key management services and signing services by third party vendors and cloud service providers. Notation will support a few built-in integrations with standard providers, and will provide plugin interfaces for users, and vendors to implement their own integrations with the solutions they use. This allows a plugin publisher to implement, test, release and patch their solutions independent of Notation’s development and release cycle. This document provides specification for the plugin model, and interfaces to implement. This specification aims to work both for [existing](../signature-specification.md) and future signature formats adopted by Notary. ## Terminology @@ -102,6 +102,7 @@ Plugin config can be also set/overriden during signing with the `notation sign` * Notation will invoke the plugin executable for each command (e.g. sign, verify), pass inputs through `stdin` and get output through `stdout` and `stderr`. * The command will be passed as the first argument to the plugin e.g. `notary-{plugin-name} `. A JSON request is passed using `stdin`. The plugin is expected to return a JSON response through `stdout` with a `0` exit code for successful response, and a non-zero exit code with a JSON error response in `stderr` for error response. Each command defines its request, response and error contract. To avoid any additional content like debug or info level logging from dependencies and inbuilt libraries, the plugin implementation should redirect any output to `stdout` on initialization, and only send the JSON response away from `stdout` when the command execution completes. E.g. For golang, set [`os.Stdout`](https://pkg.go.dev/os#pkg-variables) to point to a log file. * Every request JSON will contain a `contractVersion` top level attribute whose value will indicate the plugin contract version. Contract version is revised when there are changes to command request/response, new plugin commands are introduced, and supported through Notation. +* To maintain forward compatibility plugin implementors MUST ignore unrecognized attributes in command request which are introduced in minor version updates of the plugin contract. * For an error response, every command returns a non-zero exit code 1, with an OPTIONAL JSON error response in `stderr`. It is recommended to return an error response to help user troubleshoot the error. There is no need to send different exit codes for different error conditions, as Notation (which will call the plugin and parse the response) will use `error-code` in the error response to interpret different error conditions if needed. Notation will attempt to parse the error response in `stderr` when exit code is 1, else treat it as a general error for any other non-zero exit codes. ```jsonc @@ -126,7 +127,14 @@ Plugin config can be also set/overriden during signing with the `notation sign` ***get-plugin-metadata*** -*Request* - None +*Request* + +```jsonc +{ + // Optional plugin configuration, map of string-string + "pluginConfig" : { } +} +``` *Response* All response attributes are required. @@ -146,9 +154,18 @@ All response attributes are required. // List of contract versions supported by the plugin, one per major version "supportedContractVersions" : [ ], - // Currently one of - // SIGNATURE_GENERATOR or - // SIGNATURE_ENVELOPE_GENERATOR + // List of one or more capabilities supported by plugin. + // Valid values are + // SIGNATURE_GENERATOR.RAW + // SIGNATURE_GENERATOR.ENVELOPE + // SIGNATURE_VERIFIER.TRUSTED_IDENTITY + // SIGNATURE_VERIFIER.REVOCATION_CHECK + // + // A signing plugin implements either SIGNATURE_GENERATOR.RAW + // or SIGNATURE_GENERATOR.ENVELOPE capability. + // + // A verification plugin implements + // one or more of SIGNATURE_VERIFIER capabilities. "capabilities" : [ ] @@ -157,9 +174,9 @@ All response attributes are required. *plugin-name* - Plugin name uses reverse domain name notation to avoid plugin name collisions. -*supported-contract-versions* - The list of contract versions supported by the plugin. Currently this list must include only one version, per major version. Post initial release, Notation may add new features through plugins, in the form of new commands (e.g. tsa-sign for timestamping), or additional request and response parameters. Notation will publish updates to plugin interface along with appropriate contract version update. Backwards compatible changes (changes for which older version of plugin continue to work with versions of Notation using newer contract version) like new optional parameters on existing contracts, and new commands will be supported through minor version contract updates, breaking changes through major version updates. Plugin `get-plugin-metadata` command returns the contract version a plugin supports. Notation will evaluate the minimum plugin version required to satisfy a user's request, and reject the request if the plugin does not support the required version. +*supported-contract-versions* - The list of contract versions supported by the plugin. Currently this list must include only one version, per major version. Post initial release, Notation may add new features through plugins, in the form of new commands (e.g. tsa-sign for timestamping), or additional request and response parameters. Notation will publish updates to plugin interface along with appropriate contract version update. Backwards compatible changes (changes for which older version of plugin continue to work with versions of Notation using newer contract version) like new optional parameters on existing contracts, and new commands will be supported through minor version contract updates, breaking changes through major version updates. To maintain forward compatibility plugin implementors MUST ignore unrecognized attributes in command request which are introduced in minor version updates of the plugin contract. Plugin `get-plugin-metadata` command returns the contract version a plugin supports. Notation will evaluate the minimum plugin version required to satisfy a user's request, and reject the request if the plugin does not support the required version. -*capabilities* - Non empty list of features supported by a plugin. Each capability such as `SIGNATURE_ENVELOPE_GENERATOR` requires one of more commands to be implemented by the plugin. When new features are available for plugins to implement, an implementation may choose to not implement it, and therefore will not include the feature in capabilities. Notation will evaluate the capability required to satisfy a user’s request, and reject the request if the plugin does not support the required capability. +*capabilities* - Non empty list of features supported by a plugin. Each capability such as `SIGNATURE_GENERATOR.RAW` requires one of more commands to be implemented by the plugin. When new features are available for plugins to implement, an implementation may choose to not implement it, and therefore will not include the feature in capabilities. Notation will evaluate the capability required to satisfy a user’s request, and reject the request if the plugin does not support the required capability. ## Signing interfaces @@ -180,23 +197,23 @@ This interface targets plugins that integrate with providers of basic cryptograp 3. Append any user provided metadata and Notary metadata as descriptor annotations. 4. Determine if the registered key uses a plugin 5. Execute the plugin with `get-plugin-metadata` command - 1. If plugin supports capability `SIGNATURE_GENERATOR` + 1. If plugin supports capability `SIGNATURE_GENERATOR.RAW` 1. Execute the plugin with `describe-key` command, set `request.keyId` and the optional `request.pluginConfig` to corresponding values associated with signing key `keyName` in `config.json`. - 2. Generate the payload to be signed for [JWS](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#supported-signature-envelopes) envelope format. - 1. Create the JWS protected headers collection and set `alg` to value corresponding to `describe-key.response.keySpec` as per [signature algorithm selection](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection). - 2. Create the `JWSPayload` with appropriate private (`subject`) and public (`iat,exp`) claims. + 2. Generate the payload to be signed for [JWS](../signature-specification.md#supported-signature-envelopes) envelope format. + 1. Create the JWS protected headers collection and set `alg` to value corresponding to `describe-key.response.keySpec` as per [signature algorithm selection](../signature-specification.md#algorithm-selection). + 2. Create the Notary v2 Payload (JWS Payload) as defined [here](../signature-specification.md#payload). 3. The *payload to sign* is then created as - `ASCII(BASE64URL(UTF8(ProtectedHeaders)) ‘.’ BASE64URL(JWSPayload))` 3. Execute the plugin with `generate-signature` command. 1. Set `request.keyId` and the optional `request.pluginConfig` to corresponding values associated with signing key `keyName` in `config.json`. 2. Set `request.payload` as base64 encoded *payload to sign* (the JWS *payload to sign* is double encoded, this is a shortcoming of using plugin contract with JSON encoding). - 3. Set `keySpec` to value returned by `describe-key` command in `response.keySpec`, and `hashAlgorithm` to hash algorithm corresponding to the key spec, as per [signature algorithm selection](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection). The algorithm specified in `hashAlgorithm` MUST be used by the plugin to hash the payload (`request.payload`) as part of signature generation. + 3. Set `keySpec` to value returned by `describe-key` command in `response.keySpec`, and `hashAlgorithm` to hash algorithm corresponding to the key spec, as per [signature algorithm selection](../signature-specification.md#algorithm-selection). The algorithm specified in `hashAlgorithm` MUST be used by the plugin to hash the payload (`request.payload`) as part of signature generation. 4. Validate the generated signature, return an error if any of the checks fails. - 1. Check if `response.signingAlgorithm` is one of [supported signing algorithms](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection). + 1. Check if `response.signingAlgorithm` is one of [supported signing algorithms](../signature-specification.md#algorithm-selection). 2. Check that the plugin did not modify `request.payload` before generating the signature, and the signature is valid for the given payload. Verify the hash of the `request.payload` against `response.signature`, using the public key of signing certificate (leaf certificate) in `response.certificateChain` along with the `response.signingAlgorithm`. This step does not include certificate chain validation (certificate chain leads to a trusted root configured in Notation's Trust Store), or revocation check. - 3. Check that the `response.certificateChain` conforms to [Certificate Requirements](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#certificate-requirements). + 3. Check that the `response.certificateChain` conforms to [Certificate Requirements](../signature-specification.md#certificate-requirements). 5. Assemble the JWS Signature envelope using `response.signature`, `response.signingAlgorithm` and `response.certificateChain`. Notation may also generate and include timestamp signature in this step. 6. Generate a signature manifest for the given signature envelope. - 2. Else if, plugin supports capability `SIGNATURE_ENVELOPE_GENERATOR` *(covered in next section)* + 2. Else if, plugin supports capability `SIGNATURE_GENERATOR.ENVELOPE` *(covered in next section)* 3. Return an error #### describe-key @@ -232,7 +249,7 @@ This command is used to get metadata for a given key. } ``` -*keySpec* : One of following [supported key types](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection) - `RSA_2048`, `RSA_3072`, `RSA_4096`, `EC_256`, `EC_384`, `EC_512`. +*keySpec* : One of following [supported key types](../signature-specification.md#algorithm-selection) - `RSA_2048`, `RSA_3072`, `RSA_4096`, `EC_256`, `EC_384`, `EC_512`. NOTE: This command can also be used as part of `notation key describe {key-name}` which will include the following output @@ -275,7 +292,7 @@ This command is used to generate the raw signature for a given payload. *pluginConfig* : Optional field for plugin configuration. For details, see [Plugin Configuration section](#plugin-configuration). -*keySpec* : Required field that has one of following [supported key types](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection) - `RSA_2048`, `RSA_3072`, `RSA_4096`, `EC_256`, `EC_384`, `EC_512`. Specifies the key type and size for the key. +*keySpec* : Required field that has one of following [supported key types](../signature-specification.md#algorithm-selection) - `RSA_2048`, `RSA_3072`, `RSA_4096`, `EC_256`, `EC_384`, `EC_512`. Specifies the key type and size for the key. *hashAlgorithm* : Required field that specifies Hash algorithm corresponding to the signature algorithm determined by `keySpec` for the key. @@ -295,7 +312,7 @@ All response attributes are required. } ``` -*signingAlgorithm* : One of following [supported signing algorithms](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection), Notation uses this validate the signature, and to set the appropriate attribute in signature envelope (e.g. JWS `alg`). `RSASSA_PSS_SHA_256`, `RSASSA_PSS_SHA_384`, `RSASSA_PSS_SHA_512`, `ECDSA_SHA_256`, `ECDSA_SHA_384`, `ECDSA_SHA_512`. +*signingAlgorithm* : One of following [supported signing algorithms](../signature-specification.md#algorithm-selection), Notation uses this validate the signature, and to set the appropriate attribute in signature envelope (e.g. JWS `alg`). `RSASSA_PSS_SHA_256`, `RSASSA_PSS_SHA_384`, `RSASSA_PSS_SHA_512`, `ECDSA_SHA_256`, `ECDSA_SHA_384`, `ECDSA_SHA_512`. *certificateChain* : Ordered list of certificates starting with leaf certificate and ending with root certificate. @@ -319,18 +336,18 @@ This interface targets plugins that in addition to signature generation want to 1. Append any user provided metadata and Notary metadata as descriptor annotations. 1. Determine if the registered key uses a plugin 1. Execute the plugin with `get-plugin-metadata` command - 1. If plugin supports capability `SIGNATURE_ENVELOPE_GENERATOR` - 1. Execute the plugin with `generate-envelope` command. Set `request.keyId` and the optional `request.pluginConfig` to corresponding values associated with signing key `keyName` in `config.json`. Set `request.payload` to base64 encoded descriptor, `request.payloadType` to `application/vnd.oci.descriptor.v1+json` and `request.signatureEnvelopeType` to a pre-defined type (default to `application/vnd.cncf.notary.v2.jws.v1`). - 1. `response.signatureEnvelope` contains the base64 encoded signature envelope, value of `response.signatureEnvelopeType` MUST match `request.signatureEnvelopeType`. - 1. Validate the generated signature, return an error if of the checks fails. - 1. Check if `response.signatureEnvelopeType` is a supported envelope type and `response.signatureEnvelope`'s format matches `response.signatureEnvelopeType`. - 1. Check if the signing algorithm in the signature envelope is one of [supported signing algorithms](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection). - 1. Check that the [`subject` descriptor](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#supported-signature-envelopes) in JWSPayload in `response.signatureEnvelope` matches `request.payload`. Plugins MAY append additional annotations but MUST NOT replace/override existing descriptor attributes and annotations. - 1. Check that `response.signatureEnvelope` can be verified using the public key and signing algorithm specified in the signing certificate, which is embedded as part of certificate chain in `response.signatureEnvelope` . This step does not include certificate chain validation (certificate chain leads to a trusted root configure in Notation), or revocation check. - 1. Check that the certificate chain in `response.signatureEnvelope` confirm to [Certificate Requirements]. - 1. Generate a signature manifest for the given signature envelope, and append `response.annotations` to manifest annotations. - 1. Else if plugin supports capability `SIGNATURE_GENERATOR` *(covered in previous section)* - 1. Return an error + 1. If plugin supports capability `SIGNATURE_GENERATOR.ENVELOPE` + 1. Execute the plugin with `generate-envelope` command. Set `request.keyId` and the optional `request.pluginConfig` to corresponding values associated with signing key `keyName` in `config.json`. Set `request.payload` to base64 encoded [Notary v2 Payload](../signature-specification.md#payload), `request.payloadType` to `application/vnd.cncf.notary.payload.v1+json` and `request.signatureEnvelopeType` to a pre-defined type (`application/vnd.cncf.notary.v2.jws.v1` for JWS). + 2. `response.signatureEnvelope` contains the base64 encoded signature envelope, value of `response.signatureEnvelopeType` MUST match `request.signatureEnvelopeType`. + 3. Validate the generated signature, return an error if of the checks fails. + 1. Check if `response.signatureEnvelopeType` is a supported envelope type and `response.signatureEnvelope`'s format matches `response.signatureEnvelopeType`. + 2. Check if the signing algorithm in the signature envelope is one of [supported signing algorithms](../signature-specification.md#algorithm-selection). + 3. Check that the [`targetArtifact` descriptor](../signature-specification.md#payload) in JWSPayload in `response.signatureEnvelope` matches `request.payload`. Plugins MAY append additional annotations but MUST NOT replace/override existing descriptor attributes and annotations. + 4. Check that `response.signatureEnvelope` can be verified using the public key and signing algorithm specified in the signing certificate, which is embedded as part of certificate chain in `response.signatureEnvelope` . This step does not include certificate chain validation (certificate chain leads to a trusted root configure in Notation), or revocation check. + 5. Check that the certificate chain in `response.signatureEnvelope` confirm to [Certificate Requirements]. + 4. Generate a signature manifest for the given signature envelope, and append `response.annotations` to manifest annotations. + 2. Else if plugin supports capability `SIGNATURE_GENERATOR.RAW` *(covered in previous section)* + 3. Return an error #### generate-envelope @@ -353,7 +370,7 @@ All request attributes are required. "payload" : "", // The type of payload - currently a descriptor - "payloadType" : "application/vnd.oci.descriptor.v1+json", + "payloadType" : "application/vnd.cncf.notary.payload.v1+json", // The expected response signature envelope "signatureEnvelopeType" : "application/vnd.cncf.notary.v2.jws.v1" @@ -394,7 +411,160 @@ All response attributes are required. ## Verification extensibility -TBD [notaryproject/roadmap#27](https://github.com/notaryproject/roadmap/issues/27) +[notaryproject/roadmap#27](https://github.com/notaryproject/roadmap/issues/27) + +### Requirements + +* The interface MUST NOT be tied to a specific signature envelope format. +* The interface MUST NOT allow a plugin to customize the complete [signature verification workflow](../trust-store-trust-policy-specification.md#signature-verification). Certain steps like identifying the applicable trust policy for the artifact, signature envelope schema validation, integrity checks, certificate chain validation against trust store will be performed by Notation, and cannot be customized by a plugin. +* The interface MUST allow plugins to customize verification logic for specific supported steps + * Trusted Identity validation + * Revocation check validation +* The interface MAY be extended in future to support additional steps which can be customized through a plugin. +* The interface MUST be agnostic of sequencing of steps in signature verification workflow as implemented in Notation or other implementations. +* The interface MUST allow processing of [extended attributes](../signature-specification.md#extended-attributes) that are not part of Notary v2 [standard attributes](../signature-specification.md#standard-attributes). + +### Guidelines for Verification plugin publishers + +* Usage of extended signed attributes which are marked critical in signature will have implications on portability of the signature. +The environment where verification occurs will require dependencies on either a compatible verification plugin in addition to Notation, or a compliant verification tool that understands the extended signed attributes. +Therefore, signatures intended for public distribution which require broad signature portability SHOULD avoid extended signed attributes which are marked critical. + +* Signatures which require a plugin for verification may be distributed privately (e.g. within an organization) or publicly (e.g. via a public registry). +If the plugin publisher wants their plugin used publicly they SHOULD publish specifications for the verification logic the plugin performs and test vectors. +This allows Notary v2 implementations to perform the same logic themselves, if they choose to. + +### Signature Verifier + +#### Verification workflow using plugin + +1. If signature envelope contains *Verification Plugin* attribute, check if the plugin with the given name exists, else fail signature verification. + 1. For the resolved plugin, call the `get-plugin-metadata` plugin command to get plugin version and capabilities. + 2. If signature envelope contains *Verification plugin minimum version* attribute. + 1. Validate that *Verification plugin minimum version* and plugin version are in SemVer format + 2. Validate that plugin version is greater than or equal to *Verification plugin minimum version* + 3. Fail signature verification if these validations fail + 3. Validate if plugin capabilities contains any `SIGNATURE_VERIFIER` capabilities + 1. Fail signature verification if a matching plugin with `SIGNATURE_VERIFIER` capability cannot be found. Include *Verification Plugin Name* attribute as a hint in error response. +2. Complete steps *Identify applicable trust policy* and *Proceed based on signature verification level* from [signature verification workflow](../trust-store-trust-policy-specification.md#steps). +3. Complete steps *Validate Integrity, Validate Expiry* and *Validate Trust Store* from [signature verification workflow](../trust-store-trust-policy-specification.md#steps). +4. Based on the signature verification level, each validation may be enforced, logged or skipped. +5. Populate `verify-signature` request. NOTE: The processing order of remaining known attributes does not matter as long as they are processed before the end of signature verification workflow. + 1. Set `request.signature.criticalAttributes` to the set of [standard Notary v2 attributes](../signature-specification.md#standard-attributes) that are marked critical, from the signature envelope. + 2. Set `request.signature.criticalAttributes.extendedAttributes` to extended attributes that Notation does not recognize. Notation only supports JSON primitive types for critical extended attributes. If a signature producer required complex types, it MUST set the value to a primitive type by encoding it (e.g. Base64), and MUST be decoded by the plugin which processed the attribute. + 3. Set `request.signature.unprocessedAttributes` to set of critical attribute names that are marked critical, but unknown to Notation, and therefore cannot be processed by Notation. + 4. Set `request.signature.certificateChain` to ordered array of certificates from signing envelope. + 5. Set `request.trustPolicy.trustedIdentities` to corresponding values from user configured trust policy that is applicable for given artifact and was identified in step 2. + 6. Set `request.trustPolicy.signatureVerification` to the set of verification checks that are supported by the plugin, and are required to be performed as per step 2. Notation may not populate some checks in this array, if it determined them as “skipped” as per user’s trust policy. +6. Process `verify-signature.response` + 1. Validate `response.verificationResults` map keys match the set of verifications requested in `request.trustPolicy.signatureVerification` . Fail signature verification if they don't match. + 2. For each element in `response.verificationResults` map, process each result as follows + 1. If `verificationResult.success` is true, proceed to next element. + 2. Else if `verificationResult.success` is `false`, check if the failure should be enforced or logged based on value of `signatureVerification` level in Trust Policy. Proceed to next element if failure is logged, else fail signature verification with `verificationResult.reason`. + 3. Validate values in `response.processedAttributes` match the set of values in `request.signature.unprocessedAttributes`. If they don't, fail signature verification, as the plugin did not process a critical attribute that is unknown to the caller. +7. Perform any remaining steps in [signature verification workflow](../trust-store-trust-policy-specification.md#signature-verification) which are not covered by plugin's verification capabilities. These steps MUST process any remaining critical attributes. Fail signature verification if any critical attributes are unprocessed at the end of this step. + +#### *verify-signature* command + +*Request* + +```jsonc +{ + "contractVersion" : "", + + "signature" : { + // Array of all Notary V2 defined critical attributes and their values + // in the signature envelope. Agnostic of header names and value serialization + // in specific envelope formats like JWS or COSE. + "criticalAttributes" : + { + "contentType" : "application/vnd.cncf.notary.payload.v1+json", + // One of notary.default.x509 or notary.signingAuthority.x509 + "signingScheme" : "notary.default.x509", + // Value is always RFC 3339 formatted date time string + "expiry": "2022-10-06T07:01:20Z", + "extendedAttributes" : { + // Map of extended attributes + // Extended attributes values support JSON primitive values string, number and null. + "name" : primitive-value + } + }, + + // Array of names of critical attributes that plugin + // caller does not understand and does not intend to process. + "unprocessedAttributes" : [ ], + + // Certificate chain from signature envelope. + "certificateChain" : [ ] + }, + + "trustPolicy" : { + // Array of trusted identities as specified in + // trust policy. + "trustedIdentities": [], + // Array of verification checks to be performed by the plugin + "signatureVerification" : [ + "SIGNATURE_VERIFIER.TRUSTED_IDENTITY", + "SIGNATURE_VERIFIER.REVOCATION_CHECK" + ] + }, + + // Optional plugin configuration, map of string-string + "pluginConfig" : { } +} +``` + +The request can be extended in future by including other elements of signature envelope and trust policy as required to customize additional steps in verification. + +*Response* +All response attributes are required. + +```jsonc +{ + "verificationResults" : { + // Map of results. Key must match the set of + // verification capabilities + // in verify-signature request's trustPolicy.signatureVerification attribute. + // SIGNATURE_VERIFIER.TRUSTED_IDENTITY + // SIGNATURE_VERIFIER.REVOCATION_CHECK + "SIGNATURE_VERIFIER.TRUSTED_IDENTITY" : + { + // The result of a verification check + "success" : true | false, + // Reason for check being successful or not, + // required if value of success attribute is false. + "reason" : "" + }, + "SIGNATURE_VERIFIER.REVOCATION_CHECK" : + { + // The result of a verification check + "success" : true | false, + // Reason for check being successful or not, + // required if value of success attribute is false. + "reason" : "" + } + }, + // Array of strings containing critical attributes processed by the plugin. + "processedAttributes" : [ + ] +} +``` + +*verificationResults* : Verifications performed by the plugin. This is a map where the keys MUST match set of verify capabilities in `verify-signature` request's `trustPolicy.signatureVerification` attribute, and values are objects with following attributes + +* *success* (required): The `boolean` verification result. +* *reason* (optional): Reason associated with verification being successful or not, REQUIRED if value of success field is `false`. + +*processedAttributes* (required): Array of strings containing critical attributes processed by the plugin. Values must be one or more of attribute names in `verify-signature` request's `signature.unprocessedAttributes`. + +#### Error codes for *verify-signature* + +* VALIDATION_ERROR - Any of the required request fields was empty, or a value was malformed/invalid. +* UNSUPPORTED_CONTRACT_VERSION - The contract version used in the request is unsupported. +* ACCESS_DENIED - Authentication/authorization during signature verification, this may be due to external calls made by the plugin. +* TIMEOUT - The verification process timed out and can be retried by Notation. +* THROTTLED - The verification process was throttled and can be retried by Notation. +* ERROR - Any general error that does not fall into previous error categories. ## Threat Model From 921eeb87e844be2aaafa988b8d8131f67cf37304 Mon Sep 17 00:00:00 2001 From: Rakesh Gariganti <5878554+rgnote@users.noreply.github.com> Date: Wed, 10 Aug 2022 10:39:02 -0700 Subject: [PATCH 16/17] =?UTF-8?q?Add=20requirements=20around=20certificate?= =?UTF-8?q?=20validity=20in=20the=20absence=20of=20Authen=E2=80=A6=20(#178?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add requirements around certificate validity in the absence of AuthenticTimestamp * Update trust-store-trust-policy-specification.md Signed-off-by: rgnote <5878554+rgnote@users.noreply.github.com> Co-authored-by: Shiwei Zhang --- signature-specification.md | 1 + trust-store-trust-policy-specification.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/signature-specification.md b/signature-specification.md index e1600e75..def3bf06 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -229,6 +229,7 @@ The `keyUsage` extension MUST be present and MUST be marked critical. Bit positi 1. Any certificate in the certificate chain MUST NOT use SHA1WithRSA and ECDSAWithSHA1 signatures. 1. Only Basic Constraints, Key Usage, and Extended Key Usage extensions of X.509 certificates are honored. For rest of the extensions, Notary MUST fail open i.e. they MUST NOT be evaluated or honored. 1. The certificates in the certificate chain MUST be valid at signing time. Notary MUST NOT enforce validity period nesting, i.e the validity period for a given certificate may not fall entirely within the validity period of that certificate's issuer certificate. +1. In the absence of an Authentic Timestamp, each and every certificate in the certificate chain i.e. signing certificate, intermediate certificates, and the root certificate must be valid i.e. not expired at the time of signature verification. ## FAQ diff --git a/trust-store-trust-policy-specification.md b/trust-store-trust-policy-specification.md index af8625f5..efbf5f04 100644 --- a/trust-store-trust-policy-specification.md +++ b/trust-store-trust-policy-specification.md @@ -198,7 +198,7 @@ The following table shows the resultant validation action, either *enforced* (ve **Authenticity** : Guarantees that the artifact was signed by an identity trusted by the verifier. Its definition does not include revocation, which is when a trusted identity is subsequently untrusted because of a compromise. -**Authentic timestamp** : Guarantees that the signature was generated when the certificate was valid. It also allows a verifier to determine if a signature must be treated as valid when a certificate is revoked, if the certificate was revoked after the signature was generated. In the absence of a authentic timestamp, signatures are considered invalid after certificate expires, and all signatures are considered revoked when a certificate is revoked. +**Authentic timestamp** : Guarantees that the signature was generated when the certificate was valid. It also allows a verifier to determine if a signature must be treated as valid or invalid based on whether the signature was generated before or after the certificate revocation. In the absence of an Authentic Timestamp, a signature is considered invalid if the signing certificate or chain is either expired or revoked. - **NOTE**: `notation` RC1 will generate trusted timestamp using a TSA when the signature is generated, but will not support verification of TSA countersignatures. Related issue - [#59](https://github.com/notaryproject/roadmap/issues/59). @@ -337,7 +337,7 @@ TODO: Update this section after [verification plugin spec](https://github.com/no If the accuracy is not explicitly specified and `TSTInfo.policy` is the baseline time-stamp policy([RFC-3628](https://tools.ietf.org/html/rfc3628#section-5.2)), use accuracy of 1 second. Otherwise, use an accuracy of 0. 1. Calculate the timestamp range using the lower and upper limits per [RFC-3161 section 2.4.2](https://tools.ietf.org/html/rfc3161#section-2.4.2) and store the limits as `timeStampLowerLimit` and `timeStampUpperLimit` variables respectively. - 1. Check that the time range from `timeStampLowerLimit` to `timeStampUpperLimit` timestamp is entirely within the certificate's validity period.If the time range is entirely within the signing certificate and certificate chain's validity period, continue to the next step. Else fail this step. + 1. Validate that the time range from `timeStampLowerLimit` to `timeStampUpperLimit` timestamp is entirely within the signing certificate and certificate chain's validity period. If the validation passes, continue to the next step. Else fail this step. 1. **Validate Revocation Status:** 1. Validate signing identity(certificate and certificate chain) revocation status using [certificate revocation evaluation](#certificate-revocation-evaluation) section as per `signingIdentityRevocation` setting in trust-policy. 1. Perform extended validation using the applicable (if any) plugin. From 07a3bcd33b69362685ce0bcf983fcdd99c853a61 Mon Sep 17 00:00:00 2001 From: Steve Lasker Date: Thu, 18 Aug 2022 15:15:26 -0700 Subject: [PATCH 17/17] Reverts 169 (#188) Signed-off-by: Steve Lasker --- signature-envelope-jws.md | 2 +- signature-specification.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/signature-envelope-jws.md b/signature-envelope-jws.md index f2eb4824..d55895b9 100644 --- a/signature-envelope-jws.md +++ b/signature-envelope-jws.md @@ -39,7 +39,7 @@ Example of Notary v2 payload ```jsonc { - "subject": { + "targetArtifact": { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", "size": 16724, diff --git a/signature-specification.md b/signature-specification.md index def3bf06..aa1897e1 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -84,7 +84,7 @@ Notary v2 supports the following envelope formats: Notary v2 payload is a JSON document with media type `application/vnd.cncf.notary.payload.v1+json` and has following properties. -- `subject` : Required property whose value is the descriptor of the target artifact manifest that is being signed. Both [OCI descriptor][oci-descriptor] and [ORAS artifact descriptors][artifact-descriptor] are supported. +- `targetArtifact` : Required property whose value is the descriptor of the target artifact manifest that is being signed. Both [OCI descriptor][oci-descriptor] and [ORAS artifact descriptors][artifact-descriptor] are supported. - Descriptor MUST contain `mediaType`, `digest`, `size` fields. - Descriptor MAY contain `annotations` and if present it MUST follow the [annotation rules][annotation-rules]. Notary v2 uses annotations for storing both Notary specific and user defined metadata. The prefix `io.cncf.notary` in annotation keys is reserved for use in Notary v2 and MUST NOT be used outside this specification. - Descriptor MAY contain `artifactType` field for artifact manifests, or the `config.mediaType` for `oci.image` based manifests. @@ -93,7 +93,7 @@ Notary v2 payload is a JSON document with media type `application/vnd.cncf.notar ```jsonc { - "subject": { + "targetArtifact": { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", "size": 16724, @@ -106,7 +106,7 @@ Notary v2 payload is a JSON document with media type `application/vnd.cncf.notar ```jsonc { - "subject": { + "targetArtifact": { "mediaType": "sbom/example", "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", "size": 32654