Status: Informative. Concrete recipes for mapping between common Sigstore tooling output formats (
sigstore-python,sigstore-go,cosign) and the MDAsignatures[]entry shape (§09-2). The normative format remains §09; this section reduces the integration cost for implementations that wrap existing Sigstore tools.
§09-9 explains why MDA chose DSSE PAE over a detached signature on the digest hex. In practice that choice constrains tooling: an MDA signatures[] entry is a DSSE-over-Rekor attestation, not a sign-blob over a flat hash. This section spells out which Sigstore client APIs produce a Rekor dsse-v0.0.1 entry — the only entry type MDA verifiers accept (§09-4.2 step 3) — and which do not. Each subsection gives:
- The exact tooling command or API call
- The output format that tooling produces
- The field-by-field mapping into MDA's
signatures[]shape
The order below is the recommended order of preference for new integrations.
sigstore-python exposes sign_dsse(payload, payload_type), which constructs the DSSE PAE envelope per §09-3.1, signs it with the Fulcio-issued ephemeral key, and submits the envelope to Rekor as a dsse-v0.0.1 entry. This matches MDA's expectations end-to-end with no client-side glue.
from sigstore.sign import SigningContext
from sigstore.oidc import detect_credential
# payload_bytes = JCS canonical bytes of the integrity object (§09-3.1)
# Compute via: mda canonicalize --integrity-payload my-artifact.mda
payload_bytes = b'{"algorithm":"sha256","digest":"sha256:..."}'
ctx = SigningContext.production()
identity = detect_credential()
with ctx.signer(identity) as signer:
bundle = signer.sign_dsse(
payload=payload_bytes,
payload_type="application/vnd.mda.integrity+json",
)
# bundle is a sigstore.models.Bundle; serialize via bundle.to_json()The returned Bundle follows the Sigstore Bundle v0.3 protobuf-JSON shape:
Mapping to MDA signatures[] entry (§09-2):
| MDA field | Source in Sigstore bundle | Transformation |
|---|---|---|
signer |
OIDC issuer claim from verificationMaterial.certificate |
Decode the cert, read the OIDC issuer extension (OID 1.3.6.1.4.1.57264.1.8 for v2 issuers, or 1.3.6.1.4.1.57264.1.1 for v1 issuer extension), prefix with sigstore-oidc: |
key-id |
verificationMaterial.certificate.rawBytes |
sha256 of cert DER bytes, lowercase hex, prefix with fulcio: |
payload-digest |
(must equal integrity.digest) |
Copy from frontmatter integrity.digest; verify equality (§09-2) |
algorithm |
Cert public key algorithm | ed25519, ecdsa-p256, or rsa-pss-sha256; sigstore-python emits ecdsa-p256 for keyless |
signature |
dsseEnvelope.signatures[0].sig |
Base64 string, copy as-is |
payload-type |
dsseEnvelope.payloadType |
Copy as-is. Omit when equal to the MDA default application/vnd.mda.integrity+json (§09-2). |
rekor-log-id |
verificationMaterial.tlogEntries[0].logId.keyId |
Base64-decode, hex-encode |
rekor-log-index |
verificationMaterial.tlogEntries[0].logIndex |
Parse string as integer |
Verifiers MUST also confirm verificationMaterial.tlogEntries[0].kindVersion.kind == "dsse" and version == "0.0.1" (§09-4.2 step 3) before accepting the entry.
sigstore-go provides the equivalent producer API:
import (
"github.com/sigstore/sigstore-go/pkg/sign"
)
// payload is the JCS canonical bytes of the integrity object
bundle, err := sign.SignDSSE(payload, "application/vnd.mda.integrity+json", opts)The returned bundle uses the same Sigstore Bundle v0.3 shape as §12-2, and the same field-by-field MDA mapping table applies.
cosign is the most widely deployed Sigstore client, but its blob-signing flows do not produce a Rekor dsse-v0.0.1 entry by default:
cosign sign-blobsigns the raw blob bytes and emits a Rekorhashedrekord-v0.0.1entry. MDA verifiers MUST refuse this entry type (§09-4.2 step 3).cosign attest-blobproduces a DSSE attestation, but the resulting Rekor entry isintoto-v0.0.2(in-toto Statement payload type), notdsse-v0.0.1. MDA verifiers MUST also refuse this entry type.
There is no current cosign subcommand that emits a bare dsse-v0.0.1 entry over an arbitrary payloadType. Operators who must use cosign have two options:
- Wrap a Sigstore SDK from a thin shim. Call
sigstore-python(§12-2) orsigstore-go(§12-3) from a small wrapper script. This is the recommended path forcosign-centric pipelines today. - Track upstream cosign issues. Sigstore is exploring a generic DSSE blob-signing flow under upstream issues (e.g.
sigstore/cosign#3464). When that lands and emitsdsse-v0.0.1, this section will be updated; until then, treatcosign sign-blobandcosign attest-bloboutputs as incompatible with §09 and reject them at thekindVersioncheck.
cosign verify-blob-attestation MAY still be useful on the verification side to fetch and verify Rekor inclusion proofs, provided the operator independently confirms the entry kind is dsse-v0.0.1.
The Rust client sigstore-rs is under active development. As of the v1.0 freeze date it does not expose a stable sign_dsse equivalent; integrations SHOULD wrap sigstore-python or sigstore-go from Rust until a stable Rust DSSE producer API ships. The verifier side of sigstore-rs is more mature and can be combined with a third-party DSSE PAE construction helper for signatures[] validation.
For the air-gap path (§09-5), no Sigstore tooling is involved. The recommended construction:
# Producer side
import nacl.signing # or cryptography for ECDSA
import base64
signing_key = nacl.signing.SigningKey.generate()
signature = signing_key.sign(pae_bytes).signature
sig_b64 = base64.standard_b64encode(signature).decode("ascii")
# Publish signing_key.verify_key as base64 in
# https://<your-domain>/.well-known/mda-keys.json
# with shape:
# { "keys": [{ "key-id": "<fingerprint>", "algorithm": "ed25519",
# "public-key": "<base64-or-PEM>" }] }The mda-keys.json schema is schemas/_defs/mda-keys.schema.json (§09-5.1). The pae_bytes are the DSSE PAE envelope per §09-3.1; a custom payload-type vendor SHOULD use its registered application/vnd.<vendor>.<doc-type>+json value in the PAE payload-type slot and copy it into signatures[i].payload-type.
Verification side:
import nacl.signing, base64, requests
domain = signer_field.split(":")[1] # "did-web:tools.example.com" → "tools.example.com"
# First require `domain` to match the operator trust policy (§09-5.2).
keys = requests.get(f"https://{domain}/.well-known/mda-keys.json").json()
key_entry = next(k for k in keys["keys"] if k["key-id"] == signature["key-id"])
public_key = nacl.signing.VerifyKey(base64.standard_b64decode(key_entry["public-key"]))
public_key.verify(pae_bytes, base64.standard_b64decode(signature["signature"]))- Private Sigstore deployments. Operators running their own Fulcio + Rekor (e.g. enterprise air-gap with self-hosted transparency log) configure their tooling per the Sigstore project documentation. The MDA-side mapping (§12-2 table) applies regardless of which Sigstore deployment produced the bundle.
- Hardware security modules. Producing signatures with HSM-backed keys is outside MDA's scope; consult the HSM vendor's Sigstore integration guidance.
- Key rotation cadence and trust policy. §09-7 leaves operator policy to define key rotation, allowed issuers over time, and revocation handling.
Reference helper scripts live in the MDA reference implementation:
scripts/sigstore-bundle-to-mda-signature.{py,sh}— extract and map a Sigstore Bundle v0.3 (sigstore-python/sigstore-gooutput) to a YAMLsignatures[]entry suitable for splicing into frontmatterscripts/mda-signature-to-sigstore-bundle.{py,sh}— inverse, useful when handing off an MDA-signed artifact to a verifier that wants a Sigstore Bundle
These are convenience tooling, not normative requirements. Independent implementations MAY ship equivalent helpers under different names.
{ "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial": { "certificate": { "rawBytes": "<base64 cert>" }, "tlogEntries": [{ "logIndex": "87654321", "logId": { "keyId": "<base64 log-id>" }, "kindVersion": { "kind": "dsse", "version": "0.0.1" }, "inclusionProof": { ... }, "canonicalizedBody": "<base64>" }] }, "dsseEnvelope": { "payload": "<base64 payload-bytes>", "payloadType": "application/vnd.mda.integrity+json", "signatures": [{ "sig": "<base64 signature>", "keyid": "" }] } }