Implementation Plan: Auto-Update Signature Verification
Status: Review Research Doc: RESEARCH-SIGNATURE-VERIFICATION.md Issue: #421 - CRITICAL: Implement actual signature verification for auto-update Author: Claude (Terraphim AI Design Agent) Date: 2025-01-12 Estimated Effort: 10-16 hours (2-3 days)
Overview
Summary
Implement actual cryptographic signature verification for the auto-update system using Minisign (Ed25519 signatures). The current placeholder implementation in crates/terraphim_update/src/signature.rs always returns VerificationResult::Valid, creating a critical security vulnerability.
Approach
Chosen Solution: Minisign with Ed25519 signatures
- Verification Library:
minisign-verifycrate (pure Rust, zero dependencies) - Signing Tool:
minisignCLI for release pipeline - Signature Format:
.minisigfiles alongside release binaries - Public Key Storage: Embedded in binary with optional config override
Scope
In Scope:
- Implement actual Ed25519 signature verification using Minisign
- Generate signing key pair for Terraphim AI releases
- Update release scripts to sign Linux/macOS/Windows binaries
- Download signature files from GitHub Releases
- Add comprehensive test coverage (unit, integration, property-based)
- Update integration tests to verify signature checking
- Document public key distribution mechanism
Out of Scope:
- PGP/OpenPGP compatibility (use Sequoia if needed in future)
- Sigstore/Cosign integration (evaluate for v2)
- Binary encryption (only signing required)
- Multi-signature support
- Key rotation implementation (framework only, deferred to v1.1)
Architecture
Component Diagram
┌─────────────────────────────────────────────────────────────┐
│ Update Flow (Current) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ GitHub Releases │
│ ├─ terraphim_server (binary) │
│ ├─ terraphim_server.minisig (NEW - signature file) │
│ └─ terraphim_server.minisig.sig (optional signature) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ terraphim_update::downloader │
│ ├─ Download binary │
│ └─ Download .minisig (NEW) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ terraphim_update::signature (MODIFIED) │
│ ├─ Embedded public key │
│ ├─ verify_binary_signature() → minisign-verify │
│ └─ Return VerificationResult │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Update Installation │
│ ├─ Valid → Install binary │
│ ├─ Invalid → Reject, log error │
│ └─ Missing → Fail or warn (configurable) │
└─────────────────────────────────────────────────────────────┘Data Flow
[Release Build] → [Sign with minisign CLI] → [Upload to GitHub]
│
▼
[User checks for updates] → [Download binary + .minisig]
│
▼
[Verify with embedded public key]
│
┌───────────────┴───────────────┐
▼ ▼
[Valid Signature] [Invalid/Missing]
│ │
▼ ▼
[Install Binary] [Reject Update]
│ │
└───────────────┬───────────────┘
▼
[Update Complete]Key Design Decisions
| Decision | Rationale | Alternatives Rejected | |----------|-----------|----------------------| | Minisign over raw Ed25519 | Established file format, battle-tested, simple API | Raw ed25519-dalek requires custom format design | | Embedded public key | No user configuration needed, offline verification | Config file only adds complexity | | .minisig extension | Standard minisign convention, recognizable | .sig (ambiguous), .asc (PGP-specific) | | Optional config override | Flexibility for key rotation without rebuild | Embedded only requires rebuild for rotation | | Fail on invalid signature | Security-critical, better to fail safe | Warn-only allows compromised updates | | Separate signature download | Smaller downloads, can verify before full binary | Combined format requires full download |
File Changes
New Files
| File | Purpose |
|------|---------|
| crates/terraphim_update/src/keys/default.pub | Embedded default public key (base64 encoded) |
| scripts/sign-release.sh | Sign release binaries with minisign |
| .github/workflows/release-sign.yml | CI/CD signature generation workflow |
| crates/terraphim_update/tests/signature_test.rs | Comprehensive signature verification tests |
| docs/updates/KEYS.md | Public key distribution documentation |
Modified Files
| File | Changes |
|------|---------|
| crates/terraphim_update/Cargo.toml | Add minisign-verify = "0.7" dependency |
| crates/terraphim_update/src/signature.rs | Replace placeholder with minisign implementation |
| crates/terraphim_update/src/lib.rs | Load embedded public key, pass to verify functions |
| crates/terraphim_update/src/downloader.rs | Download .minisig files alongside binaries |
| scripts/release.sh | Call sign-release.sh after building binaries |
| README.md | Document signature verification and public key |
| crates/terraphim_update/tests/integration_test.rs | Add signature verification to integration tests |
Deleted Files
| File | Reason | |------|--------| | None | No files deleted, only replacement of placeholder code |
API Design
Public Types
/// Result of a signature verification operation
/// Public key for signature verification
/// Trust level for public keys (reserved for future key rotation)
Public Functions
/// Verify the signature of a downloaded binary using Minisign
///
/// This function performs cryptographic verification of a binary's signature
/// using Ed25519 signatures via the minisign-verify crate. It ensures the
/// binary has not been tampered with and originates from a trusted source.
///
/// # Arguments
/// * `binary_path` - Path to the binary file to verify
/// * `signature_path` - Path to the .minisig signature file
///
/// # Returns
/// * `Ok(VerificationResult)` - Result of verification
/// - `Valid`: Cryptographic verification passed
/// - `Invalid`: Signature verification failed (binary may be tampered)
/// - `MissingSignature`: No .minisig file found
/// - `Error`: Verification error occurred
/// * `Err(SignatureError)` - Invalid input or I/O error
///
/// # Errors
/// Returns `SignatureError::BinaryNotFound` if binary doesn't exist
/// Returns `SignatureError::SignatureNotFound` if .minisig doesn't exist
/// Returns `SignatureError::InvalidPublicKey` if embedded key is corrupt
///
/// # Security
/// This function uses constant-time comparisons and does not short-circuit
/// on signature validation to prevent timing attacks.
///
/// # Example
/// ```no_run
/// use terraphim_update::signature::verify_binary_signature;
/// use std::path::Path;
///
/// let result = verify_binary_signature(
/// Path::new("/tmp/terraphim_server"),
/// Path::new("/tmp/terraphim_server.minisig")
/// ).unwrap();
///
/// match result {
/// VerificationResult::Valid => println!("Signature verified"),
/// VerificationResult::Invalid { reason } => eprintln!("Invalid: {}", reason),
/// VerificationResult::MissingSignature => eprintln!("No signature found"),
/// VerificationResult::Error(msg) => eprintln!("Error: {}", msg),
/// }
/// ```
;
/// Verify signature using a custom public key (for testing or advanced users)
///
/// Similar to `verify_binary_signature` but allows specifying a custom public key
/// instead of using the embedded default. This is useful for:
/// - Testing signature verification with different keys
/// - Advanced users who want to override the embedded key
/// - Key rotation scenarios where multiple keys are trusted
///
/// # Arguments
/// * `binary_path` - Path to the binary file to verify
/// * `signature_path` - Path to the .minisig signature file
/// * `public_key_base64` - Public key in base64 format (minisign format)
///
/// # Returns
/// * `Ok(VerificationResult)` - Result of verification
/// * `Err(SignatureError)` - Invalid input or corrupt key
;
/// Get the embedded default public key
///
/// Returns the public key that is compiled into the binary. This key is used
/// by default for all signature verification operations.
///
/// # Returns
/// * `PublicKey` - The embedded public key
///
/// # Example
/// ```no_run
/// use terraphim_update::signature::get_embedded_public_key;
///
/// let key = get_embedded_public_key();
/// println!("Public key: {}", key.key_data);
/// ```
;
/// Check if signature verification is available and configured
///
/// Returns true if signature verification is available (always true in
/// production, may be false in test environments or feature builds).
///
/// # Returns
/// * `bool` - true if verification is available
;
/// Get the expected signature file name for a binary
///
/// # Arguments
/// * `binary_name` - Name of the binary (e.g., "terraphim_server")
///
/// # Returns
/// * `String` - Expected signature file name (e.g., "terraphim_server.minisig")
///
/// # Example
/// ```no_run
/// use terraphim_update::signature::get_signature_filename;
///
/// let sig_file = get_signature_filename("terraphim_server");
/// assert_eq!(sig_file, "terraphim_server.minisig");
/// ```
;Error Types
/// Error types for signature verification operations
Internal Types (Downloader Module)
/// Download result including signature verification
/// Download configuration with signature verification
Test Strategy
Unit Tests
| Test | Location | Purpose |
|------|----------|---------|
| test_verify_valid_signature | signature.rs | Verify correctly signed binary passes |
| test_verify_invalid_signature | signature.rs | Reject tampered binary (wrong content) |
| test_verify_wrong_key | signature.rs | Reject signature signed by wrong key |
| test_verify_missing_binary | signature.rs | Return error if binary doesn't exist |
| test_verify_missing_signature | signature.rs | Return MissingSignature if .minisig missing |
| test_verify_malformed_signature | signature.rs | Reject corrupt signature file |
| test_verify_malformed_public_key | signature.rs | Error on corrupt public key |
| test_verify_custom_key | signature.rs | Test custom key override function |
| test_get_embedded_public_key | signature.rs | Verify embedded key is valid format |
| test_signature_filename_generation | signature.rs | Verify correct .minisig naming |
| test_verification_result_equality | signature.rs | Test enum comparison |
| test_verification_result_display | signature.rs | Test debug formatting |
Integration Tests
| Test | Location | Purpose |
|------|----------|---------|
| test_full_update_with_valid_signature | integration_test.rs | Download binary, verify, install |
| test_update_rejects_invalid_signature | integration_test.rs | Reject tampered update |
| test_update_handles_missing_signature | integration_test.rs | Configurable behavior (fail or warn) |
| test_signature_download_fallback | integration_test.rs | Handle network errors gracefully |
| test_concurrent_verification | integration_test.rs | Multiple verifications don't interfere |
| test_backup_rollback_preserves_verification | integration_test.rs | Verify rollback doesn't break checks |
Property Tests
// Test that verification never panics on arbitrary inputs
proptest!
// Test that valid signatures always verify
proptest! Security Tests
| Test | Location | Purpose |
|------|----------|---------|
| test_tampered_binary_rejected | signature_test.rs | Modified binary content fails |
| test_tampered_signature_rejected | signature_test.rs | Modified signature fails |
| test_wrong_key_rejected | signature_test.rs | Signature from different key fails |
| test_timing_attack_resistance | signature_test.rs | Verify constant-time comparison |
Performance Tests
Implementation Steps
Step 1: Dependency Setup
Files: crates/terraphim_update/Cargo.toml
Description: Add minisign-verify dependency
Tests: Compile verification
Estimated: 15 minutes
Dependencies: None
[dependencies]
# Existing dependencies...
minisign-verify = "0.7"Step 2: Generate Signing Key Pair
Files: crates/terraphim_update/src/keys/default.pub
Description: Generate Minisign key pair for releases
Tests: Verify key format is valid
Estimated: 30 minutes
Dependencies: None
Commands:
# Generate key pair
# Extract public key for embedding
# Store private key in GitHub Actions Secrets
# (Copy content of terraphim-release.key to MINISIGN_PRIVATE_KEY secret)Key File Format:
RWT+5ZvQzV/5/K5Z9Y3v6Y8V6Z8Z6Z9Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Step 3: Implement Verification Functions
Files: crates/terraphim_update/src/signature.rs
Description: Replace placeholder with minisign implementation
Tests: Unit tests for all verification paths
Dependencies: Steps 1-2
Estimated: 4-6 hours
Key Changes:
use ;
use ;
use fs;
use Path;
use Error;
use ;
// Embedded public key (generated in Step 2)
const DEFAULT_PUBLIC_KEY: &str = include_str!;
Step 4: Update Downloader Module
Files: crates/terraphim_update/src/downloader.rs
Description: Download signature files alongside binaries
Tests: Integration tests for download + verify flow
Dependencies: Step 3
Estimated: 2-3 hours
Key Changes:
/// Download binary and signature from GitHub release
pub async
/// Construct signature URL from binary URL
Step 5: Create Release Signing Script
Files: scripts/sign-release.sh
Description: Sign all release binaries with minisign
Tests: Test signing produces valid .minisig files
Dependencies: Step 2
Estimated: 1 hour
#!/usr/bin/env bash
SCRIPT_DIR=""
PROJECT_ROOT=""
# Colors
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
# Check if minisign is installed
if ! ; then
fi
# Check for private key
if ; then
fi
# Create temp key file
TEMP_KEY=
# Decode private key from base64 (if stored in CI)
# Sign all binaries in release directory
RELEASE_DIR=""
if ; then
fi
SIGNED_COUNT=0
for; do
if ; then
binary_name=
# Skip if already signed
if ; then
continue
fi
# Sign with minisign (will prompt for password if key is encrypted)
if ; then
else
fi
fi
done
Step 6: Update Release Scripts
Files: scripts/release.sh
Description: Integrate signing into release workflow
Tests: Run full release, verify signatures generated
Dependencies: Step 5
Estimated: 1 hour
Changes to release.sh:
# After build_binaries() function call
# Add to main() after build_binaries
# Modify create_github_release() to include .minisig files
|| {
}Step 7: Add Comprehensive Tests
Files: crates/terraphim_update/tests/signature_test.rs
Description: Create dedicated signature verification test suite
Tests: All unit, integration, property, security tests
Dependencies: Step 3
Estimated: 3-4 hours
Test File Structure:
//! Signature verification tests
use fs;
use Write;
use PathBuf;
use TempDir;
use *;
use ;
// Helper: Generate test key pair
// Helper: Sign a binary with test key
// ... more tests for all scenariosStep 8: Update Integration Tests
Files: crates/terraphim_update/tests/integration_test.rs
Description: Add signature verification to existing integration tests
Tests: Full update flow with signature verification
Dependencies: Steps 3-4
Estimated: 2 hours
Step 9: Create Documentation
Files: docs/updates/KEYS.md, README.md
Description: Document public key distribution and verification
Tests: Doc tests compile
Dependencies: Step 7
Estimated: 1 hour
KEYS.md Content:
RWT+5ZvQzV/5/K5Z9Y3v6Y8V6Z8Z6Z9Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6Z6
**Key ID**: `RWT+5ZvQzV/5/K5Z9Y`
**Algorithm**: Ed25519
**Trust Level**: Primary
## Verification
Users can verify release binaries using the embedded public key or the
key above. Signatures are distributed as `.minisig` files alongside
binaries in GitHub Releases.
## Key Rotation
If the primary key is compromised, a new key will be generated and
announced via:
1. Security advisory
2. GitHub repository security bulletin
3. Update to this file
## Reporting Issues
If signature verification fails or you suspect a compromised key:
- Report immediately: https://github.com/terraphim/terraphim-ai/security/advisories
- Do NOT install the binary
- Check the repository for security announcementsStep 10: CI/CD Integration
Files: .github/workflows/release-sign.yml
Description: Automated signature generation in releases
Tests: Run release workflow, verify signatures generated
Dependencies: Steps 2, 5, 6
Estimated: 1-2 hours
name: Release Signing
on:
workflow_dispatch:
inputs:
version:
description: "Release version"
required: true
ref:
description: "Git ref to build"
required: true
jobs:
sign-release:
name: Sign Release Binaries
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.ref }}
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install minisign
run: cargo install minisign
- name: Build release binaries
run: |
./scripts/build-release.sh --version ${{ inputs.version }}
- name: Sign binaries
env:
MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }}
run: |
./scripts/sign-release.sh release-artifacts/
- name: Verify signatures
run: |
for sig in release-artifacts/*.minisig; do
echo "Verifying $sig"
# Verification test using embedded key
done
- name: Upload signatures
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "v${{ inputs.version }}" release-artifacts/*.minisigRollback Plan
If issues discovered during implementation:
Immediate Rollback
- Revert signature.rs changes: Restore placeholder implementation
- Remove minisign-verify dependency: Revert Cargo.toml changes
- Disable signature generation: Skip sign-release.sh in release workflow
- Update issue: Document rollback reason in #421
Feature Flag
// Add to lib.rs
pub use verify_binary_signature;
Graceful Degradation
Migration
Database Changes
None - No database schema changes required.
Data Migration
None - No data migration required.
Configuration Migration
Before (Placeholder):
# No signature verification configurationAfter (With Verification):
[update]
# Enable signature verification (default: true)
verify_signatures = true
# Allow updates without signatures (fail-open)
allow_missing_signatures = false
# Optional: Override embedded public key
# public_key_path = "/path/to/custom.pub"Migration Script: None - Configuration is optional with sensible defaults.
Dependencies
New Dependencies
| Crate | Version | Justification |
|-------|---------|---------------|
| minisign-verify | 0.7 | Zero-dependency Ed25519 signature verification |
| thiserror | 1.0 | Derive error enums (already in workspace) |
Dependency Updates
| Crate | From | To | Reason | |-------|------|-----|--------| | None | - | - | No dependency updates required |
Removed Dependencies
None - All existing dependencies retained.
Performance Considerations
Expected Performance
| Metric | Target | Measurement |
|--------|--------|-------------|
| Verification time (1MB binary) | < 10ms | Benchmark with cargo bench |
| Verification time (100MB binary) | < 100ms | Benchmark with cargo bench |
| Memory overhead | < 1MB | Profiling with heaptrack |
| Binary size increase | < 100KB | ls -lh before/after |
Benchmarks to Add
// In crates/terraphim_update/benches/signature.rs
use ;
use fs;
use TempDir;
criterion_group!;
criterion_main!;Optimization Opportunities
- Parallel verification: Verify multiple signatures concurrently
- Signature caching: Cache verification results for same binary
- Lazy verification: Only verify when binary is about to execute
- Stream verification: Verify during download (requires streamable signatures)
Security Considerations
Constant-Time Verification
Minisign uses Ed25519 which provides:
- Constant-time signature verification
- No timing side channels
- Deterministic validation
Public Key Storage
Embedded Key:
- Compiled into binary (read-only memory)
- Cannot be modified at runtime
- Single source of truth
Config Override (Optional):
- Allows key rotation without rebuild
- Must be protected by file permissions (0600)
- Validation on load (must be valid base64, minisign format)
Key Compromise Response
Immediate Actions:
- Revoke compromised key: Add to revocation list in code
- Generate new keypair: Create new signing keys offline
- Emergency release: Release new version signed with new key
- Security advisory: Publish disclosure on GitHub Security Advisories
- Update embedded key: Rebuild with new public key
Long-term Actions:
- Post-mortem: Analyze compromise vector
- Improve procedures: Strengthen key storage and access controls
- Key rotation schedule: Implement regular key rotation (annual)
Open Items
| Item | Status | Owner | Priority | |------|--------|-------|----------| | Generate signing key pair | Pending | TBD | P0 | | Configure GitHub Actions secrets | Pending | TBD | P0 | | Property test implementation | Deferred | TBD | P2 | | Key rotation framework | Deferred | TBD | P2 | | Sigstore integration evaluation | Deferred | TBD | P3 |
Approval Checklist
- [x] Research document approved (Phase 1)
- [x] Design document complete (Phase 2)
- [ ] Specification interview completed (Phase 2.5)
- [ ] All file changes listed
- [ ] All public APIs defined
- [ ] Test strategy complete
- [ ] Steps sequenced with dependencies
- [ ] Performance targets set
- [ ] Security considerations addressed
- [ ] Rollback plan documented
- [ ] Human approval received
Next Steps
Phase 2.5: Specification Interview (Optional but Recommended)
Before proceeding to implementation, conduct a specification interview using the disciplined-specification skill to:
- Deep dive into edge cases and failure modes
- Verify acceptance criteria completeness
- Surface hidden requirements
- Validate implementation assumptions
Questions to Explore:
- What should happen if signature verification fails in production?
- Should there be an escape hatch for emergency updates?
- How do we handle key rotation without breaking existing installs?
- What error messages are most helpful to users?
- How do we verify the signature verification implementation itself?
Phase 3: Implementation (After Approval)
Proceed to implementation using the disciplined-implementation skill:
- Execute implementation steps in sequence
- Write tests before code (TDD approach)
- Commit each step independently
- Run full test suite after each step
- Update documentation continuously
Appendix
Test Vectors
From Wycheproof Project:
// Valid signature test vector
const TEST_VALID_SIGNATURE: & = b"...";
// Invalid signature test vector (wrong message)
const TEST_INVALID_WRONG_MESSAGE: & = b"...";
// Invalid signature test vector (wrong key)
const TEST_INVALID_WRONG_KEY: & = b"...";Example Release Workflow
# 1. Build release
# 2. Sign release
MINISIGN_PRIVATE_KEY="" \
# 3. Verify signatures
for; do
done
# 4. Create GitHub release
Verifying Release as User
# Download release
# Verify signature
# Extract and install
Status: Ready for Specification Interview or Approval Next Phase: Phase 2.5 (Specification) or Phase 3 (Implementation) Completion Date: 2025-01-12