Documentation
AdvancedSigning Keys

Signing Keys

Ed25519 key generation, management, rotation, and security best practices for evidence bundles.

Ed25519 Overview

Algorithm: EdDSA (Edwards-curve Digital Signature Algorithm) with Curve25519

Properties:

  • Fast: Signature generation and verification < 1ms
  • Small: 64-byte signatures, 32-byte keys
  • Secure: 128-bit security level
  • Deterministic: Same input always produces same signature
  • Standard: RFC 8032

Use in evidence SDK:

  • Sign checksums.sha256 file
  • Signature stored in signature.sig
  • Public key embedded in manifest.json
  • Verification performed by auditors or evidence CLI

Key Generation

Generate key pair:

evidence init --generate-keys

Output:

Generating Ed25519 key pair...

✓ Private key: ~/.evidence/keys/private.pem
✓ Public key: ~/.evidence/keys/public.pem

⚠ IMPORTANT:
  - Keep private.pem SECRET (never commit to git)
  - Share public.pem with auditors for bundle verification
  - Back up private key securely (encrypted)

Key ID: evidence-sdk-v1
Fingerprint: abc123def456ghi789jkl012mno345pqr678stu901vwx234yz0567

Created files:

~/.evidence/keys/
├── private.pem    # 32 bytes (256 bits) - KEEP SECRET
└── public.pem     # 32 bytes (256 bits) - OK to share

Manual Generation (Advanced)

Using OpenSSL:

# Generate private key
openssl genpkey -algorithm ed25519 -out private.pem

# Extract public key
openssl pkey -in private.pem -pubout -out public.pem

Using Node.js (noble-ed25519):

import * as ed from '@noble/ed25519';
import { writeFileSync } from 'fs';

// Generate key pair
const privateKey = ed.utils.randomPrivateKey();
const publicKey = await ed.getPublicKey(privateKey);

// Convert to PEM format
const privatePem = `-----BEGIN PRIVATE KEY-----
${Buffer.from(privateKey).toString('base64')}
-----END PRIVATE KEY-----`;

const publicPem = `-----BEGIN PUBLIC KEY-----
${Buffer.from(publicKey).toString('base64')}
-----END PUBLIC KEY-----`;

// Save to files
writeFileSync('private.pem', privatePem);
writeFileSync('public.pem', publicPem);

Key Storage

Local Development

Standard location:

~/.evidence/keys/
├── private.pem
└── public.pem

File permissions:

# Private key: readable only by owner
chmod 600 ~/.evidence/keys/private.pem

# Public key: readable by all (OK to share)
chmod 644 ~/.evidence/keys/public.pem

# Verify
ls -l ~/.evidence/keys/
# -rw------- 1 user user ... private.pem
# -rw-r--r-- 1 user user ... public.pem

Production Servers

Secure location:

# Create secure directory
sudo mkdir -p /etc/evidence/keys
sudo chown root:root /etc/evidence/keys
sudo chmod 700 /etc/evidence/keys

# Copy keys
sudo cp private.pem /etc/evidence/keys/
sudo chmod 600 /etc/evidence/keys/private.pem

# Configuration
bundle:
  signing:
    private_key_path: /etc/evidence/keys/private.pem

CI/CD Environments

GitHub Actions:

name: Evidence Collection
on:
  schedule:
    - cron: '0 0 1 * *'

jobs:
  collect:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup signing key
        run: |
          mkdir -p ~/.evidence/keys
          echo '${{ secrets.EVIDENCE_SIGNING_KEY }}' > ~/.evidence/keys/private.pem
          chmod 600 ~/.evidence/keys/private.pem

      - name: Collect evidence
        run: evidence collect

      - name: Cleanup
        if: always()
        run: rm -f ~/.evidence/keys/private.pem

Add secret:

  1. Copy private key content:
    cat ~/.evidence/keys/private.pem | pbcopy
  2. Repository → Settings → Secrets and variables → Actions
  3. New repository secret
  4. Name: EVIDENCE_SIGNING_KEY
  5. Value: Paste private key content
  6. Click "Add secret"

Key Rotation

Why Rotate Keys

Security best practices:

  • Limit exposure window if key compromised
  • Comply with security policies (annual rotation)
  • Maintain cryptographic hygiene

Recommended rotation schedule:

  • Production: Every 365 days
  • After security incident: Immediately
  • After team member with key access departs: Within 7 days

Rotation Procedure

Step 1: Generate new key pair

evidence init --generate-keys --key-id production-2027-01

Or with custom location:

openssl genpkey -algorithm ed25519 -out ~/.evidence/keys/private-2027-01.pem
openssl pkey -in ~/.evidence/keys/private-2027-01.pem -pubout -out ~/.evidence/keys/public-2027-01.pem

Step 2: Update configuration

