Skip to content

Commit 0f4bb1a

Browse files
committed
feat: auth conf with pubkey and algs
1 parent d8c0fff commit 0f4bb1a

File tree

5 files changed

+689
-77
lines changed

5 files changed

+689
-77
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
db/
22
tmp/
33
bin/
4-
identity/
54
event_initiator.identity.json
65
event_initiator.key
76
event_initiator.key.age

config.yaml.template

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@ session_warm_up_delay_ms: 100
1818
# Authorization (optional)
1919
authorization:
2020
enabled: false
21-
default_threshold: 0
22-
operation_policies:
23-
keygen:
24-
required_authorizers: 0
25-
authorizer_ids: []
26-
signing:
27-
required_authorizers: 0
28-
authorizer_ids: []
29-
reshare:
30-
required_authorizers: 0
31-
authorizer_ids: []
21+
required_authorizers: 0
22+
# Authorizer public keys configuration (applies to all operations: keygen, signing, reshare)
23+
authorizer_public_keys: {}
24+
# Example:
25+
# auth1:
26+
# public_key: "deadbeef..."
27+
# algorithm: "ed25519"
28+
# auth2:
29+
# public_key: "cafebabe..."
30+
# algorithm: "p256"
31+
# Global authorizers (backward compatibility)
3232
authorizers: {}
33+
# Example:
34+
# auth1:
35+
# pubkey: "deadbeef..."
36+
# algorithm: "ed25519"

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ require (
1919
github.com/spf13/viper v1.18.0
2020
github.com/stretchr/testify v1.10.0
2121
github.com/urfave/cli/v3 v3.3.2
22+
golang.org/x/crypto v0.37.0
2223
golang.org/x/term v0.31.0
2324
)
2425

@@ -80,7 +81,6 @@ require (
8081
go.uber.org/goleak v1.3.0 // indirect
8182
go.uber.org/multierr v1.9.0 // indirect
8283
go.uber.org/zap v1.21.0 // indirect
83-
golang.org/x/crypto v0.37.0 // indirect
8484
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
8585
golang.org/x/net v0.39.0 // indirect
8686
golang.org/x/sys v0.33.0 // indirect

pkg/identity/identity.go

Lines changed: 154 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package identity
22

33
import (
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
5773
type 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.
427491
func (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+
516606
func partyIDToNodeID(partyID *tss.PartyID) string {
517607
return strings.Split(string(partyID.KeyInt().Bytes()), ":")[0]
518608
}

0 commit comments

Comments
 (0)