An in-toto attestation is authenticated metadata about one or more software artifacts, as per the SLSA Attestation Model. It has three layers that are independent but designed to work together:
- Envelope: Handles authentication and serialization.
- Statement: Binds the attestation to a particular subject and unambiguously identifies the types of the predicate.
- Predicate: Contains arbitrary metadata about the subject, with a
type-specific schema. This repo defines the following predicate types,
though custom types are allowed:
- Provenance: To describe the origins of a software artifact.
- Link: For migration from in-toto 0.9.
- SPDX: A Software Package Data Exchange document.
The processing model provides pseudocode showing how these layers fit together.
See the top-level README for background and examples.
{
"payloadType": "https://in-toto.io/Statement/v1-json",
"payload": "<Base64(Statement)>",
"signatures": [{"sig": "<Base64(Signature)>"}]
}
The Envelope is the outermost layer of the attestation, handling authentication and serialization. The format and protocol are defined in signing-spec and adopted by in-toto in ITE-5. It is a JSON object with the following fields:
payloadType
string, required
Always
https://in-toto.io/Statement/v1-json
(for the Statement defined below).
payload
string, required
Base64-encoded JSON Statement.
signatures
array of objects, required
One or more signatures over
payloadType
andpayload
, as defined in signing-spec.
{
"subject": [
{
"name": "<NAME>",
"digest": {"<ALGORITHM>": "<HEX_VALUE>"}
},
...
],
"predicateType": "<URI>",
"predicate": { ... }
}
The Statement is the middle layer of the attestation, binding it to a particular subject and unambiguously identifying the types of the predicate. It is a JSON object with the following fields:
subject
array of objects, required
Set of software artifacts that the attestation applies to. Each element represents a single software artifact.
IMPORTANT: Subject artifacts are matched purely by digest, regardless of content type. If this matters to you, please open a GitHub issue to discuss.
subject[*].digest
object (DigestSet), required
Collection of cryptographic digests for the contents of this artifact.
Two DigestSets are considered matching if ANY of the fields match. The producer and consumer must agree on acceptable algorithms. If there are no overlapping algorithms, the subject is considered not matching.
subject[*].name
string, required
Identifier to distinguish this artifact from others within the
subject
.The semantics are up to the producer and consumer. Because consumers evaluate the name against a policy, the name SHOULD be stable between attestations. If the name is not meaningful, use "_". For example, a Provenance attestation might use the name to specify output filename, expecting the consumer to only considers entries with a particular name. Alternatively, a vulnerability scan attestation might use the name "_" because the results apply regardless of what the artifact is named.
MUST be non-empty and unique within
subject
.
predicateType
string (TypeURI), required
URI identifying the type of the Predicate.
predicate
object, optional
Additional parameters of the Predicate. Unset is treated the same as set-but-empty. MAY be omitted if
predicateType
fully describes the predicate.
"predicateType": "<URI>",
"predicate": {
// arbitrary object
}
The Predicate is the innermost layer of the attestation, containing arbitrary
metadata about the Statement's subject
.
A predicate has a requried predicateType
(TypeURI) identifying what the
predicate means, plus an optional predicate
(object) containing additional,
type-dependent parameters.
Users are expected to choose a predicate type that fits their needs, or invent a new one if no existing one satisfies. Predicate types are not registered.
This repo defines the following predicate types:
- Provenance: To describe the origins of a software artifact.
- Link: For migration from in-toto 0.9.
- SPDX: A Software Package Data Exchange document.
We recommend the following conventions for predicates:
-
Field names SHOULD use lowerCamelCase.
-
Timestamps SHOULD use RFC 3339 syntax with timezone "Z" and SHOULD clarify the meaning of the timestamp. For example, a field named
timestamp
is too ambiguous; a better name would bebuiltAt
orallowedAt
orscannedAt
. -
References to other artifacts SHOULD be an object that includes a
digest
field of type DigestSet. Consider using the same type as Provenancematerials
if it is a good fit. -
Predicates SHOULD be designed to encourage policies to be "monotonic," meaning that deleting an attestation will never turn a DENY decision into an ALLOW. One reason because verifiers MUST ignore unrecognized subject digest types; if no subject is recognized, the attestation is effectively deleted. Example: instead of "deny if a 'has vulnerabilities' attestation exists", prefer "deny unless a 'no vulnerabilities' attestation exists".
Predicate designers are free to limit what subject types are valid for a given predicate type. For example, suppose a "Gerrit code review" predicate only applies to git commit subjects. In that case, a producer of such attestations should never use a subject other than a git commit.
The following pseudocode shows how to verify and extract metadata about a single artifact from a single attestation. The expectation is that consumers will feed the resulting metadata into a policy engine.
TODO: Explain how to process multiple artifacts and/or multiple attestations.
Inputs:
artifactToVerify
: blob of dataattestation
: JSON-encoded EnveloperecognizedAttesters
: collection of (name
,publicKey
) pairsacceptableDigestAlgorithms
: collection of acceptable cryptographic hash algorithms (usually justsha256
)
Steps:
- Envelope layer:
envelope
:= decodeattestation
as a JSON-encoded Envelope; reject if decoding failsattesterNames
:= empty set of names- For each
signature
inenvelope.signatures
:- For each (
name
,publicKey
) inrecognizedAttesters
:- Optional: skip if
signature.keyid
does not matchpublicKey
- If
signature.sig
matchespublicKey
:- Add
name
toattesterNames
- Add
- Optional: skip if
- For each (
- Reject if
attesterNames
is empty
- Intermediate state:
envelope.payloadType
,envelope.payload
,attesterNames
- Statement layer:
- Reject if
envelope.payloadType
!=https://in-toto.io/Attestation/v1-json
statement
:= decodeenvelope.payload
as a JSON-encoded Statement; reject if decoding failsartifactNames
:= empty set of names- For each
s
instatement.subject
:- For each digest (
alg
,value
) ins.digest
:- If
alg
is inacceptableDigestAlgorithms
:- If
hash(alg, artifactToVerify)
==hexDecode(value)
:- Add
s.name
toartifactNames
- Add
- If
- If
- For each digest (
- Reject if
artifactNames
is empty
- Reject if
Output (to be fed into policy engine):
statement.predicateType
statement.predicate
artifactNames
attesterNames