Signature & Verification Guide
Cryptographic signatures are central to Makoto's trust model. Starting at Level 2, all attestations must be signed to ensure authenticity and tamper-evidence. This guide covers key management, signature generation, verification, and security best practices.
Key Principle: Signatures bind attestations to identities. At L2, signatures prove who created the attestation. At L3, isolated signing infrastructure ensures even compromised processing code cannot forge attestations.
Overview
Makoto uses the DSSE (Dead Simple Signing Envelope) format for signed attestations, the same format used by SLSA and in-toto. This ensures interoperability with existing software supply chain security tooling.
Signature Requirements by Level
| Level | Signature Required | Key Storage | Timestamp |
|---|---|---|---|
| L1 | Optional | Any | Optional |
| L2 | Required | Software-protected | Required (RFC 3161) |
| L3 | Required | HSM/TEE isolated | Required (RFC 3161) |
DSSE Envelope Format
A signed Makoto attestation wraps the in-toto statement in a DSSE envelope. The envelope contains the payload (base64-encoded statement), payload type, and one or more signatures.
{
"payloadType": "application/vnd.in-toto+json",
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLC4uLn0=",
"signatures": [
{
"keyid": "SHA256:abc123...",
"sig": "MEUCIQD...base64-signature..."
}
]
}
Signature Computation
The signature is computed over a PAE (Pre-Authentication Encoding) of the payload:
// PAE (Pre-Authentication Encoding) PAE(type, payload) = "DSSEv1" + SP + LEN(type) + SP + type + SP + LEN(payload) + SP + payload // Where SP is a space (0x20) and LEN is the ASCII decimal length // Example: "DSSEv1 29 application/vnd.in-toto+json 123 {\"_type\":...}"
This encoding prevents type confusion attacks where an attacker might try to get a signature for one payload type accepted as another.
Signing Key Management
Proper key management is essential for maintaining the integrity of your attestations. The approach varies significantly between L2 and L3.
L2 Key Management
Software-protected keys with identity binding.
Supported Key Types
| Algorithm | Key Size | Recommended |
|---|---|---|
| ECDSA P-256 | 256-bit | Yes (primary) |
| ECDSA P-384 | 384-bit | Yes (high security) |
| Ed25519 | 256-bit | Yes (performance) |
| RSA-PSS | 3072+ bit | Acceptable (legacy) |
Key Generation
# Generate private key openssl ecparam -genkey -name prime256v1 -noout -out signing-key.pem # Extract public key openssl ec -in signing-key.pem -pubout -out signing-key.pub # Compute key ID (SHA256 fingerprint of public key) openssl ec -in signing-key.pem -pubout -outform DER | \ openssl dgst -sha256 -binary | base64
Key Storage Options
- Environment variables — Simple but less secure; suitable for development
- Secret managers — AWS Secrets Manager, HashiCorp Vault, Azure Key Vault
- Kubernetes Secrets — For containerized deployments
- Sigstore/Fulcio — Keyless signing using OIDC identity (recommended)
L3 Key Management
Hardware-isolated keys inaccessible to processing code.
L3 requires that signing keys are stored in isolated hardware that the data processing code cannot directly access. This ensures that even if the processing environment is compromised, attestations cannot be forged.
Supported Hardware
| Technology | Description | Cloud Options |
|---|---|---|
| HSM | Hardware Security Module — dedicated crypto hardware | AWS CloudHSM, Azure Dedicated HSM, GCP Cloud HSM |
| Cloud KMS | Managed key service with HSM backing | AWS KMS, Azure Key Vault, GCP Cloud KMS |
| TEE | Trusted Execution Environment — isolated CPU enclave | AWS Nitro Enclaves, Azure Confidential Computing |
Architecture Pattern
In L3 architecture, the processing code sends attestation requests to a trusted control plane. The control plane computes data hashes, constructs the attestation, and requests the HSM/TEE to sign. The processing code never has access to signing keys.
Keyless Signing with Sigstore
Sigstore enables keyless signing using short-lived certificates bound to OIDC identities. This eliminates the burden of long-term key management while providing strong identity binding.
# Sign attestation with Sigstore (using cosign)
cosign attest --type makoto --predicate attestation.json \
--fulcio-url https://fulcio.sigstore.dev \
--rekor-url https://rekor.sigstore.dev \
dataset:sha256:abc123...
Signature Generation
The signature generation process creates a DSSE envelope containing the signed attestation. This section covers the step-by-step process.
Generation Steps
- Construct the in-toto statement — Create the attestation with subject and predicate
- Serialize to JSON — Canonicalize the JSON (sorted keys, no extra whitespace)
- Base64 encode — Encode the serialized statement
- Compute PAE — Pre-Authentication Encoding with payload type
- Sign the PAE — Using ECDSA P-256 or equivalent algorithm
- Construct envelope — Wrap payload and signature(s) in DSSE format
Example: Python Signature Generation
import json import base64 import hashlib from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec def sign_attestation(statement: dict, private_key) -> dict: # 1. Serialize statement (canonical JSON) payload = json.dumps(statement, sort_keys=True, separators=(',', ':')) payload_bytes = payload.encode('utf-8') # 2. Base64 encode payload_b64 = base64.b64encode(payload_bytes).decode('ascii') # 3. Compute PAE payload_type = "application/vnd.in-toto+json" pae = f"DSSEv1 {len(payload_type)} {payload_type} {len(payload_bytes)} ".encode() pae += payload_bytes # 4. Sign signature = private_key.sign(pae, ec.ECDSA(hashes.SHA256())) sig_b64 = base64.b64encode(signature).decode('ascii') # 5. Compute key ID public_der = private_key.public_key().public_bytes(...) key_id = "SHA256:" + hashlib.sha256(public_der).hexdigest()[:16] # 6. Construct envelope return { "payloadType": payload_type, "payload": payload_b64, "signatures": [{"keyid": key_id, "sig": sig_b64}] }
Multi-Signature Support
DSSE envelopes can contain multiple signatures, useful for scenarios requiring multi-party attestation or key rotation:
{
"payloadType": "application/vnd.in-toto+json",
"payload": "...",
"signatures": [
{
"keyid": "SHA256:abc123...",
// Primary signer (data processor)
"sig": "MEUCIQD..."
},
{
"keyid": "SHA256:def456...",
// Secondary signer (platform attestation)
"sig": "MEQCIGx..."
}
]
}
Signature Verification
Consumers must verify signatures before trusting attestation claims. Verification confirms both the cryptographic validity and the identity binding.
Verification Steps
- Parse the DSSE envelope — Extract payload, payload type, and signatures
- Fetch the public key — Using keyid, retrieve from trusted key registry
- Recompute PAE — Same Pre-Authentication Encoding as signing
- Verify cryptographic signature — Using the public key
- Check identity policy — Verify signer is authorized for this data/pipeline
Example: Python Verification
def verify_attestation(envelope: dict, trusted_keys: dict) -> bool: # 1. Parse envelope payload_type = envelope["payloadType"] payload_b64 = envelope["payload"] payload_bytes = base64.b64decode(payload_b64) # 2. Recompute PAE pae = f"DSSEv1 {len(payload_type)} {payload_type} {len(payload_bytes)} ".encode() pae += payload_bytes # 3. Verify at least one signature for sig_info in envelope["signatures"]: key_id = sig_info["keyid"] signature = base64.b64decode(sig_info["sig"]) # Fetch public key from trusted registry if key_id not in trusted_keys: continue # Unknown key public_key = trusted_keys[key_id] try: public_key.verify(signature, pae, ec.ECDSA(hashes.SHA256())) return True # Valid signature found except InvalidSignature: continue return False # No valid signature
Trust Policies
Verification isn't just about cryptographic validity—you must also verify that the signer is authorized. Common policy patterns include:
| Policy Type | Description | Example |
|---|---|---|
| Key Allowlist | Only accept signatures from specific keys | Key fingerprint matches expected value |
| Identity-Based | Accept signatures from identities matching pattern | OIDC subject matches *@mycompany.com |
| Certificate Chain | Verify certificate chains to trusted CA | Certificate issued by Fulcio, chained to Sigstore root |
| Transparency Log | Require signature recorded in public log | Entry exists in Rekor transparency log |
Timestamp Authority Integration
Timestamps prove when an attestation was created, preventing backdating attacks. Makoto L2+ requires verifiable timestamps following RFC 3161.
Why Timestamps Matter
Threat D8: Time Manipulation — Without trusted timestamps, an attacker could backdate attestations to claim data existed at a time when it didn't, or to bypass time-based policies. RFC 3161 timestamps from a trusted TSA (Timestamp Authority) provide cryptographic proof of when the signature was created.
RFC 3161 Timestamp Structure
A timestamp token contains:
- Message imprint — Hash of the signed data (the DSSE signature)
- Serial number — Unique identifier from the TSA
- Generation time — When the TSA created the token
- TSA signature — The authority's signature over the token
Adding Timestamps to Attestations
{
"payloadType": "application/vnd.in-toto+json",
"payload": "...",
"signatures": [
{
"keyid": "SHA256:abc123...",
"sig": "MEUCIQD...",
"timestamp": {
"authority": "https://freetsa.org/tsr",
"token": "MIIEpgYJKoZI...base64-encoded-rfc3161-token..."
}
}
]
}
Timestamp Authorities
| TSA | URL | Notes |
|---|---|---|
| Sigstore TSA | https://timestamp.sigstore.dev |
Free, integrated with Sigstore ecosystem |
| FreeTSA | https://freetsa.org/tsr |
Free, RFC 3161 compliant |
| DigiCert | http://timestamp.digicert.com |
Commercial, high availability |
| Sectigo | http://timestamp.sectigo.com |
Commercial, widely trusted |
Obtaining a Timestamp
# 1. Create timestamp request from signature hash openssl ts -query -data signature.bin -sha256 -out request.tsq # 2. Send to TSA curl -H "Content-Type: application/timestamp-query" \ --data-binary @request.tsq \ https://freetsa.org/tsr -o response.tsr # 3. Verify timestamp response openssl ts -verify -data signature.bin -in response.tsr \ -CAfile freetsa-cacert.pem
Security Best Practices
Follow these practices to maintain the integrity of your signing infrastructure and protect against common attack vectors.
Key Management
Rotate Keys Regularly
Rotate signing keys at least annually, or immediately if compromise is suspected. Use key versioning to support graceful transitions.
Principle of Least Privilege
Signing keys should only be accessible to the specific service that needs them. Use IAM policies, network segmentation, and access controls.
Maintain Key Inventory
Track all signing keys, their purposes, rotation schedules, and authorized users. Immediately revoke keys when personnel leave.
Signing Operations
Sign Immediately After Processing
Generate attestations as close as possible to the processing operation. Delays create windows for tampering.
Include All Relevant Metadata
Attestations should capture sufficient context (timestamps, versions, configurations) to enable meaningful verification.
Log All Signing Operations
Maintain immutable audit logs of all signing operations, including key ID, timestamp, and attestation hash.
Verification
Verify Before Trust
Always verify signatures before using attestation claims. Never trust attestations from unknown or untrusted signers.
Check Timestamp Validity
Verify that timestamps are from trusted TSAs and fall within acceptable time windows for your use case.
Verify Full Chain
For data with lineage, verify the entire attestation chain back to the origin. A single unverified link breaks the chain of trust.
Common Pitfalls to Avoid
| Pitfall | Risk | Mitigation |
|---|---|---|
| Storing private keys in code | Keys leaked via source control | Use secret managers or environment variables |
| Sharing keys across environments | Dev key compromise affects prod | Separate keys per environment |
| Ignoring key expiration | Signatures become unverifiable | Monitor and rotate before expiry |
| Skipping timestamp verification | Backdating attacks possible | Always verify RFC 3161 timestamps |
| Trusting all signers equally | Unauthorized attestations accepted | Implement identity-based policies |
Quick Reference
Algorithm Identifiers
| Algorithm | JOSE Identifier | OID |
|---|---|---|
| ECDSA P-256 | ES256 |
1.2.840.10045.4.3.2 |
| ECDSA P-384 | ES384 |
1.2.840.10045.4.3.3 |
| Ed25519 | EdDSA |
1.3.101.112 |
| RSA-PSS (SHA-256) | PS256 |
1.2.840.113549.1.1.10 |
Related Specifications
- DSSE (Dead Simple Signing Envelope) — Envelope format
- in-toto Attestation Framework — Statement format
- RFC 3161 — Timestamp protocol
- Sigstore Documentation — Keyless signing
- SLSA Verification — Related verification guidance