@@ -4,14 +4,16 @@ import (
44 "crypto/ed25519"
55 "errors"
66 "fmt"
7+ "strings"
78
89 "ix-agent-notary/internal/canon"
910 "ix-agent-notary/internal/crypto"
1011)
1112
1213type SignatureValidationOptions struct {
1314 Strict bool
14- PublicKeyPath string // optional override for public key lookup (base64url file)
15+ PublicKeyPath string // optional exact public key file (base64url)
16+ PublicKeyDir string // optional directory containing <key_id>.pub
1517}
1618
1719type SignatureCheck struct {
@@ -35,6 +37,10 @@ func ValidateSignature(r Receipt, opts SignatureValidationOptions) (*SignatureCh
3537 keyID , _ := sigObj ["key_id" ].(string )
3638 val , _ := sigObj ["value" ].(string )
3739
40+ alg = strings .ToLower (strings .TrimSpace (alg ))
41+ keyID = strings .TrimSpace (keyID )
42+ val = strings .TrimSpace (val )
43+
3844 if isPlaceholder (val ) {
3945 if opts .Strict {
4046 return nil , fmt .Errorf ("signature is missing/placeholder" )
@@ -49,25 +55,16 @@ func ValidateSignature(r Receipt, opts SignatureValidationOptions) (*SignatureCh
4955 return nil , fmt .Errorf ("missing integrity.signature.key_id" )
5056 }
5157
52- var pub ed25519.PublicKey
53- if opts .PublicKeyPath != "" {
54- p , err := crypto .LoadEd25519PublicKeyFile (opts .PublicKeyPath )
55- if err != nil {
56- if opts .Strict {
57- return nil , err
58- }
59- return & SignatureCheck {Skipped : true , Alg : alg , KeyID : keyID }, nil
60- }
61- pub = p
62- } else {
63- p , _ , err := crypto .ResolvePublicKeyByID (keyID )
64- if err != nil {
65- if opts .Strict {
66- return nil , err
67- }
68- return & SignatureCheck {Skipped : true , Alg : alg , KeyID : keyID }, nil
58+ pub , _ , err := crypto .ResolveEd25519PublicKey (crypto.ResolvePublicKeyOptions {
59+ KeyID : keyID ,
60+ PublicKeyPath : opts .PublicKeyPath ,
61+ SearchDirs : receiptPublicKeySearchDirs (opts .PublicKeyDir ),
62+ })
63+ if err != nil {
64+ if opts .Strict {
65+ return nil , err
6966 }
70- pub = p
67+ return & SignatureCheck { Skipped : true , Alg : alg , KeyID : keyID }, nil
7168 }
7269
7370 sigBytes , err := crypto .DecodeBase64URLNoPad (val )
@@ -91,13 +88,11 @@ func ValidateSignature(r Receipt, opts SignatureValidationOptions) (*SignatureCh
9188}
9289
9390func canonicalBytesForSignature (r Receipt ) ([]byte , error ) {
94- // Deep-clone via JSON round-trip to avoid mutating the original receipt.
9591 cloned , err := cloneReceipt (r )
9692 if err != nil {
9793 return nil , err
9894 }
9995
100- // Remove signature.value before canonicalization (normative rule in spec).
10196 if integrity , ok := cloned ["integrity" ].(map [string ]any ); ok {
10297 if sigObj , ok := integrity ["signature" ].(map [string ]any ); ok {
10398 delete (sigObj , "value" )
@@ -112,11 +107,17 @@ func canonicalBytesForSignature(r Receipt) ([]byte, error) {
112107}
113108
114109func cloneReceipt (r Receipt ) (map [string ]any , error ) {
115- // Use the fact that Receipt is map[string]any; marshal/unmarshal gives a safe deep clone.
116- // This is acceptable for verification tooling (not a hot path).
117110 b , err := marshalJSON (r )
118111 if err != nil {
119112 return nil , err
120113 }
121114 return unmarshalJSONObject (b )
122115}
116+
117+ func receiptPublicKeySearchDirs (dir string ) []string {
118+ dir = strings .TrimSpace (dir )
119+ if dir == "" {
120+ return nil
121+ }
122+ return []string {dir }
123+ }
0 commit comments