Watch
1
0
Fork
You've already forked RedFlag
0
RedFlag/server/internal/services/update_nonce.go
Fimeg 37d9731f21 fix(CRITICAL-003): convert all timestamps to UTC/TIMESTAMPTZ (migration 036)
Migration 036 converts every TIMESTAMP column to TIMESTAMPTZ across 20+ tables,
interpreting existing values as UTC. Server handlers, queries, services, and
agent files converted from time.Now() to time.Now().UTC() consistently.
Monotonic-paired time.Now() sites preserved where needed.
2026-05-24 20:08:10 -04:00

90 lines
2.3 KiB
Go

package services
import (
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"fmt"
"time"
)
type UpdateNonce struct {
AgentID string `json:"agent_id"`
TargetVersion string `json:"target_version"`
Timestamp int64 `json:"timestamp"`
Signature string `json:"signature"`
}
type UpdateNonceService struct {
privateKey ed25519.PrivateKey
maxAge time.Duration
}
func NewUpdateNonceService(privateKey ed25519.PrivateKey) *UpdateNonceService {
return &UpdateNonceService{
privateKey: privateKey,
maxAge: 10 * time.Minute,
}
}
// Generate creates a signed nonce authorizing an agent to update
func (s *UpdateNonceService) Generate(agentID, targetVersion string) (string, error) {
nonce := UpdateNonce{
AgentID: agentID,
TargetVersion: targetVersion,
Timestamp: time.Now().UTC().Unix(),
}
data, err := json.Marshal(nonce)
if err != nil {
return "", fmt.Errorf("marshal failed: %w", err)
}
signature := ed25519.Sign(s.privateKey, data)
nonce.Signature = base64.StdEncoding.EncodeToString(signature)
encoded, err := json.Marshal(nonce)
if err != nil {
return "", fmt.Errorf("encode failed: %w", err)
}
return base64.StdEncoding.EncodeToString(encoded), nil
}
// Validate verifies the nonce signature and freshness
func (s *UpdateNonceService) Validate(encodedNonce string) (*UpdateNonce, error) {
data, err := base64.StdEncoding.DecodeString(encodedNonce)
if err != nil {
return nil, fmt.Errorf("invalid base64: %w", err)
}
var nonce UpdateNonce
if err := json.Unmarshal(data, &nonce); err != nil {
return nil, fmt.Errorf("invalid format: %w", err)
}
// Check freshness
if time.Now().UTC().Unix()-nonce.Timestamp > int64(s.maxAge.Seconds()) {
return nil, fmt.Errorf("nonce expired")
}
// Verify signature
signature, err := base64.StdEncoding.DecodeString(nonce.Signature)
if err != nil {
return nil, fmt.Errorf("invalid signature: %w", err)
}
// Remove signature for verification
nonce.Signature = ""
verifyData, err := json.Marshal(nonce)
if err != nil {
return nil, fmt.Errorf("marshal verify data: %w", err)
}
if !ed25519.Verify(s.privateKey.Public().(ed25519.PublicKey), verifyData, signature) {
return nil, fmt.Errorf("signature verification failed")
}
// Return validated nonce
return &nonce, nil
}