bundle:
  signing:
    private_key_path: ~/.evidence/keys/private-2027-01.pem
    key_id: production-2027-01

Step 3: Test collection

evidence collect

Verify:

# Check bundle was signed with new key
tar -xzf evidence-bundle-*.tar.gz
cat manifest.json | jq '.signer.key_id'
# Output: "production-2027-01"

Step 4: Archive old key

# Move to archive (don't delete yet!)
mkdir -p ~/.evidence/keys/archive
mv ~/.evidence/keys/private.pem ~/.evidence/keys/archive/private-2026-01.pem
mv ~/.evidence/keys/public.pem ~/.evidence/keys/archive/public-2026-01.pem

Why keep old keys?

  • Verify historical bundles
  • Auditors may request verification of old bundles
  • Recommended retention: 3 years

Step 5: Update public key distribution

  1. Share new public key with auditors
  2. Upload to evidence platform (Settings → Signing Keys)
  3. Update documentation
  4. Notify compliance team

Zero-Downtime Rotation

For CI/CD environments:

Step 1: Add new key alongside old key

# GitHub Actions - both keys available
- name: Setup signing keys
  run: |
    mkdir -p ~/.evidence/keys
    echo '${{ secrets.EVIDENCE_SIGNING_KEY_OLD }}' > ~/.evidence/keys/private-old.pem
    echo '${{ secrets.EVIDENCE_SIGNING_KEY_NEW }}' > ~/.evidence/keys/private-new.pem

Step 2: Update configuration to use new key

bundle:
  signing:
    private_key_path: ~/.evidence/keys/private-new.pem

Step 3: Test in staging

Step 4: Deploy to production

Step 5: Remove old key (after 30 days)


Key Distribution

Sharing Public Keys

Public keys are safe to share - they only verify signatures, cannot create them.

Distribution methods:

1. Email to auditor:

cat ~/.evidence/keys/public.pem | mail -s "Evidence Signing Public Key" auditor@compliance-firm.com

2. Upload to evidence platform:

  • Log in to evidence platform
  • Settings → Signing Keys
  • Click "Upload Public Key"
  • Select public.pem
  • Add key ID and description
  • Click "Save"

3. Include in documentation:

## Public Key Verification

Verify evidence bundles with our public key:

