Vendor trust-root set v1

The vendor trust-root set is the list of Stella Ops vendor public keys that an installation pins. Vendor-signed material, such as product update manifests and entitlement certificates, verifies against this set offline. The set is rotated by signed root updates: the current root authorises its successor, and the successor countersigns. An installation never holds a vendor private key.

Implementation: VendorTrustRootSet in src/__Libraries/StellaOps.Cryptography.Dsse.Signing/VendorTrustRootSet.cs.

Purposes

Each purpose has its own roots. A root pinned for one purpose never verifies material of another.

PurposeVerifiesConsumers
manufacturer-releaseProduct update manifests (product-update-manifest-v1.md)stella self update --check and --dry-run, and stella verify release-manifest, when the operator supplies no trust root
vendor-entitlementEntitlement certificates (entitlement-certificate-v1.md)Entitlement verification; the Platform entitlement import (POST /api/v1/platform/entitlements)

The purpose for signed vulnerability-intelligence manifests is added with that contract.

Location

Layout

<set>/<purpose>/anchors/*.pem        roots pinned by the release, one PUBLIC KEY PEM block per file
<set>/<purpose>/updates/*.dsse.json  root updates, applied in sequence order (file names are free)

Key identifier

A root’s key identifier is sha256: followed by the lowercase hexadecimal SHA-256 of its SubjectPublicKeyInfo DER encoding. It is derived from the key, so a root cannot be mislabelled.

Root update

A root update is a DSSE envelope:

{
  "payloadType": "application/vnd.stellaops.vendor-root-update.v1+json",
  "payload": "<base64 of the canonical payload bytes>",
  "signatures": [
    { "keyid": "<previousKeyId>", "sig": "<base64 signature by the current root>" },
    { "keyid": "<newKeyId>", "sig": "<base64 signature by the new root>" }
  ]
}

The payload is canonical JSON (keys sorted, no insignificant whitespace). Every field is required and no other field is allowed.

FieldTypeMeaning
schemaVersionstringstella.ops/vendorRootUpdate@v1.
purposestringThe purpose whose root is replaced.
sequenceintegerPosition in the purpose’s update chain, from 1, without gaps.
previousKeyIdstringKey identifier of the root being replaced.
newKeyIdstringKey identifier of the new root.
newPublicKeystringStandard base64 of the new root’s SubjectPublicKeyInfo DER.
issuedAtstringTime the update was signed, UTC, YYYY-MM-DDTHH:MM:SSZ. Informational.

Both signatures cover the DSSE pre-authentication encoding of the payload type and the payload bytes. Supported algorithms: ES256 (P-256, IEEE P1363 r||s signature) and Ed25519.

Resolution rules

A verifier resolves the set for one purpose as follows. Any issue refuses the whole set: no root of that purpose is trusted, and no partial result is used.

  1. Load every anchor. Refuse private key material, a label other than PUBLIC KEY, more than one PEM block in a file, and a duplicate root.
  2. Order the updates by sequence; the sequence must run from 1 without a gap or a repeat.
  3. For each update, require that purpose matches; that previousKeyId names a root that is active at that point (a superseded root cannot authorise another update); that newKeyId is the identifier of newPublicKey; that the new key has not been used before; that a signature with keyid equal to previousKeyId verifies under the current root; and that a signature with keyid equal to newKeyId verifies under the new key.
  4. Apply the update: the new root becomes active and the previous root is superseded.
  5. Material signed by a superseded root is refused (vendor_root_superseded:<keyId>).

Operator-supplied trust roots (--trust-root, --trusted-public-key-pem, or the STELLAOPS_PRODUCT_UPDATE_TRUST_ROOT and STELLAOPS_RELEASE_TRUST_ROOT variables) replace the pinned set for that command. They are never merged with it.

Issue codes

CodeMeaning
vendor_roots_missing:<purpose>No set, or no anchor, for the purpose.
vendor_root_purpose_invalid:<purpose>The purpose name is not ^[a-z][a-z0-9-]{1,63}$.
private_key_material_rejected:<file>A file in the set contains private key material.
vendor_root_pem_invalid, vendor_root_pem_label_invalid, vendor_root_pem_multiple (:<file>)An anchor is not exactly one PUBLIC KEY PEM block.
vendor_root_duplicate:<keyId>The same root is pinned twice.
vendor_root_file_too_large:<file>, vendor_root_file_unreadable:<file>A file exceeds 64 KiB or cannot be read.
root_update_payload_type_invalid, root_update_payload_invalid, root_update_payload_not_canonical, root_update_payload_fields_invalid, root_update_signatures_missing, root_update_signature_malformed, root_update_invalid (:<file>)The envelope or its payload does not match this contract.
root_update_purpose_mismatch:<file>The update belongs to another purpose.
root_update_sequence_invalid:<file>A gap or a repeated sequence number.
root_update_previous_unknown:<file>, root_update_previous_superseded:<file>The replaced root is not active.
root_update_key_id_mismatch:<file>, root_update_key_reused:<file>The new key’s identifier is wrong, or the key was used before.
root_update_signature_invalid:<file>, root_update_countersignature_invalid:<file>The current root’s signature or the new root’s countersignature does not verify.
vendor_root_superseded:<keyId>, vendor_root_unknown:<keyId>Material names a superseded or unknown root.

Limits