source: advance the checked projection
Source-Sha: 194a59adfd6c901635da7a9c18253bb2210e2426 Policy-Sha: b9b0be0ee74e55f561a803b8aef6be3c2134f5a83b46816a069a372f2eb04f4d Tree-Digest: 4697f2e281c1c6ecafff17cf6eeb391a0a1ac0fa6e5fc429f5b473f4579c252c
This commit is contained in:
parent
0401c76c79
commit
ffdccba10f
42 changed files with 2516 additions and 554 deletions
|
|
@ -2,13 +2,16 @@ package crypto
|
|||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
|
|
@ -156,6 +159,60 @@ func saveCacheMetadata(meta *CacheMetadata) error {
|
|||
return os.WriteFile(getPrimaryMetaPath(), data, 0644)
|
||||
}
|
||||
|
||||
// CacheFleetServerPublicKey installs the exact key returned by the fleet-join
|
||||
// transaction. This bypasses TOFU cache reuse: a standalone host may have an
|
||||
// unrelated stale cache, and that must not outrank the authority just adopted
|
||||
// by the privileged helper.
|
||||
func CacheFleetServerPublicKey(publicKeyHex, keyID string) error {
|
||||
raw, err := hex.DecodeString(publicKeyHex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode joined Server public key: %w", err)
|
||||
}
|
||||
if len(raw) != ed25519.PublicKeySize {
|
||||
return fmt.Errorf("joined Server public key is %d bytes, want %d", len(raw), ed25519.PublicKeySize)
|
||||
}
|
||||
digest := sha256.Sum256(raw)
|
||||
computedID := hex.EncodeToString(digest[:16])
|
||||
if keyID == "" || keyID != computedID {
|
||||
return fmt.Errorf("joined Server key id mismatch: got %q want %q", keyID, computedID)
|
||||
}
|
||||
if err := removeCachedKeysExcept(keyID); err != nil {
|
||||
return fmt.Errorf("retire pre-join Server key cache: %w", err)
|
||||
}
|
||||
key := ed25519.PublicKey(raw)
|
||||
if err := cachePublicKey(key); err != nil {
|
||||
return fmt.Errorf("cache joined Server primary key: %w", err)
|
||||
}
|
||||
if err := CachePublicKeyByID(keyID, key); err != nil {
|
||||
return fmt.Errorf("cache joined Server key id: %w", err)
|
||||
}
|
||||
return saveCacheMetadata(&CacheMetadata{
|
||||
KeyID: keyID, Version: 1, CachedAt: time.Now().UTC(), TTLHours: defaultCacheTTLHours,
|
||||
})
|
||||
}
|
||||
|
||||
func removeCachedKeysExcept(keyID string) error {
|
||||
dir := getPublicKeyDir()
|
||||
entries, err := os.ReadDir(dir)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keep := filepath.Base(getKeyPathByID(keyID))
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if !strings.HasPrefix(name, "server_public_key_") || name == keep {
|
||||
continue
|
||||
}
|
||||
if err := os.Remove(filepath.Join(dir, name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchAndCacheServerPublicKey fetches the server's Ed25519 primary public key.
|
||||
// Uses a TTL+key_id cache: skips the fetch only if both TTL is valid AND key_id matches.
|
||||
// Implements Trust-On-First-Use (TOFU) with rotation awareness.
|
||||
|
|
|
|||
Loading…
Reference in a new issue