RedFlag/RAF/verification/01-signing-pipeline.md
Fimeg 67e26be2d9 projection: begin the exact-path public epoch
The public tree and its history contain only the listed paths. Earlier projection history remains preserved internally.

Source-Sha: 913fde029b935671833254797f0f20f1eb9fabba

Policy-Sha: 913fde029b935671833254797f0f20f1eb9fabba

Tree-Digest: 180ae530c1058a2a5c89837bdce2d323ae83e669e38590ca72e75b8d92b7262f
2026-09-08 21:59:33 -04:00

177 lines
No EOL
5.5 KiB
Markdown

# Ed25519 Signing Pipeline
**Server-side command and binary signing with Ed25519.**
---
## Overview
All commands and binaries are signed with Ed25519 private key on the server. Agents verify signatures before execution.
**Cross-references:**
- [verification/02-agent-verification](02-agent-verification.md) (agent-side verification)
- [verification/03-key-rotation](03-key-rotation.md) (key rotation support)
- [security/01-trust-boundaries](../security/01-trust-boundaries.md) (signature as trust boundary)
---
## Key Generation
**Method:** Server startup automatic key registration
**File:** `server/internal/services/signing.go`
```go
func (s *SigningService) InitializePrimaryKey(ctx context.Context) error {
// 1. Get current key fingerprint (SHA-256 of public key, truncated)
keyID := s.GetCurrentKeyID()
publicKeyHex := s.GetPublicKeyHex()
// 2. Query next version number from database
nextVersion, err := s.signingKeyQueries.GetNextVersion(ctx)
if err != nil {
nextVersion = 1
}
// 3. Insert key (ON CONFLICT DO NOTHING — safe on every startup)
if err := s.signingKeyQueries.InsertSigningKey(ctx, keyID, publicKeyHex, nextVersion); err != nil {
return fmt.Errorf("failed to insert signing key: %w", err)
}
// 4. Set as primary
if err := s.signingKeyQueries.SetPrimaryKey(ctx, keyID); err != nil {
return fmt.Errorf("failed to set primary key: %w", err)
}
return nil
}
```
---
## Command Signing
**Method:** v3 message format with agent_id binding
**File:** `server/internal/services/signing.go`
```go
func (s *SigningService) SignCommand(cmd *models.AgentCommand) (string, error) {
// 1. Record signing time and key identity
now := time.Now().UTC()
cmd.SignedAt = &now
cmd.KeyID = s.GetCurrentKeyID()
// 2. Serialize params and hash
paramsJSON, _ := json.Marshal(cmd.Params)
paramsHash := sha256.Sum256(paramsJSON)
paramsHashHex := hex.EncodeToString(paramsHash[:])
// 3. Create v3 message format
// agent_id binding prevents cross-agent replay (F-1 fix)
message := fmt.Sprintf("%s:%s:%s:%s:%d",
cmd.AgentID.String(),
cmd.ID.String(),
cmd.CommandType,
paramsHashHex,
now.Unix())
// 4. Sign with Ed25519
signature := ed25519.Sign(s.privateKey, []byte(message))
return hex.EncodeToString(signature), nil
}
```
**v3 Message Format Benefits:**
- **Agent binding:** Includes agent_id to prevent command relay attacks
- **Timestamp:** Prevents replay attacks (4-hour max age)
- **Parameter hash:** Hides full parameter data while allowing verification
---
## Binary Signing
**Method:** BuildOrchestrator signs binaries at startup
**File:** `server/internal/services/build_orchestrator.go`
```go
func (s *BuildOrchestratorService) BuildAndSignAgent(version, platform, architecture string) (*models.AgentUpdatePackage, error) {
// 1. Load binary from disk
binaryName := "redflag-agent"
if strings.HasPrefix(platform, "windows") {
binaryName += ".exe"
}
binaryPath := filepath.Join(s.agentDir, "binaries", platform+"-"+architecture, binaryName)
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
return nil, fmt.Errorf("binary not found for platform %s: %w", platform, err)
}
if s.signingService.IsEnabled() {
// 2. Compute checksum and sign
signedPackage, err := s.signingService.SignFile(binaryPath)
if err != nil {
return nil, fmt.Errorf("failed to sign agent binary: %w", err)
}
// 3. Set metadata
signedPackage.Version = version
signedPackage.Platform = platform
signedPackage.Architecture = architecture
// 4. Store in database
err = s.packageQueries.StoreSignedPackage(signedPackage)
if err != nil {
return nil, fmt.Errorf("failed to store signed package: %w", err)
}
log.Printf("[INFO] [server] [build_orchestrator] package_signed id=%s version=%s platform=%s arch=%s", signedPackage.ID, version, platform, architecture)
return signedPackage, nil
} else {
log.Printf("Signing disabled, creating unsigned package entry")
// Create unsigned package entry for backward compatibility
unsignedPackage := &models.AgentUpdatePackage{
ID: uuid.New(),
Version: version,
Platform: platform,
Architecture: architecture,
BinaryPath: binaryPath,
Signature: "",
Checksum: "",
CreatedBy: "build-orchestrator",
IsActive: true,
}
// Get file info
if info, err := os.Stat(binaryPath); err == nil {
unsignedPackage.FileSize = info.Size()
}
// Store unsigned package
err := s.packageQueries.StoreSignedPackage(unsignedPackage)
if err != nil {
return nil, fmt.Errorf("failed to store unsigned package: %w", err)
}
return unsignedPackage, nil
}
}
```
---
## Footer: Assumptions & Connections
**Assumption:** Ed25519 signing is enabled when `REDFLAG_SIGNING_PRIVATE_KEY` environment variable is set.
**Connection:** [verification/02-agent-verification](02-agent-verification.md) (agent-side signature verification)
**Connection:** [verification/03-key-rotation](03-key-rotation.md) (multi-key rotation support)
**Connection:** [security/01-trust-boundaries](../security/01-trust-boundaries.md) (cryptographic trust boundary)
---
*Last reviewed: 2026-06-14*