nip46: revamp dynamic signer, require a ctx on HandleRequest().

This commit is contained in:
fiatjaf 2024-10-14 16:26:01 -03:00
parent ee5ca07d6d
commit e05dbb5d51
3 changed files with 69 additions and 103 deletions

View File

@ -1,6 +1,7 @@
package nip46 package nip46
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"slices" "slices"
@ -9,7 +10,6 @@ import (
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip04" "github.com/nbd-wtf/go-nostr/nip04"
"github.com/nbd-wtf/go-nostr/nip44"
) )
var _ Signer = (*DynamicSigner)(nil) var _ Signer = (*DynamicSigner)(nil)
@ -20,26 +20,40 @@ type DynamicSigner struct {
sync.Mutex sync.Mutex
RelaysToAdvertise map[string]RelayReadWrite getHandlerSecretKey func(handlerPubkey string) (string, error)
getUserKeyer func(handlerPubkey string) (nostr.Keyer, error)
getPrivateKey func(pubkey string) (string, error)
authorizeSigning func(event nostr.Event, from string, secret string) bool authorizeSigning func(event nostr.Event, from string, secret string) bool
onEventSigned func(event nostr.Event)
authorizeEncryption func(from string, secret string) bool authorizeEncryption func(from string, secret string) bool
onEventSigned func(event nostr.Event)
getRelays func(pubkey string) map[string]RelayReadWrite
} }
func NewDynamicSigner( func NewDynamicSigner(
getPrivateKey func(pubkey string) (string, error), // the handler is the keypair we use to communicate with the NIP-46 client, decrypt requests, encrypt responses etc
getHandlerSecretKey func(handlerPubkey string) (string, error),
// this should correspond to the actual user on behalf of which we will respond to requests
getUserKeyer func(handlerPubkey string) (nostr.Keyer, error),
// this is called on every sign_event call, if it is nil it will be assumed that everything is authorized
authorizeSigning func(event nostr.Event, from string, secret string) bool, authorizeSigning func(event nostr.Event, from string, secret string) bool,
onEventSigned func(event nostr.Event),
// this is called on every encrypt or decrypt calls, if it is nil it will be assumed that everything is authorized
authorizeEncryption func(from string, secret string) bool, authorizeEncryption func(from string, secret string) bool,
// unless it is nil, this is called after every event is signed
onEventSigned func(event nostr.Event),
// unless it is nil, the results of this will be used in reply to get_relays
getRelays func(pubkey string) map[string]RelayReadWrite,
) DynamicSigner { ) DynamicSigner {
return DynamicSigner{ return DynamicSigner{
getPrivateKey: getPrivateKey, getHandlerSecretKey: getHandlerSecretKey,
getUserKeyer: getUserKeyer,
authorizeSigning: authorizeSigning, authorizeSigning: authorizeSigning,
onEventSigned: onEventSigned,
authorizeEncryption: authorizeEncryption, authorizeEncryption: authorizeEncryption,
RelaysToAdvertise: make(map[string]RelayReadWrite), onEventSigned: onEventSigned,
getRelays: getRelays,
} }
} }
@ -69,7 +83,7 @@ func (p *DynamicSigner) setSession(clientPubkey string, session Session) {
p.sessions[idx] = session p.sessions[idx] = session
} }
func (p *DynamicSigner) HandleRequest(event *nostr.Event) ( func (p *DynamicSigner) HandleRequest(ctx context.Context, event *nostr.Event) (
req Request, req Request,
resp Response, resp Response,
eventResponse nostr.Event, eventResponse nostr.Event,
@ -80,16 +94,19 @@ func (p *DynamicSigner) HandleRequest(event *nostr.Event) (
fmt.Errorf("event kind is %d, but we expected %d", event.Kind, nostr.KindNostrConnect) fmt.Errorf("event kind is %d, but we expected %d", event.Kind, nostr.KindNostrConnect)
} }
targetUser := event.Tags.GetFirst([]string{"p", ""}) handler := event.Tags.GetFirst([]string{"p", ""})
if targetUser == nil || !nostr.IsValid32ByteHex((*targetUser)[1]) { if handler == nil || !nostr.IsValid32ByteHex((*handler)[1]) {
return req, resp, eventResponse, fmt.Errorf("invalid \"p\" tag") return req, resp, eventResponse, fmt.Errorf("invalid \"p\" tag")
} }
targetPubkey := (*targetUser)[1] handlerPubkey := (*handler)[1]
handlerSecret, err := p.getHandlerSecretKey(handlerPubkey)
privateKey, err := p.getPrivateKey(targetPubkey)
if err != nil { if err != nil {
return req, resp, eventResponse, fmt.Errorf("no private key for %s: %w", targetPubkey, err) return req, resp, eventResponse, fmt.Errorf("no private key for %s: %w", handlerPubkey, err)
}
userKeyer, err := p.getUserKeyer(handlerPubkey)
if err != nil {
return req, resp, eventResponse, fmt.Errorf("failed to get user keyer for %s: %w", handlerPubkey, err)
} }
var session Session var session Session
@ -99,11 +116,16 @@ func (p *DynamicSigner) HandleRequest(event *nostr.Event) (
} else { } else {
session = Session{} session = Session{}
session.SharedKey, err = nip04.ComputeSharedSecret(event.PubKey, privateKey) session.SharedKey, err = nip04.ComputeSharedSecret(event.PubKey, handlerSecret)
if err != nil { if err != nil {
return req, resp, eventResponse, fmt.Errorf("failed to compute shared secret: %w", err) return req, resp, eventResponse, fmt.Errorf("failed to compute shared secret: %w", err)
} }
session.PublicKey, err = userKeyer.GetPublicKey(ctx)
if err != nil {
return req, resp, eventResponse, fmt.Errorf("failed to get public key: %w", err)
}
p.setSession(event.PubKey, session) p.setSession(event.PubKey, session)
} }
@ -123,7 +145,7 @@ func (p *DynamicSigner) HandleRequest(event *nostr.Event) (
} }
result = "ack" result = "ack"
case "get_public_key": case "get_public_key":
result = targetPubkey result = session.PublicKey
case "sign_event": case "sign_event":
if len(req.Params) != 1 { if len(req.Params) != 1 {
resultErr = fmt.Errorf("wrong number of arguments to 'sign_event'") resultErr = fmt.Errorf("wrong number of arguments to 'sign_event'")
@ -135,11 +157,12 @@ func (p *DynamicSigner) HandleRequest(event *nostr.Event) (
resultErr = fmt.Errorf("failed to decode event/2: %w", err) resultErr = fmt.Errorf("failed to decode event/2: %w", err)
break break
} }
if !p.authorizeSigning(evt, event.PubKey, secret) { if p.authorizeSigning != nil && !p.authorizeSigning(evt, event.PubKey, secret) {
resultErr = fmt.Errorf("refusing to sign this event") resultErr = fmt.Errorf("refusing to sign this event")
break break
} }
err = evt.Sign(privateKey)
err = userKeyer.SignEvent(ctx, &evt)
if err != nil { if err != nil {
resultErr = fmt.Errorf("failed to sign event: %w", err) resultErr = fmt.Errorf("failed to sign event: %w", err)
break break
@ -147,8 +170,12 @@ func (p *DynamicSigner) HandleRequest(event *nostr.Event) (
jrevt, _ := easyjson.Marshal(evt) jrevt, _ := easyjson.Marshal(evt)
result = string(jrevt) result = string(jrevt)
case "get_relays": case "get_relays":
jrelays, _ := json.Marshal(p.RelaysToAdvertise) if p.getRelays == nil {
result = string(jrelays) jrelays, _ := json.Marshal(p.getRelays(session.PublicKey))
result = string(jrelays)
} else {
result = "{}"
}
case "nip44_encrypt": case "nip44_encrypt":
if len(req.Params) != 2 { if len(req.Params) != 2 {
resultErr = fmt.Errorf("wrong number of arguments to 'nip04_encrypt'") resultErr = fmt.Errorf("wrong number of arguments to 'nip04_encrypt'")
@ -159,18 +186,13 @@ func (p *DynamicSigner) HandleRequest(event *nostr.Event) (
resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a pubkey string") resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a pubkey string")
break break
} }
if !p.authorizeEncryption(event.PubKey, secret) { if p.authorizeEncryption != nil && !p.authorizeEncryption(event.PubKey, secret) {
resultErr = fmt.Errorf("refusing to encrypt") resultErr = fmt.Errorf("refusing to encrypt")
break break
} }
plaintext := req.Params[1] plaintext := req.Params[1]
sharedSecret, err := nip44.GenerateConversationKey(thirdPartyPubkey, privateKey) ciphertext, err := userKeyer.Encrypt(ctx, plaintext, thirdPartyPubkey)
if err != nil {
resultErr = fmt.Errorf("failed to compute shared secret: %w", err)
break
}
ciphertext, err := nip44.Encrypt(plaintext, sharedSecret)
if err != nil { if err != nil {
resultErr = fmt.Errorf("failed to encrypt: %w", err) resultErr = fmt.Errorf("failed to encrypt: %w", err)
break break
@ -186,74 +208,15 @@ func (p *DynamicSigner) HandleRequest(event *nostr.Event) (
resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a pubkey string") resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a pubkey string")
break break
} }
if !p.authorizeEncryption(event.PubKey, secret) { if p.authorizeEncryption != nil && !p.authorizeEncryption(event.PubKey, secret) {
resultErr = fmt.Errorf("refusing to decrypt") resultErr = fmt.Errorf("refusing to decrypt")
break break
} }
ciphertext := req.Params[1] ciphertext := req.Params[1]
sharedSecret, err := nip44.GenerateConversationKey(thirdPartyPubkey, privateKey) plaintext, err := userKeyer.Decrypt(ctx, ciphertext, thirdPartyPubkey)
if err != nil { if err != nil {
resultErr = fmt.Errorf("failed to compute shared secret: %w", err) resultErr = fmt.Errorf("failed to decrypt: %w", err)
break
}
plaintext, err := nip44.Decrypt(ciphertext, sharedSecret)
if err != nil {
resultErr = fmt.Errorf("failed to encrypt: %w", err)
break
}
result = plaintext
case "nip04_encrypt":
if len(req.Params) != 2 {
resultErr = fmt.Errorf("wrong number of arguments to 'nip04_encrypt'")
break
}
thirdPartyPubkey := req.Params[0]
if !nostr.IsValidPublicKey(thirdPartyPubkey) {
resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a pubkey string")
break
}
if !p.authorizeEncryption(event.PubKey, secret) {
resultErr = fmt.Errorf("refusing to encrypt")
break
}
plaintext := req.Params[1]
sharedSecret, err := nip04.ComputeSharedSecret(thirdPartyPubkey, privateKey)
if err != nil {
resultErr = fmt.Errorf("failed to compute shared secret: %w", err)
break
}
ciphertext, err := nip04.Encrypt(plaintext, sharedSecret)
if err != nil {
resultErr = fmt.Errorf("failed to encrypt: %w", err)
break
}
result = ciphertext
case "nip04_decrypt":
if len(req.Params) != 2 {
resultErr = fmt.Errorf("wrong number of arguments to 'nip04_decrypt'")
break
}
thirdPartyPubkey := req.Params[0]
if !nostr.IsValidPublicKey(thirdPartyPubkey) {
resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a pubkey string")
break
}
if !p.authorizeEncryption(event.PubKey, secret) {
resultErr = fmt.Errorf("refusing to decrypt")
break
}
ciphertext := req.Params[1]
sharedSecret, err := nip04.ComputeSharedSecret(thirdPartyPubkey, privateKey)
if err != nil {
resultErr = fmt.Errorf("failed to compute shared secret: %w", err)
break
}
plaintext, err := nip04.Decrypt(ciphertext, sharedSecret)
if err != nil {
resultErr = fmt.Errorf("failed to encrypt: %w", err)
break break
} }
result = plaintext result = plaintext
@ -269,7 +232,7 @@ func (p *DynamicSigner) HandleRequest(event *nostr.Event) (
return req, resp, eventResponse, err return req, resp, eventResponse, err
} }
err = eventResponse.Sign(privateKey) err = eventResponse.Sign(handlerSecret)
if err != nil { if err != nil {
return req, resp, eventResponse, err return req, resp, eventResponse, err
} }

View File

@ -1,6 +1,7 @@
package nip46 package nip46
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"regexp" "regexp"
@ -26,10 +27,11 @@ type Response struct {
type Signer interface { type Signer interface {
GetSession(clientPubkey string) (Session, bool) GetSession(clientPubkey string) (Session, bool)
HandleRequest(event *nostr.Event) (req Request, resp Response, eventResponse nostr.Event, err error) HandleRequest(context.Context, *nostr.Event) (req Request, resp Response, eventResponse nostr.Event, err error)
} }
type Session struct { type Session struct {
PublicKey string
SharedKey []byte // nip04 SharedKey []byte // nip04
ConversationKey [32]byte // nip44 ConversationKey [32]byte // nip44
} }

View File

@ -1,6 +1,7 @@
package nip46 package nip46
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"slices" "slices"
@ -60,7 +61,13 @@ func (p *StaticKeySigner) getOrCreateSession(clientPubkey string) (Session, erro
return Session{}, fmt.Errorf("failed to compute shared secret: %w", err) return Session{}, fmt.Errorf("failed to compute shared secret: %w", err)
} }
pubkey, err := nostr.GetPublicKey(p.secretKey)
if err != nil {
return Session{}, fmt.Errorf("failed to derive public key: %w", err)
}
session := Session{ session := Session{
PublicKey: pubkey,
SharedKey: shared, SharedKey: shared,
ConversationKey: ck, ConversationKey: ck,
} }
@ -76,7 +83,7 @@ func (p *StaticKeySigner) getOrCreateSession(clientPubkey string) (Session, erro
return session, nil return session, nil
} }
func (p *StaticKeySigner) HandleRequest(event *nostr.Event) ( func (p *StaticKeySigner) HandleRequest(_ context.Context, event *nostr.Event) (
req Request, req Request,
resp Response, resp Response,
eventResponse nostr.Event, eventResponse nostr.Event,
@ -110,14 +117,8 @@ func (p *StaticKeySigner) HandleRequest(event *nostr.Event) (
result = "ack" result = "ack"
harmless = true harmless = true
case "get_public_key": case "get_public_key":
pubkey, err := nostr.GetPublicKey(p.secretKey) result = session.PublicKey
if err != nil { harmless = true
resultErr = fmt.Errorf("failed to derive public key: %w", err)
break
} else {
result = pubkey
harmless = true
}
case "sign_event": case "sign_event":
if len(req.Params) != 1 { if len(req.Params) != 1 {
resultErr = fmt.Errorf("wrong number of arguments to 'sign_event'") resultErr = fmt.Errorf("wrong number of arguments to 'sign_event'")