\`\`\`
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAXyz9876abc123def456ghi789jkl012mno345
-----END PUBLIC KEY-----
\`\`\`

Key ID: production-2026-01
Fingerprint: abc123def456ghi789jkl012mno345pqr678stu901vwx234

Out-of-Band Verification

Best practice for auditors:

  1. Receive bundle (email, file transfer)
  2. Receive public key via separate channel (phone, Slack, verified email)
  3. Verify fingerprint (voice call, video call)
  4. Verify bundle with confirmed public key

Generate fingerprint:

openssl pkey -pubin -in public.pem -outform DER | \
  openssl dgst -sha256 -binary | \
  base64

Example fingerprint:

abc123def456ghi789jkl012mno345pqr678stu901vwx234=

Confirm via phone:

  • "Alpha Bravo Charlie one two three..."
  • Read back for verification
  • Document confirmation

Multi-Environment Keys

Separate Keys Per Environment

Development:

bundle:
  signing:
    private_key_path: ~/.evidence/keys/dev-private.pem
    key_id: development-2026

Staging:

bundle:
  signing:
    private_key_path: ~/.evidence/keys/staging-private.pem
    key_id: staging-2026

Production:

bundle:
  signing:
    private_key_path: /etc/evidence/keys/production-private.pem
    key_id: production-2026-01

Benefits:

  • Compromise of dev key doesn't affect production
  • Different access control per environment
  • Clear distinction in bundle metadata

Key Inventory

Maintain key inventory document:

# Evidence Signing Key Inventory

## Active Keys

| Key ID | Environment | Created | Expires | Public Key Fingerprint |
|--------|-------------|---------|---------|------------------------|
| production-2026-01 | Production | 2026-01-01 | 2027-01-01 | abc123... |
| staging-2026-01 | Staging | 2026-01-15 | 2027-01-15 | def456... |
| development-2026 | Development | 2026-01-01 | Never | ghi789... |

## Archived Keys

| Key ID | Environment | Created | Rotated | Retention Until |
|--------|-------------|---------|---------|-----------------|
| production-2025-01 | Production | 2025-01-01 | 2026-01-01 | 2029-01-01 |
| staging-2025-01 | Staging | 2025-01-15 | 2026-01-15 | 2029-01-15 |

## Access Log

| Date | Action | Key ID | User | Notes |
|------|--------|--------|------|-------|
| 2026-01-01 | Generated | production-2026-01 | admin | Annual rotation |
| 2026-01-02 | Distributed | production-2026-01 | admin | Sent to auditor |
| 2026-01-30 | Archived | production-2025-01 | admin | 30-day grace period |

Key Backup

Encrypted Backup

Create encrypted backup:

# Backup with GPG
gpg --symmetric --cipher-algo AES256 ~/.evidence/keys/private.pem

# Output: private.pem.gpg (encrypted)

# Restore
gpg --decrypt private.pem.gpg > private.pem
chmod 600 private.pem

Password requirements:

  • Minimum 20 characters
  • Mix of uppercase, lowercase, numbers, symbols
  • Store password in separate location (password manager)

Cold Storage

For long-term archival:

  1. Encrypt key:

    gpg --symmetric --cipher-algo AES256 private.pem
  2. Create redundant copies:

    • Copy 1: Secure cloud storage (encrypted)
    • Copy 2: Physical USB drive (encrypted, locked safe)
    • Copy 3: Password manager secure notes
  3. Document recovery procedure:

    # Key Recovery Procedure
    
    1. Retrieve encrypted key from storage
    2. Decrypt with GPG password (stored in 1Password)
    3. Verify fingerprint matches inventory
    4. Copy to ~/.evidence/keys/
    5. Set permissions: chmod 600
    6. Test with verification

Verification

Verify Key Pair Match

Ensure private and public keys match:

# Extract public key from private key
openssl pkey -in private.pem -pubout -out public-from-private.pem

# Compare with existing public key
diff public.pem public-from-private.pem

# If identical, keys match (no output)

Verify Bundle Signature

Using evidence CLI:

evidence verify evidence-bundle-*.tar.gz --public-key public.pem

Manual verification:

# Extract bundle
tar -xzf evidence-bundle-*.tar.gz -C /tmp/bundle

# Verify with public key (requires Ed25519 library)
# Complex - use evidence CLI instead
evidence verify evidence-bundle-*.tar.gz

Security Best Practices

✅ DO

  • Generate unique keys per environment
  • Rotate keys annually (production)
  • Use strong file permissions (600 for private keys)
  • Back up keys encrypted
  • Archive old keys for 3+ years
  • Document all key operations
  • Verify fingerprints out-of-band
  • Use CI/CD secrets for keys, not files

❌ DON'T

  • Never commit private keys to git
  • Never share private keys (even within team)
  • Never use same key across environments
  • Never delete old keys immediately after rotation
  • Never store keys unencrypted in cloud storage
  • Never email private keys
  • Never screenshot or copy private keys to clipboard tools

Troubleshooting

Private Key Not Found

Symptom:

✗ Bundle signing failed

Error: ENOENT: no such file or directory, open '~/.evidence/keys/private.pem'

Solution:

# Check if file exists
ls -l ~/.evidence/keys/private.pem

# If not, generate new key pair
evidence init --generate-keys

# Or specify correct path in config

Permission Denied

Symptom:

✗ Bundle signing failed

Error: EACCES: permission denied, open '/etc/evidence/keys/private.pem'

Solution:

# Fix permissions
sudo chmod 600 /etc/evidence/keys/private.pem
sudo chown $USER:$USER /etc/evidence/keys/private.pem

Invalid Key Format

Symptom:

✗ Bundle signing failed

Error: Invalid private key format

Causes:

  • Key file corrupted
  • Wrong file (public key instead of private)
  • Not Ed25519 format

Solution:

# Verify key type
openssl pkey -in private.pem -text -noout

# Should show: ED25519 Private-Key

# If corrupted, restore from backup or generate new

Signature Verification Failed

Symptom:

✗ Signature verification failed

Signature does not match checksums file.

Causes:

  • Bundle modified after signing
  • Wrong public key used for verification
  • Bundle created with different private key

Solution:

# Verify public key matches bundle
tar -xzf bundle.tar.gz
cat manifest.json | jq '.signer.key_id'
# Compare with your key inventory

# Use correct public key
evidence verify bundle.tar.gz --public-key public-2026-01.pem

Key Lifecycle

Timeline

Day 0: Generate key pair
├── Save private key to secure location
├── Set file permissions (chmod 600)
├── Add to evidence.yaml configuration
└── Test with evidence collect

Day 1-7: Distribution
├── Share public key with auditors
├── Upload to evidence platform
├── Document in key inventory
└── Back up private key (encrypted)

Day 30-365: Active use
├── Monitor bundle signing
├── Verify periodic backups
└── Review key inventory quarterly

Day 365: Rotation
├── Generate new key pair
├── Update configuration
├── Test collection
├── Archive old key (don't delete)
└── Update public key distribution

Day 366-1095: Retention
├── Keep archived keys for verification
├── Update key inventory (archived status)
└── Maintain encrypted backups

Day 1095+: Disposal
├── Securely delete archived keys
├── Update inventory (disposed status)
└── Document disposal date

See Also