Watch
1
0
Fork
You've already forked RedFlag
0
RedFlag/RAF/verification/01-signing-pipeline.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

5.5 KiB

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:


Key Generation

Method: Server startup automatic key registration

File: server/internal/services/signing.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

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

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
    }
}

Assumption: Ed25519 signing is enabled when REDFLAG_SIGNING_PRIVATE_KEY environment variable is set.

Connection: verification/02-agent-verification (agent-side signature verification)

Connection: verification/03-key-rotation (multi-key rotation support)

Connection: security/01-trust-boundaries (cryptographic trust boundary)


Last reviewed: 2026-06-14