@@ -5,19 +5,21 @@ import (
55 "encoding/base64"
66 "errors"
77 "fmt"
8- "os"
9- "path/filepath"
10- "runtime"
118 "strings"
9+
10+ "ix-agent-notary/internal/crypto"
1211)
1312
1413type ApprovalSigValidationOptions struct {
15- // Strict fails if an approval exists but is missing a signature, or has an invalid signature.
1614 Strict bool
1715
18- // PublicKeyPath overrides key resolution and uses this pubkey for verification.
16+ // PublicKeyPath is the highest-precedence verification source .
1917 // Expected format: base64url (no padding) encoded 32-byte ed25519 public key.
2018 PublicKeyPath string
19+
20+ // PublicKeyDir is an optional directory containing <key_id>.pub files.
21+ // If unset, default lookup falls back to keys/ then keys/dev/ relative to cwd.
22+ PublicKeyDir string
2123}
2224
2325type ApprovalSigCheck struct {
@@ -29,7 +31,6 @@ type ApprovalSigCheck struct {
2931func ValidateApprovalSignatures (r Receipt , opts ApprovalSigValidationOptions ) (* ApprovalSigCheck , error ) {
3032 pol , ok := r ["policy" ].(map [string ]any )
3133 if ! ok {
32- // schema should prevent this; keep defensive
3334 if opts .Strict {
3435 return nil , errors .New ("approval sig: missing policy object" )
3536 }
@@ -52,23 +53,12 @@ func ValidateApprovalSignatures(r Receipt, opts ApprovalSigValidationOptions) (*
5253 return & ApprovalSigCheck {Skipped : true }, nil
5354 }
5455
55- // No approvals is fine even in strict mode.
5656 if len (apprs ) == 0 {
5757 return & ApprovalSigCheck {Skipped : false , Total : 0 , Verified : 0 }, nil
5858 }
5959
6060 check := & ApprovalSigCheck {Skipped : false , Total : len (apprs ), Verified : 0 }
6161
62- // Optional override: one pubkey for all approvals (demo-friendly).
63- var overridePub ed25519.PublicKey
64- if strings .TrimSpace (opts .PublicKeyPath ) != "" {
65- pub , err := loadEd25519PublicKeyBase64URLFile (opts .PublicKeyPath )
66- if err != nil {
67- return nil , err
68- }
69- overridePub = pub
70- }
71-
7262 for i , a := range apprs {
7363 obj , ok := a .(map [string ]any )
7464 if ! ok {
@@ -125,14 +115,13 @@ func ValidateApprovalSignatures(r Receipt, opts ApprovalSigValidationOptions) (*
125115 return nil , fmt .Errorf ("approval sig: approvals[%d] signature size invalid (got %d)" , i , len (sigBytes ))
126116 }
127117
128- var pub ed25519.PublicKey
129- if overridePub != nil {
130- pub = overridePub
131- } else {
132- pub , err = resolveEd25519PublicKeyByKeyID (keyID )
133- if err != nil {
134- return nil , fmt .Errorf ("approval sig: approvals[%d] resolve pubkey: %w" , i , err )
135- }
118+ pub , _ , err := crypto .ResolveEd25519PublicKey (crypto.ResolvePublicKeyOptions {
119+ KeyID : keyID ,
120+ PublicKeyPath : opts .PublicKeyPath ,
121+ SearchDirs : receiptPublicKeySearchDirs (opts .PublicKeyDir ),
122+ })
123+ if err != nil {
124+ return nil , fmt .Errorf ("approval sig: approvals[%d] resolve pubkey: %w" , i , err )
136125 }
137126
138127 if ! ed25519 .Verify (pub , payload , sigBytes ) {
@@ -142,44 +131,9 @@ func ValidateApprovalSignatures(r Receipt, opts ApprovalSigValidationOptions) (*
142131 check .Verified ++
143132 }
144133
145- // If strict, require that every approval is signed+valid.
146134 if opts .Strict && check .Verified != check .Total {
147135 return nil , fmt .Errorf ("approval sig: strict mode requires all approvals be signed (%d/%d verified)" , check .Verified , check .Total )
148136 }
149137
150138 return check , nil
151139}
152-
153- func resolveEd25519PublicKeyByKeyID (keyID string ) (ed25519.PublicKey , error ) {
154- root , err := repoRootForReceiptPackage ()
155- if err != nil {
156- return nil , err
157- }
158- p := filepath .Join (root , "keys" , "dev" , keyID + ".pub" )
159- return loadEd25519PublicKeyBase64URLFile (p )
160- }
161-
162- func loadEd25519PublicKeyBase64URLFile (path string ) (ed25519.PublicKey , error ) {
163- b , err := os .ReadFile (path )
164- if err != nil {
165- return nil , fmt .Errorf ("approval sig: read pubkey: %w" , err )
166- }
167-
168- raw , err := base64 .RawURLEncoding .DecodeString (strings .TrimSpace (string (b )))
169- if err != nil {
170- return nil , fmt .Errorf ("approval sig: decode pubkey (base64url): %w" , err )
171- }
172- if len (raw ) != ed25519 .PublicKeySize {
173- return nil , fmt .Errorf ("approval sig: pubkey must be %d bytes (got %d)" , ed25519 .PublicKeySize , len (raw ))
174- }
175- return ed25519 .PublicKey (raw ), nil
176- }
177-
178- func repoRootForReceiptPackage () (string , error ) {
179- // This file is: <root>/internal/receipt/approval_sig.go
180- _ , file , _ , ok := runtime .Caller (0 )
181- if ! ok {
182- return "" , errors .New ("approval sig: runtime.Caller failed" )
183- }
184- return filepath .Clean (filepath .Join (filepath .Dir (file ), ".." , ".." )), nil
185- }
0 commit comments