11package identity
22
33import (
4+ "crypto/ecdsa"
45 "crypto/ed25519"
6+ "crypto/elliptic"
57 "encoding/hex"
68 "encoding/json"
79 "errors"
810 "fmt"
911 "io"
12+ "math/big"
1013 "os"
1114 "strings"
1215 "sync"
@@ -53,6 +56,19 @@ type Store interface {
5356 DecryptMessage (cipher []byte , peerID string ) ([]byte , error )
5457}
5558
59+ // AuthorizerInfo represents a single authorizer with their public key and algorithm
60+ type AuthorizerInfo struct {
61+ PublicKey string `json:"public_key"`
62+ Algorithm string `json:"algorithm"` // "ed25519" or "secp256k1"
63+ }
64+
65+ // AuthorizationConfig holds the cached authorization configuration
66+ type AuthorizationConfig struct {
67+ Enabled bool
68+ RequiredAuthorizers int
69+ AuthorizerPublicKeys map [string ]AuthorizerInfo // key is authorizer ID
70+ }
71+
5672// fileStore implements the Store interface using the filesystem
5773type fileStore struct {
5874 identityDir string
@@ -66,8 +82,11 @@ type fileStore struct {
6682 initiatorPubKey []byte
6783 symmetricKeys map [string ][]byte
6884
69- // Cached authorizer public keys by authorizer ID
70- authorizerPubKeys map [string ][]byte
85+ // Cached authorizer information by authorizer ID
86+ authorizerInfo map [string ]AuthorizerInfo
87+
88+ // Cached authorization configuration
89+ authzConfig AuthorizationConfig
7190}
7291
7392// NewFileStore creates a new identity store
@@ -109,12 +128,13 @@ func NewFileStore(identityDir, nodeName string, decrypt bool) (*fileStore, error
109128 }
110129
111130 store := & fileStore {
112- identityDir : identityDir ,
113- currentNodeName : nodeName ,
114- publicKeys : make (map [string ][]byte ),
115- privateKey : privateKey ,
116- initiatorPubKey : initiatorPubKey ,
117- authorizerPubKeys : make (map [string ][]byte ),
131+ identityDir : identityDir ,
132+ currentNodeName : nodeName ,
133+ publicKeys : make (map [string ][]byte ),
134+ privateKey : privateKey ,
135+ initiatorPubKey : initiatorPubKey ,
136+ authorizerInfo : make (map [string ]AuthorizerInfo ),
137+ symmetricKeys : make (map [string ][]byte ),
118138 }
119139
120140 // Check that each node in peers.json has an identity file
@@ -149,23 +169,66 @@ func NewFileStore(identityDir, nodeName string, decrypt bool) (*fileStore, error
149169 store .publicKeys [identity .NodeID ] = key
150170 }
151171
152- // Load authorizer public keys from configuration if present
172+ // Load authorization configuration
173+ store .authzConfig = AuthorizationConfig {
174+ Enabled : viper .GetBool ("authorization.enabled" ),
175+ RequiredAuthorizers : viper .GetInt ("authorization.required_authorizers" ),
176+ AuthorizerPublicKeys : make (map [string ]AuthorizerInfo ),
177+ }
178+
179+ // Load authorizer public keys
180+ authKeys := viper .GetStringMap ("authorization.authorizer_public_keys" )
181+ for authID , authData := range authKeys {
182+ if authInfo , ok := authData .(map [string ]interface {}); ok {
183+ info := AuthorizerInfo {
184+ Algorithm : "ed25519" , // default algorithm
185+ }
186+
187+ if pubKey , ok := authInfo ["public_key" ].(string ); ok && pubKey != "" {
188+ info .PublicKey = pubKey
189+ }
190+
191+ if algo , ok := authInfo ["algorithm" ].(string ); ok && algo != "" {
192+ info .Algorithm = algo
193+ }
194+
195+ if info .PublicKey != "" {
196+ store .authzConfig .AuthorizerPublicKeys [authID ] = info
197+ store .authorizerInfo [authID ] = info
198+ }
199+ }
200+ }
201+
202+ // Load global authorizer configuration (backward compatibility)
153203 authzAuthorizers := viper .GetStringMap ("authorization.authorizers" )
154204 for id , v := range authzAuthorizers {
155- // v is expected to be a map with key "pubkey"
205+ // Skip if already loaded from operation-specific config
206+ if _ , exists := store .authorizerInfo [id ]; exists {
207+ continue
208+ }
209+
210+ // v is expected to be a map with key "pubkey" and optional "algorithm"
156211 if entry , ok := v .(map [string ]interface {}); ok {
212+ info := AuthorizerInfo {
213+ Algorithm : "ed25519" , // default algorithm
214+ }
215+
157216 if pubHexRaw , ok := entry ["pubkey" ]; ok {
158- pubHex , ok := pubHexRaw .(string )
159- if ! ok || pubHex == "" {
160- logger .Warn ("Invalid or empty pubkey for authorizer" , "authorizerID" , id )
161- continue
217+ if pubHex , ok := pubHexRaw .(string ); ok && pubHex != "" {
218+ info .PublicKey = pubHex
162219 }
163- key , err := hex .DecodeString (pubHex )
164- if err != nil {
165- logger .Warn ("Invalid hex pubkey for authorizer" , "authorizerID" , id , "error" , err )
166- continue
220+ }
221+
222+ if algoRaw , ok := entry ["algorithm" ]; ok {
223+ if algo , ok := algoRaw .(string ); ok && algo != "" {
224+ info .Algorithm = algo
167225 }
168- store .authorizerPubKeys [id ] = key
226+ }
227+
228+ if info .PublicKey != "" {
229+ store .authorizerInfo [id ] = info
230+ } else {
231+ logger .Warn ("Invalid or empty pubkey for authorizer" , "authorizerID" , id )
169232 }
170233 }
171234 }
@@ -424,57 +487,25 @@ func (s *fileStore) VerifyInitiatorMessage(msg types.InitiatorMessage) error {
424487// AuthorizeInitiatorMessage verifies that a message has sufficient valid authorizer signatures
425488// according to the configured authorization policy. If authorization is disabled or the
426489// required threshold resolves to zero, this is a no-op.
490+ // The operation parameter is kept for logging and debugging purposes.
427491func (s * fileStore ) AuthorizeInitiatorMessage (operation string , msg types.InitiatorMessage ) error {
428492 // If authorization is not enabled, allow
429- if ! viper . GetBool ( "authorization.enabled" ) {
493+ if ! s . authzConfig . Enabled {
430494 return nil
431495 }
432496
433- // Determine required threshold: operation-specific overrides default
434- defaultThreshold := viper .GetInt ("authorization.default_threshold" )
435- opPolicy := viper .GetStringMap ("authorization.operation_policies." + operation )
436- required := 0
437- if val , ok := opPolicy ["required_authorizers" ]; ok {
438- switch t := val .(type ) {
439- case int :
440- required = t
441- case int64 :
442- required = int (t )
443- case float64 :
444- required = int (t )
445- }
446- }
447- if required <= 0 {
448- required = defaultThreshold
449- }
497+ // Get required threshold
498+ required := s .authzConfig .RequiredAuthorizers
450499 if required <= 0 {
451500 // No requirement; authorization effectively disabled
452501 return nil
453502 }
454503
455- // Build allowed authorizer ID set
456- allowedIDs := map [string ]struct {}{}
457- if idsVal , ok := opPolicy ["authorizer_ids" ]; ok {
458- switch ids := idsVal .(type ) {
459- case []interface {}:
460- for _ , idv := range ids {
461- if sId , ok := idv .(string ); ok && sId != "" {
462- allowedIDs [sId ] = struct {}{}
463- }
464- }
465- case []string :
466- for _ , sId := range ids {
467- if sId != "" {
468- allowedIDs [sId ] = struct {}{}
469- }
470- }
471- }
472- }
473- // If no explicit allowed IDs configured, allow any configured authorizer
474- if len (allowedIDs ) == 0 {
475- for id := range s .authorizerPubKeys {
476- allowedIDs [id ] = struct {}{}
477- }
504+ // Use configured authorizers
505+ allowedAuthorizers := s .authzConfig .AuthorizerPublicKeys
506+ if len (allowedAuthorizers ) == 0 {
507+ // Fallback to global authorizer info for backward compatibility
508+ allowedAuthorizers = s .authorizerInfo
478509 }
479510
480511 // Prepare payload
@@ -493,14 +524,20 @@ func (s *fileStore) AuthorizeInitiatorMessage(operation string, msg types.Initia
493524 if _ , dup := seen [sig .AuthorizerID ]; dup {
494525 continue
495526 }
496- if _ , ok := allowedIDs [sig .AuthorizerID ]; ! ok {
527+
528+ authInfo , ok := allowedAuthorizers [sig .AuthorizerID ]
529+ if ! ok || authInfo .PublicKey == "" {
497530 continue
498531 }
499- pub , ok := s .authorizerPubKeys [sig .AuthorizerID ]
500- if ! ok || len (pub ) == 0 {
532+
533+ // Verify signature using the appropriate algorithm
534+ valid , err := verifySignatureByAlgorithm (authInfo .PublicKey , authInfo .Algorithm , msgBytes , sig .Signature )
535+ if err != nil {
536+ logger .Warn ("Failed to verify authorizer signature" , "authorizerID" , sig .AuthorizerID , "algorithm" , authInfo .Algorithm , "error" , err )
501537 continue
502538 }
503- if ed25519 .Verify (pub , msgBytes , sig .Signature ) {
539+
540+ if valid {
504541 seen [sig .AuthorizerID ] = struct {}{}
505542 validCount ++
506543 }
@@ -513,6 +550,59 @@ func (s *fileStore) AuthorizeInitiatorMessage(operation string, msg types.Initia
513550 return nil
514551}
515552
553+ // verifySignatureByAlgorithm verifies a signature using the specified algorithm
554+ func verifySignatureByAlgorithm (publicKeyHex , algorithm string , message , signature []byte ) (bool , error ) {
555+ switch algorithm {
556+ case "ed25519" :
557+ pubKeyBytes , err := hex .DecodeString (publicKeyHex )
558+ if err != nil {
559+ return false , fmt .Errorf ("invalid ed25519 public key hex: %w" , err )
560+ }
561+ if len (pubKeyBytes ) != ed25519 .PublicKeySize {
562+ return false , fmt .Errorf ("invalid ed25519 public key length: expected %d, got %d" , ed25519 .PublicKeySize , len (pubKeyBytes ))
563+ }
564+ return ed25519 .Verify (pubKeyBytes , message , signature ), nil
565+
566+ case "secp256k1" , "p256" :
567+ pubKeyBytes , err := hex .DecodeString (publicKeyHex )
568+ if err != nil {
569+ return false , fmt .Errorf ("invalid ecdsa public key hex: %w" , err )
570+ }
571+
572+ // Parse the public key
573+ var curve elliptic.Curve
574+ if algorithm == "secp256k1" {
575+ // For secp256k1, we'd need to import a secp256k1 library
576+ // For now, we'll use P256 as a placeholder
577+ curve = elliptic .P256 ()
578+ } else {
579+ curve = elliptic .P256 ()
580+ }
581+
582+ // Assume uncompressed point format (0x04 + 32 bytes x + 32 bytes y)
583+ if len (pubKeyBytes ) == 65 && pubKeyBytes [0 ] == 0x04 {
584+ x := new (big.Int ).SetBytes (pubKeyBytes [1 :33 ])
585+ y := new (big.Int ).SetBytes (pubKeyBytes [33 :65 ])
586+ _ = & ecdsa.PublicKey {Curve : curve , X : x , Y : y } // pubKey would be used for actual verification
587+
588+ // Parse DER-encoded signature
589+ // This is a simplified implementation - in production you'd want proper ASN.1 parsing
590+ if len (signature ) < 6 {
591+ return false , fmt .Errorf ("signature too short" )
592+ }
593+
594+ // For now, return false - proper ECDSA signature verification would need more robust parsing
595+ logger .Warn ("ECDSA signature verification not fully implemented" , "algorithm" , algorithm )
596+ return false , fmt .Errorf ("ECDSA signature verification not fully implemented for %s" , algorithm )
597+ } else {
598+ return false , fmt .Errorf ("unsupported public key format for %s" , algorithm )
599+ }
600+
601+ default :
602+ return false , fmt .Errorf ("unsupported signature algorithm: %s" , algorithm )
603+ }
604+ }
605+
516606func partyIDToNodeID (partyID * tss.PartyID ) string {
517607 return strings .Split (string (partyID .KeyInt ().Bytes ()), ":" )[0 ]
518608}
0 commit comments