Watch
1
0
Fork
You've already forked RedFlag
0
RedFlag/RAF/verification/02-agent-verification.md
Fimeg d9ba008f67 raf: sync docs to code + relative cross-reference links
Handler/migration counts, calculateBackoff -> calculateDelay, machine-id
binding (cross-platform machineid + fallbacks, not hostname), last-reviewed
dates, and made [[cross-references]] relative so they resolve.
2026-06-15 09:39:34 -04:00

162 lines
No EOL
4.8 KiB
Markdown

# Agent-Side Verification
**Agent verifies commands and binaries before execution.**
---
## Overview
Agent verifies Ed25519 signatures, timestamps, and nonces before executing commands.
**Cross-references:**
- [verification/01-signing-pipeline](01-signing-pipeline.md) (server-side signing)
- [verification/03-key-rotation](03-key-rotation.md) (key rotation)
- [verification/04-replay-protection](04-replay-protection.md) (replay protection)
---
## Public Key Caching (TOFU)
**Method:** Trust On First Use — TTL+key_id cache with rotation awareness
**File:** `agent/internal/crypto/pubkey.go`
```go
func FetchAndCacheServerPublicKey(serverURL string) (ed25519.PublicKey, error) {
// 1. Check if cache is valid (TTL + key_id match)
if meta, err := loadCacheMetadata(); err == nil && meta.KeyID != "" && !meta.IsExpired() {
if cachedKey, loadErr := LoadCachedPublicKey(); loadErr == nil {
return cachedKey, nil
}
}
// 2. Fetch from server GET /api/v1/public-key
pubKeyBytes, err := httpGetPublicKey(serverURL)
if err != nil {
return nil, fmt.Errorf("failed to fetch public key: %w", err)
}
// 3. Cache to disk
if err := cachePublicKey(pubKeyBytes); err != nil {
fmt.Printf("Warning: Failed to cache public key: %v\n", err)
}
return pubKeyBytes, nil
}
```
---
## Command Verification
**Method:** Verify v3 signature with timestamp, falls back to older formats for backward compatibility
**File:** `agent/internal/crypto/verification.go`
```go
func (v *CommandVerifier) VerifyCommandWithTimestamp(
cmd client.Command,
serverPubKey ed25519.PublicKey,
maxAge time.Duration,
clockSkew time.Duration,
) error {
// 1. If cmd.SignedAt is nil, fall back to oldest format (backward compat)
if cmd.SignedAt == nil {
fmt.Printf("[WARNING] [agent] [crypto] command_uses_oldest_format command_id=%s no_signed_at=true upgrade_server_recommended\n", cmd.ID)
return v.VerifyCommand(cmd, serverPubKey)
}
// 2. Validate timestamp window
now := time.Now().UTC()
age := now.Sub(*cmd.SignedAt)
if age > maxAge {
return fmt.Errorf("command timestamp too old: signed %v ago (max %v)", age.Round(time.Second), maxAge)
}
if age < -clockSkew {
return fmt.Errorf("command timestamp is in the future: %v ahead (max skew %v)", (-age).Round(time.Second), clockSkew)
}
// 3. Try v3 format first (with agent_id) if AgentID is present
if cmd.AgentID != "" {
message, err := v.reconstructMessageV3(cmd)
if err != nil {
return fmt.Errorf("failed to reconstruct v3 message: %w", err)
}
if ed25519.Verify(serverPubKey, message, sig) {
return nil // v3 verification succeeded
}
// v3 failed — try v2 as fallback
}
// 4. v2 format: timestamp but no agent_id (backward compat)
message, err := v.reconstructMessageWithTimestamp(cmd)
if err != nil {
return fmt.Errorf("failed to reconstruct timestamped message: %w", err)
}
if !ed25519.Verify(serverPubKey, message, sig) {
return errors.New("signature verification failed")
}
return nil
}
```
**Verification Modes:**
- **Strict:** Reject all verification failures (default)
- **Warning:** Log failure but execute command
- **Disabled:** Skip verification entirely
**Fallback Chain:** v3 (agent_id + timestamp) → v2 (timestamp only) → oldest (no timestamp)
```
**Verification Modes:**
- **Strict:** Reject all verification failures (default)
- **Warning:** Log failure but execute command
- **Disabled:** Skip verification entirely
---
## Binary Verification
**Method:** Verify checksum and Ed25519 signature before installation
**File:** `agent/internal/orchestrator/update_handler.go`
```go
func (h *UpdateHandler) verifyDownload(binaryData []byte, checksum string) error {
// 1. Verify checksum
computedChecksum := sha256.Sum256(binaryData)
if hex.EncodeToString(computedChecksum[:]) != checksum {
return errors.New("checksum mismatch")
}
// 2. Verify Ed25519 signature
signature, err := h.downloadBinarySignature()
if err != nil {
return err
}
publicKey, err := h.LoadCachedPublicKey()
if err != nil {
return err
}
if !ed25519.Verify(publicKey, binaryData, []byte(signature)) {
return errors.New("signature verification failed")
}
return nil
}
```
---
## Cross-References
- **Server signing** → [verification/01-signing-pipeline](01-signing-pipeline.md)
- **Key rotation** → [verification/03-key-rotation](03-key-rotation.md)
- **Replay protection** → [verification/04-replay-protection](04-replay-protection.md)
- **Install script** → [flows/01-registration](../flows/01-registration.md) (TOFU key caching)
---
*Last reviewed: 2026-06-14*