Status
Draft v0.1

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

AlgorithmKey SizeRecommended
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 ECDSA P-256 Key Pair
# 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

TechnologyDescriptionCloud 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

⚙️
Processing
🔐
Control Plane
🔑
HSM/TEE

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

  1. Construct the in-toto statement — Create the attestation with subject and predicate
  2. Serialize to JSON — Canonicalize the JSON (sorted keys, no extra whitespace)
  3. Base64 encode — Encode the serialized statement
  4. Compute PAE — Pre-Authentication Encoding with payload type
  5. Sign the PAE — Using ECDSA P-256 or equivalent algorithm
  6. 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

1. Parse Envelope
2. Fetch Public Key
3. Compute PAE
4. Verify Signature
5. Check Policy
  1. Parse the DSSE envelope — Extract payload, payload type, and signatures
  2. Fetch the public key — Using keyid, retrieve from trusted key registry
  3. Recompute PAE — Same Pre-Authentication Encoding as signing
  4. Verify cryptographic signature — Using the public key
  5. 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 TypeDescriptionExample
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:

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

TSAURLNotes
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

Request RFC 3161 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

PitfallRiskMitigation
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

AlgorithmJOSE IdentifierOID
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