Keys and associated certificates used for signing artifacts using Notation 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 and PKCS #11 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 and future signature formats adopted by Notary Project.
- Plugin Publisher - A user, organization, open source project or 3rd party vendor that creates a Notation plugin for internal or public distribution.
- Plugin - A component external to Notation that can integrate as one of the steps in Notation’s workflow for signature generation or verification. A Notation plugin can be distributed as a single executable file, or an archive file (
ziportar.gz). - Default provider - Signing and verification mechanisms built into Notation itself to provide default experience without requiring to install/configure additional plugins. [We are yet to define what is included in the default experience].
- 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 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.
- 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 Go library) does not have a in built support 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 supports the installation of a plugin from an https URL or from a file in the filesystem. To support the plugin installation and management using notation plugin commands, the plugin publisher must adhere to the following conventions:
- Plugin executable file MUST follow the naming convention
notation-{plugin-name}. OnwindowsOS, the file extension.exeis REQUIRED. - The plugin distribution format MUST be either .zip, .tar.gz, or a single plugin executable file. If the release format is single plugin executable file, it is highly recommended to compress it into an archive for installation efficiency consideration.
- If the archive format is
.zipor.tar.gz, there MUST be one and only one plugin executable file within each archive. - Plugin publisher MUST provide SHA256 checksum for each released archive or single plugin executable file if they want to enable users to install a plugin from an
httpsURL. - The plugin archive MAY contain License files. Also, it's recommended to include licenses in the archive.
- Currently, Notation facilitates the installation of plugin executable files and archives, with a size limit of less than 256 MiB. Consequently, the plugin executable file and archive size MUST be less than 256 MiB. Note: Although users have the option to install plugins larger than 256 MiB, they will be unable to utilize the
notation plugin installcommand in such cases.
For example, an archive of a Notation plugin helloworld for Linux AMD64 machine notation-helloworld_1.0.1_linux_amd64.tar.gz includes these files:
notation-helloworld_1.0.1_linux_amd64.tar.gz
├── notation-helloworld (required)
├── LICENSE (optional)
└── Dependencies (required if present)
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.
Notation offers plugin management commands that allow users to install plugins from various sources, including https URL and filesystem.
To enumerate all available plugins the PLUGIN_DIRECTORY is scanned based on per OS:
| OS | PLUGIN_DIRECTORY |
|---|---|
| Unix | $XDG_CONFIG_HOME/notation/plugins |
| Windows | %AppData%/notation/plugins |
| Darwin | $HOME/Library/Application Support/notation/plugins |
Each plugin executable must be located under $PLUGIN_DIRECTORY/{plugin-name} directory, with executable named as notation-{plugin-name}.
Any directory found inside $PLUGIN_DIRECTORY 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
.exesuffix. - 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-metadatamust produce a valid JSON metadata (and nothing else) on its standard output (schema to be discussed later).
Notation provides various commands, including install, list, and uninstall, to manage plugin lifecycle. For more information, please refer to the notation plugin CLI specification.
- 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, wheremysigningkeyis a friendly key name to refer during signing operation from the CLI,idis an key identifier known to plugin that is used for signing, and the value ofpluginspecifies it's using a plugin located at~/notation/plugins/com.example.nv2plugin/notation-com.example.nv2plugin.
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 "mysigningkey" --id "keyid" --plugin "com.example.nv2plugin" --plugin-config key1=value1 --plugin-config "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" --plugin-config "key 2"=newValue2
- Notation will invoke the plugin executable for each command (e.g. sign, verify), pass inputs through
stdinand get output throughstdoutandstderr. Currently, a size limit of less than 64 MiB is applied to each output channel for preventing out-of-memory issues of potential plugin malfunctioning. - The command will be passed as the first argument to the plugin e.g.
notation-{plugin-name} <command>. A JSON request is passed usingstdin. The plugin is expected to return a JSON response throughstdoutwith a0exit code for successful response, and a non-zero exit code with a JSON error response instderrfor 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 tostdouton initialization, and only send the JSON response away fromstdoutwhen the command execution completes. E.g. For golang, setos.Stdoutto point to a log file. - Every request JSON will contain a
contractVersiontop 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 useerror-codein the error response to interpret different error conditions if needed. Notation will attempt to parse the error response instderrwhen exit code is 1, else treat it as a general error for any other non-zero exit codes.
{
// Each command defines expected error codes
"errorCode" : "<error code>",
// Plugin defined error message.
"errorMessage" : "User friendly error message",
// Optional plugin defined additional key value pairs related to the error.
"errorMetadata" : {
"key1" : "value1",
"key2" : "value2"
}
}- 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-metadatacommand every time a signing or verification workflow is invoked to discover metadata. Notation will not cache the response ofget-plugin-metadatacommand as it involves invalidating cache when a plugin is updated, detecting a plugin update can be non trivial. Theget-plugin-metadatacommand is expected to return only static data, and plugin implementors should avoid making remote calls in this command.
get-plugin-metadata
Request
{
// Optional plugin configuration, map of string-string
"pluginConfig" : { }
}Response All response attributes are required.
{
// Plugin name that matches its install dir
// e.g. "com.example.nv2plugin".
"name" : "<plugin name>",
// Plugin description.
"description" : "description>",
// Plugin publisher controlled version.
"version" : "<version>",
// Plugin webpage for support or documentation.
"url" : "<URL>",
// List of contract versions supported by the plugin, one per major version
"supportedContractVersions" : [ ],
// List of one or more capabilities supported by the plugin.
// See the 'capabilities' section below for valid values and details.
"capabilities" : [
]
}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. 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 - A non-empty list of features supported by the plugin. Each capability requires the plugin to implement specific commands. Implementations of Notary Project spec can evaluate the required capability of a user's request and may reject the request if the plugin does not support it.
Valid capabilities and required commands:
SIGNATURE_GENERATOR.RAW: Generates a raw signature for OCI or blob payloads.- Required commands:
describe-key,generate-signature
- Required commands:
SIGNATURE_GENERATOR.ENVELOPE: Generates a complete signature envelope for OCI payloads.- Required command:
generate-envelope
- Required command:
SIGNATURE_GENERATOR.ENVELOPE_FOR_BLOB: Generates a complete signature envelope for blob payloads.- Required commands:
describe-key,generate-envelope
- Required commands:
SIGNATURE_VERIFIER.TRUSTED_IDENTITY: Performs trusted identity verification.- Required command:
verify-signature
- Required command:
SIGNATURE_VERIFIER.REVOCATION_CHECK: Performs revocation check verification.- Required command:
verify-signature
- Required command:
- The interface MUST NOT be tied to a specific signature envelope format.
Notation will support plugins to be developed against the following interfaces - Signature Generator, and Signature Envelope Generator. These interfaces target abstraction levels that satisfy most plugin integration scenarios.
This interface targets plugins that integrate with providers of basic cryptographic operations (e.g., Local PIV/PKCS#11 hardware tokens, remote KMS, or key vault services). Plugins that target this interface will only generate a raw signature given a payload to sign. Plugin developers who implement the Notary Project specification are responsible for packaging this signature into a signature envelope along with a signature manifest (OCI) or a signature file (blob). Plugin developers are also responsible for generating the TSA signature if indicated by the user. The plugin does not need to be signature envelope format aware and will continue to work if Notary Project adopts additional signature formats.
OCI signing:
- Given a user request to sign an OCI artifact, with signing key
keyName(the friendly key name) - Pull the image manifest using the
oci-artifactURL, and construct a descriptor - Append any user provided metadata and metadata defined in Notary Project signature specification as descriptor annotations.
- Determine if the registered key uses a plugin
- Execute the plugin with
get-plugin-metadatacommand- If plugin supports capability
SIGNATURE_GENERATOR.RAW- Execute the plugin with
describe-keycommand, setrequest.keyIdand the optionalrequest.pluginConfigto corresponding values associated with signing keykeyNameinconfig.json. - Generate the payload to be signed for JWS envelope format.
- Create the JWS protected headers collection and set
algto value corresponding todescribe-key.response.keySpecas per signature algorithm selection. - Create the Notary Project signature Payload (JWS Payload) as defined here.
- The payload to sign is then created as -
ASCII(BASE64URL(UTF8(ProtectedHeaders)) ‘.’ BASE64URL(JWSPayload))
- Create the JWS protected headers collection and set
- Execute the plugin with
generate-signaturecommand.- Set
request.keyIdand the optionalrequest.pluginConfigto corresponding values associated with signing keykeyNameinconfig.json. - Set
request.payloadas base64 encoded payload to sign (the JWS payload to sign is double encoded, this is a shortcoming of using plugin contract with JSON encoding). - Set
keySpecto value returned bydescribe-keycommand inresponse.keySpec, andhashAlgorithmto hash algorithm corresponding to the key spec, as per signature algorithm selection. The algorithm specified inhashAlgorithmMUST be used by the plugin to hash the payload (request.payload) as part of signature generation.
- Set
- Validate the generated signature, return an error if any of the checks fails.
- Check if
response.signingAlgorithmis one of supported signing algorithms. - Check that the plugin did not modify
request.payloadbefore generating the signature, and the signature is valid for the given payload. Verify the hash of therequest.payloadagainstresponse.signature, using the public key of signing certificate (leaf certificate) inresponse.certificateChainalong with theresponse.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. - Check that the
response.certificateChainconforms to Certificate Requirements.
- Check if
- Assemble the signature envelope using
response.signature,response.signingAlgorithmandresponse.certificateChain. If the signing scheme isnotary.x509, implementations SHOULD also request and include TSA timestamp countersignature in this step. ThecertReqfield in the timestamping request MUST be set to true. - Generate a signature manifest for the given signature envelope.
- Execute the plugin with
- Else if, plugin supports capability
SIGNATURE_GENERATOR.ENVELOPE(covered in next section) - Return an error
- If plugin supports capability
Blob signing:
- Given a user request to sign a blob, with signing key
keyName - Determine if the registered key uses a plugin
- Execute the plugin with the
get-plugin-metadatacommand and check its capabilities:- If the plugin supports
SIGNATURE_GENERATOR.RAW:- Execute the plugin with the
describe-keycommand, settingrequest.keyIdand the optionalrequest.pluginConfigto the values associated with signing keykeyNameinconfig.json. - Generate the digest of the blob using the hash algorithm specified in the
keySpecfrom thedescribe-keyresponse. Construct the payload to be signed using this digest, as described in the signature specification. - Follow the same signing steps as in the OCI signing workflow above (i.e., generate the signature, validate it, and assemble the signature envelope).
- Generate the signature file for the resulting signature envelope.
- Execute the plugin with the
- Else if plugin supports capability
SIGNATURE_GENERATOR.ENVELOPE_FOR_BLOB(covered in next section) - Else return an error indicating that the plugin does not support the required signing capability.
- If the plugin supports
Note: The plugin does not need to be aware of the signature envelope format or whether it is signing an OCI artifact or a blob; it only receives the payload to sign.
This command is used to get metadata for a given key. The command is required for SIGNATURE_GENERATOR.RAW and SIGNATURE_GENERATOR.ENVELOPE_FOR_BLOB capabilities.
Request
{
"contractVersion" : "<major-version.minor-version>",
// Key id associated with signing key (keyName)
// in config.json /signingKeys/keys
"keyId": "<key id>",
// Optional plugin configuration, map of string-string
"pluginConfig" : { }
}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.
Response
{
// The same key id as passed in the request.
"keyId" : "<key id>",
"keySpec" : "<key type and size>"
}keySpec : One of following supported key types - RSA-2048, RSA-3072, RSA-4096, EC-256, EC-384, EC-521.
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
discovercommand - Output of
describe-keycommand for the specific key
- Output of plugin
This command is used to generate the raw signature for a given payload.
Request
{
"contractVersion" : "<major-version.minor-version>",
// Key id associated with signing key (keyName)
// in config.json /signingKeys/keys
"keyId": "<key id>",
// Optional plugin configuration, map of string-string
"pluginConfig" : { },
// The key spec for the given key id
"keySpec" : "<key type and size>",
// 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" : "<base64 encoded payload to be signed>",
}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.
keySpec : Required field that has one of following supported key types - RSA-2048, RSA-3072, RSA-4096, EC-256, EC-384, EC-521. 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.
{
// The same key id as passed in the request.
"keyId" : "<key id>",
"signature" : "<Base64 encoded signature>",
"signingAlgorithm" : "<signing algorithm>",
"certificateChain": ["Base64(DER(leafCert))","Base64(DER(intermediateCACert))","Base64(DER(rootCert))"]
}signingAlgorithm : One of following supported signing algorithms, Notation uses this validate the signature, and to set the appropriate attribute in signature envelope (e.g. JWS alg). Supported values are
RSASSA-PSS-SHA-256: RSASSA-PSS with SHA-256RSASSA-PSS-SHA-384: RSASSA-PSS with SHA-384RSASSA-PSS-SHA-512: RSASSA-PSS with SHA-512ECDSA-SHA-256: ECDSA on secp256r1 with SHA-256ECDSA-SHA-384: ECDSA on secp384r1 with SHA-384ECDSA-SHA-512: ECDSA on secp521r1 with SHA-512
certificateChain : Ordered list of certificates starting with leaf certificate and ending with root certificate.
- VALIDATION_ERROR - Any of the required request fields was empty, or a value was malformed/invalid. Includes condition where the key referenced by
keyIdwas not found. - UNSUPPORTED_CONTRACT_VERSION - The contract version used in the request is unsupported.
- ACCESS_DENIED - Authentication/authorization error to use given key.
- TIMEOUT - The operation to generate signature timed out and can be retried by Notation.
- THROTTLED - The operation to generate signature was throttles and can be retried by Notation.
- ERROR - Any general error that does not fall into previous error categories.
This interface targets plugins that, in addition to signature generation, want to generate the complete signature envelope. This interface allows plugins to have full control over the generated signature envelope, and can append additional signed and unsigned metadata, including timestamp countersignatures. The plugin must be signature envelope format aware, and implement new formats when Notary Project adopts new formats.
OCI signing:
- Given a user request to sign an OCI artifact, with
keyName(friendly key name) - Pull the image manifest using the
imageURL, and construct a descriptor - Append any user provided metadata and metadata defined in Notary Project signature specification as descriptor annotations.
- Determine if the registered key uses a plugin
- Execute the plugin with
get-plugin-metadatacommand- If plugin supports capability
SIGNATURE_GENERATOR.ENVELOPE- Execute the plugin with
generate-envelopecommand. Setrequest.keyIdand the optionalrequest.pluginConfigto corresponding values associated with signing keykeyNameinconfig.json. Setrequest.payloadto base64 encoded Notary Project signature Payload,request.payloadTypetoapplication/vnd.cncf.notary.payload.v1+jsonandrequest.signatureEnvelopeTypeto a pre-defined type (application/jose+jsonfor JWS). - If plugin supports timestamping under signing scheme
notary.x509, the plugin SHOULD request and include TSA timestamp countersignature in the signature envelope at this step. The timestamp countersignature MUST be RFC 3161 compliant. ThecertReqfield in the timestamping request MUST be set to true. response.signatureEnvelopecontains the base64 encoded signature envelope, value ofresponse.signatureEnvelopeTypeMUST matchrequest.signatureEnvelopeType.- Validate the generated signature envelope, return an error if any of the checks fails.
- Check if
response.signatureEnvelopeTypeis a supported envelope type andresponse.signatureEnvelope's format matchesresponse.signatureEnvelopeType. - Check if the signing algorithm in the signature envelope is one of supported signing algorithms.
- Check that the
targetArtifactdescriptor in JWSPayload inresponse.signatureEnvelopematchesrequest.payload. Plugins MAY append additional annotations but MUST NOT replace/override existing descriptor attributes and annotations. - Check that
response.signatureEnvelopecan be verified using the public key and signing algorithm specified in the signing certificate, which is embedded as part of certificate chain inresponse.signatureEnvelope. This step does not include certificate chain validation (certificate chain leads to a trusted root configured in Notation), or revocation check. - Check that the certificate chain in
response.signatureEnvelopeand timestamp countersignature confirm to certificate requirements.
- Check if
- Generate a signature manifest for the given signature envelope, and append
response.annotationsto manifest annotations.
- Execute the plugin with
- Else if plugin supports capability
SIGNATURE_GENERATOR.RAW(covered in previous section) - Return an error
- If plugin supports capability
Blob signing:
- Given a user request to sign a blob, with signing key
keyName(the friendly key name) - Determine if the registered key uses a plugin
- Execute the plugin with the
get-plugin-metadatacommand and check its capabilities:- If the plugin supports
SIGNATURE_GENERATOR.ENVELOPE_FOR_BLOB:- Execute the plugin with the
describe-keycommand, settingrequest.keyIdand the optionalrequest.pluginConfigto the values associated with signing keykeyNameinconfig.json. - Generate the digest of the blob using the hash algorithm specified in the
keySpecfrom thedescribe-keyresponse. Construct the payload to be signed using this digest, as described in the signature specification. - Follow the same envelope generation steps as in the OCI signing workflow above (i.e., generate the envelope and validate it).
- Generate the signature file for the resulting signature envelope.
- Execute the plugin with the
- Else return an error indicating that the plugin does not support the required signing capability.
- If the plugin supports
Note: The plugin does not need to be aware of whether it is signing an OCI artifact or a blob; however, the plugin must provide an additional
describe-keycommand forSIGNATURE_GENERATOR.ENVELOPE_FOR_BLOBcapability compared toSIGNATURE_GENERATOR.ENVELOPEcapability.
This command is used to generate the complete signature envelope for a given payload.
Request All request attributes are required.
{
"contractVersion" : "<major-version.minor-version>",
// Key id associated with signing key (keyName)
// in config.json /signingKeys/keys
"keyId": "<key id>",
// Optional plugin configuration, map of string-string
"pluginConfig" : { },
// Optional signature expiry duration in seconds, number
"expiryDurationInSeconds" : 1234567,
"payload" : "<Base64 encoded payload to be signed>",
// The type of payload - currently a descriptor
"payloadType" : "application/vnd.cncf.notary.payload.v1+json",
// The expected response signature envelope
"signatureEnvelopeType" : "application/jose+json"
}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.
expiryDurationInSeconds : Optional field which contains signature expiry duration(in seconds) as integer number.
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 envelope format supported by Notation, by passing an optional parameter.
e.g. notation sign $IMAGE --key {key-name} --signature-format {some-new-type}
Response All response attributes are required.
{
"signatureEnvelope": "<Base64 encoded signature envelope>",
"signatureEnvelopeType" : "application/jose+json",
// Annotations to be appended to Signature Manifest annotations
"annotations" : {
"key1" : "value1",
"key2" : "value2"
}
}- VALIDATION_ERROR - Any of the required request fields was empty, or a value was malformed/invalid. Includes condition where the key referenced by
keyIdwas 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.
- ACCESS_DENIED - Authentication/authorization error to use given key.
- TIMEOUT - The operation to generate signature timed out and can be retried by Notation.
- THROTTLED - The operation to generate signature was throttles and can be retried by Notation.
- ERROR - Any general error that does not fall into previous error categories.
- 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. 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 that are not part of the Notary Project signature standard attributes.
-
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 implementations of the Notary Project signature specification to perform the same logic themselves, if they choose to.
- If signature envelope contains Verification Plugin attribute, check if the plugin with the given name exists, else fail signature verification.
- For the resolved plugin, call the
get-plugin-metadataplugin command to get plugin version and capabilities. - If signature envelope contains Verification plugin minimum version attribute.
- Validate that Verification plugin minimum version and plugin version are in SemVer format
- Validate that plugin version is greater than or equal to Verification plugin minimum version
- Fail signature verification if these validations fail
- Validate if plugin capabilities contains any
SIGNATURE_VERIFIERcapabilities- Fail signature verification if a matching plugin with
SIGNATURE_VERIFIERcapability cannot be found. Include Verification Plugin Name attribute as a hint in error response.
- Fail signature verification if a matching plugin with
- For the resolved plugin, call the
- Complete steps Identify applicable trust policy and Proceed based on signature verification level from signature verification workflow.
- Complete steps Validate Integrity, Validate Expiry and Validate Trust Store from signature verification workflow.
- Based on the signature verification level, each validation may be enforced, logged or skipped.
- Populate
verify-signaturerequest. NOTE: The processing order of remaining known attributes does not matter as long as they are processed before the end of signature verification workflow.- Set
request.signature.criticalAttributesto the set of standard Notary Project signature attributes that are marked critical, from the signature envelope. - Set
request.signature.criticalAttributes.extendedAttributesto 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. - Set
request.signature.unprocessedAttributesto set of critical attribute names that are marked critical, but unknown to Notation, and therefore cannot be processed by Notation. - Set
request.signature.certificateChainto ordered array of certificates from signing envelope. - Set
request.trustPolicy.trustedIdentitiesto corresponding values from user configured trust policy that is applicable for given artifact and was identified in step 2. - Set
request.trustPolicy.signatureVerificationto 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.
- Set
- Process
verify-signature.response- Validate
response.verificationResultsmap keys match the set of verifications requested inrequest.trustPolicy.signatureVerification. Fail signature verification if they don't match. - For each element in
response.verificationResultsmap, process each result as follows- If
verificationResult.successis true, proceed to next element. - Else if
verificationResult.successisfalse, check if the failure should be enforced or logged based on value ofsignatureVerificationlevel in Trust Policy. Proceed to next element if failure is logged, else fail signature verification withverificationResult.reason.
- If
- Validate values in
response.processedAttributesmatch the set of values inrequest.signature.unprocessedAttributes. If they don't, fail signature verification, as the plugin did not process a critical attribute that is unknown to the caller.
- Validate
- Perform any remaining steps in signature verification workflow 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.
Request
{
"contractVersion" : "<major-version.minor-version>",
"signature" : {
// Array of all the Notary Project 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.x509 or notary.x509.signingAuthority
"signingScheme" : "notary.x509" | "notary.x509.signingAuthority",
// Value is always RFC 3339 formatted date time string
"expiry": "2022-10-06T07:01:20Z",
// if signingScheme is notary.x509.signingAuthority
"authenticSigningTime": "2022-04-06T07:01:20Z",
// Name of the verification plugin
"verificationPlugin": "com.example.nv2plugin",
// Optional plugin minimum version, if present.
"verificationPluginMinVersion": "1.0.0",
// Map of extended attributes
// Extended attributes values support JSON primitive values string, number and null
"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.
{
"verificationResults" : {
// Map of results. Key must match the set of verification capabilities
// in verify-signature request's trustPolicy.signatureVerification attribute.
"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
booleanverification 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.
- 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.
Q: Will Notation generate timestamp countersignature for Signature Envelope Generator plugin or its responsibility of plugin publisher?
A : The Signature Envelope Generator plugin has full control over the generated signature envelope including any timestamp countersignature. Notation will NOT add/update/delete any timestamp countersignature in an envelope that's generated by a Signature Envelope Generator plugin.
{ "signingKeys": { "default": "", "keys": [ { "name": "mysigningkey", "id" : "keyid", "plugin": "com.example.nv2plugin", // Optional configuration as required by the plugin "pluginConfig" : { "key1" : "value1", "key 2" : "value 2" } } ] } }