mirror of
https://github.com/fiatjaf/nak.git
synced 2026-01-24 11:28:50 +00:00
use dekey by default on gift wrap and unwrap.
This commit is contained in:
36
dekey.go
36
dekey.go
@@ -33,16 +33,16 @@ var dekey = &cli.Command{
|
|||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "rotate",
|
Name: "rotate",
|
||||||
Usage: "force the creation of a new encryption key, effectively invalidating any previous ones",
|
Usage: "force the creation of a new decoupled encryption key, effectively invalidating any previous ones",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "authorize-all",
|
Name: "authorize-all",
|
||||||
Aliases: []string{"yolo"},
|
Aliases: []string{"yolo"},
|
||||||
Usage: "do not ask for confirmation, just automatically send the encryption key to all devices that exist",
|
Usage: "do not ask for confirmation, just automatically send the decoupled encryption key to all devices that exist",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "reject-all",
|
Name: "reject-all",
|
||||||
Usage: "do not ask for confirmation, just not send the encryption key to any device",
|
Usage: "do not ask for confirmation, just not send the decoupled encryption key to any device",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
@@ -93,7 +93,7 @@ var dekey = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check for kind:10044
|
// check for kind:10044
|
||||||
log("- checking for user encryption key (kind:10044)\n")
|
log("- checking for decoupled encryption key (kind:10044)\n")
|
||||||
keyAnnouncementResult := sys.Pool.FetchManyReplaceable(ctx, relays, nostr.Filter{
|
keyAnnouncementResult := sys.Pool.FetchManyReplaceable(ctx, relays, nostr.Filter{
|
||||||
Kinds: []nostr.Kind{10044},
|
Kinds: []nostr.Kind{10044},
|
||||||
Authors: []nostr.PubKey{userPub},
|
Authors: []nostr.PubKey{userPub},
|
||||||
@@ -104,7 +104,7 @@ var dekey = &cli.Command{
|
|||||||
var generateNewEncryptionKey bool
|
var generateNewEncryptionKey bool
|
||||||
keyAnnouncementEvent, ok := keyAnnouncementResult.Load(nostr.ReplaceableKey{PubKey: userPub, D: ""})
|
keyAnnouncementEvent, ok := keyAnnouncementResult.Load(nostr.ReplaceableKey{PubKey: userPub, D: ""})
|
||||||
if !ok {
|
if !ok {
|
||||||
log("- no user encryption key found, generating new one\n")
|
log("- no decoupled encryption key found, generating new one\n")
|
||||||
generateNewEncryptionKey = true
|
generateNewEncryptionKey = true
|
||||||
} else {
|
} else {
|
||||||
// get the pub from the tag
|
// get the pub from the tag
|
||||||
@@ -118,7 +118,7 @@ var dekey = &cli.Command{
|
|||||||
return fmt.Errorf("got invalid kind:10044 event, no 'n' tag")
|
return fmt.Errorf("got invalid kind:10044 event, no 'n' tag")
|
||||||
}
|
}
|
||||||
|
|
||||||
log(". an encryption public key already exists: %s\n", color.CyanString(ePub.Hex()))
|
log(". a decoupled encryption public key already exists: %s\n", color.CyanString(ePub.Hex()))
|
||||||
if c.Bool("rotate") {
|
if c.Bool("rotate") {
|
||||||
log(color.GreenString("rotating it by generating a new one\n"))
|
log(color.GreenString("rotating it by generating a new one\n"))
|
||||||
generateNewEncryptionKey = true
|
generateNewEncryptionKey = true
|
||||||
@@ -134,12 +134,12 @@ var dekey = &cli.Command{
|
|||||||
eKeyPath := filepath.Join(configPath, "dekey", "p", userPub.Hex(), "e", ePub.Hex())
|
eKeyPath := filepath.Join(configPath, "dekey", "p", userPub.Hex(), "e", ePub.Hex())
|
||||||
os.MkdirAll(filepath.Dir(eKeyPath), 0700)
|
os.MkdirAll(filepath.Dir(eKeyPath), 0700)
|
||||||
if err := os.WriteFile(eKeyPath, []byte(eSec.Hex()), 0600); err != nil {
|
if err := os.WriteFile(eKeyPath, []byte(eSec.Hex()), 0600); err != nil {
|
||||||
return fmt.Errorf("failed to write user encryption key: %w", err)
|
return fmt.Errorf("failed to write decoupled encryption key: %w", err)
|
||||||
}
|
}
|
||||||
log("user encryption key generated and stored, public key: %s\n", color.CyanString(ePub.Hex()))
|
log("decoupled encryption key generated and stored, public key: %s\n", color.CyanString(ePub.Hex()))
|
||||||
|
|
||||||
// publish kind:10044
|
// publish kind:10044
|
||||||
log("publishing user encryption public key (kind:10044)\n")
|
log("publishing decoupled encryption public key (kind:10044)\n")
|
||||||
evt10044 := nostr.Event{
|
evt10044 := nostr.Event{
|
||||||
Kind: 10044,
|
Kind: 10044,
|
||||||
Content: "",
|
Content: "",
|
||||||
@@ -164,10 +164,10 @@ var dekey = &cli.Command{
|
|||||||
return fmt.Errorf("invalid main key: %w", err)
|
return fmt.Errorf("invalid main key: %w", err)
|
||||||
}
|
}
|
||||||
if eSec.Public() != ePub {
|
if eSec.Public() != ePub {
|
||||||
return fmt.Errorf("stored user encryption key is corrupted: %w", err)
|
return fmt.Errorf("stored decoupled encryption key is corrupted: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log("- encryption key not found locally, attempting to fetch the key from other devices\n")
|
log("- decoupled encryption key not found locally, attempting to fetch the key from other devices\n")
|
||||||
|
|
||||||
// check if our kind:4454 is already published
|
// check if our kind:4454 is already published
|
||||||
log("- checking for existing device announcement (kind:4454)\n")
|
log("- checking for existing device announcement (kind:4454)\n")
|
||||||
@@ -242,14 +242,14 @@ var dekey = &cli.Command{
|
|||||||
}
|
}
|
||||||
// check if it matches mainPub
|
// check if it matches mainPub
|
||||||
if eSec.Public() == ePub {
|
if eSec.Public() == ePub {
|
||||||
log(color.GreenString("successfully decrypted encryption key from another device\n"))
|
log(color.GreenString("successfully received decoupled encryption key from another device\n"))
|
||||||
// store it
|
// store it
|
||||||
os.MkdirAll(filepath.Dir(eKeyPath), 0700)
|
os.MkdirAll(filepath.Dir(eKeyPath), 0700)
|
||||||
os.WriteFile(eKeyPath, []byte(eSecHex), 0600)
|
os.WriteFile(eKeyPath, []byte(eSecHex), 0600)
|
||||||
|
|
||||||
// delete our 4454 if we had one, since we received the key
|
// delete our 4454 if we had one, since we received the key
|
||||||
if len(ourDeviceAnnouncementEvents) > 0 {
|
if len(ourDeviceAnnouncementEvents) > 0 {
|
||||||
log("deleting our device announcement (kind:4454) since we received the encryption key\n")
|
log("deleting our device announcement (kind:4454) since we received the decoupled encryption key\n")
|
||||||
deletion4454 := nostr.Event{
|
deletion4454 := nostr.Event{
|
||||||
CreatedAt: nostr.Now(),
|
CreatedAt: nostr.Now(),
|
||||||
Kind: 5,
|
Kind: 5,
|
||||||
@@ -290,11 +290,11 @@ var dekey = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if eSec == [32]byte{} {
|
if eSec == [32]byte{} {
|
||||||
log("encryption secret key not available, must be sent from another device to %s first\n",
|
log("decoupled encryption secret key not available, must be sent from another device to %s first\n",
|
||||||
color.YellowString(deviceName))
|
color.YellowString(deviceName))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log(color.GreenString("- encryption key ready\n"))
|
log(color.GreenString("- decoupled encryption key ready\n"))
|
||||||
|
|
||||||
// now we have mainSec, check for other kind:4454 events newer than the 10044
|
// now we have mainSec, check for other kind:4454 events newer than the 10044
|
||||||
log("- checking for other devices and key messages so we can send the key\n")
|
log("- checking for other devices and key messages so we can send the key\n")
|
||||||
@@ -359,7 +359,7 @@ var dekey = &cli.Command{
|
|||||||
} else {
|
} else {
|
||||||
var proceed bool
|
var proceed bool
|
||||||
if err := survey.AskOne(&survey.Confirm{
|
if err := survey.AskOne(&survey.Confirm{
|
||||||
Message: fmt.Sprintf("share encryption key with %s"+colors.bold("?"),
|
Message: fmt.Sprintf("share decoupled encryption key with %s"+colors.bold("?"),
|
||||||
color.YellowString(deviceTag[1])),
|
color.YellowString(deviceTag[1])),
|
||||||
}, &proceed); err != nil {
|
}, &proceed); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -398,7 +398,7 @@ var dekey = &cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log("- sending encryption key to new device %s\n", color.YellowString(deviceTag[1]))
|
log("- sending decoupled encryption key to new device %s\n", color.YellowString(deviceTag[1]))
|
||||||
ss, err := nip44.GenerateConversationKey(theirDevice, deviceSec)
|
ss, err := nip44.GenerateConversationKey(theirDevice, deviceSec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@@ -424,7 +424,7 @@ var dekey = &cli.Command{
|
|||||||
if err := publishFlow(ctx, c, kr, evt4455, relayList); err != nil {
|
if err := publishFlow(ctx, c, kr, evt4455, relayList); err != nil {
|
||||||
log(color.RedString("failed to publish key message: %v\n"), err)
|
log(color.RedString("failed to publish key message: %v\n"), err)
|
||||||
} else {
|
} else {
|
||||||
log(" - encryption key sent to %s\n", color.GreenString(deviceTag[1]))
|
log(" - decoupled encryption key sent to %s\n", color.GreenString(deviceTag[1]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
107
gift.go
107
gift.go
@@ -4,10 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
|
"fiatjaf.com/nostr/keyer"
|
||||||
"fiatjaf.com/nostr/nip44"
|
"fiatjaf.com/nostr/nip44"
|
||||||
|
"github.com/fatih/color"
|
||||||
"github.com/mailru/easyjson"
|
"github.com/mailru/easyjson"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
@@ -16,19 +20,27 @@ var gift = &cli.Command{
|
|||||||
Name: "gift",
|
Name: "gift",
|
||||||
Usage: "gift-wraps (or unwraps) an event according to NIP-59",
|
Usage: "gift-wraps (or unwraps) an event according to NIP-59",
|
||||||
Description: `example:
|
Description: `example:
|
||||||
nak event | nak gift wrap --sec <sec-a> -p <sec-b> | nak gift unwrap --sec <sec-b> --from <pub-a>`,
|
nak event | nak gift wrap --sec <sec-a> -p <sec-b> | nak gift unwrap --sec <sec-b> --from <pub-a>
|
||||||
|
|
||||||
|
a decoupled key (if it has been created or received with "nak dekey" previously) will be used by default.`,
|
||||||
DisableSliceFlagSeparator: true,
|
DisableSliceFlagSeparator: true,
|
||||||
|
Flags: append(
|
||||||
|
defaultKeyFlags,
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "use-direct",
|
||||||
|
Usage: "Use the key given to --sec directly even when a decoupled key exists.",
|
||||||
|
},
|
||||||
|
),
|
||||||
Commands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
{
|
{
|
||||||
Name: "wrap",
|
Name: "wrap",
|
||||||
Flags: append(
|
Flags: []cli.Flag{
|
||||||
defaultKeyFlags,
|
|
||||||
&PubKeyFlag{
|
&PubKeyFlag{
|
||||||
Name: "recipient-pubkey",
|
Name: "recipient-pubkey",
|
||||||
Aliases: []string{"p", "tgt", "target", "pubkey", "to"},
|
Aliases: []string{"p", "tgt", "target", "pubkey", "to"},
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
),
|
},
|
||||||
Usage: "turns an event into a rumor (unsigned) then gift-wraps it to the recipient",
|
Usage: "turns an event into a rumor (unsigned) then gift-wraps it to the recipient",
|
||||||
Description: `example:
|
Description: `example:
|
||||||
nak event -c 'hello' | nak gift wrap --sec <my-secret-key> -p <target-public-key>`,
|
nak event -c 'hello' | nak gift wrap --sec <my-secret-key> -p <target-public-key>`,
|
||||||
@@ -38,14 +50,25 @@ var gift = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
recipient := getPubKey(c, "recipient-pubkey")
|
// get sender pubkey (ourselves)
|
||||||
|
|
||||||
// get sender pubkey
|
|
||||||
sender, err := kr.GetPublicKey(ctx)
|
sender, err := kr.GetPublicKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get sender pubkey: %w", err)
|
return fmt.Errorf("failed to get sender pubkey: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cipher nostr.Cipher = kr
|
||||||
|
// use decoupled key if it exists
|
||||||
|
configPath := c.String("config-path")
|
||||||
|
eSec, has, err := getDecoupledEncryptionKey(ctx, configPath, sender)
|
||||||
|
if has {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decoupled encryption key exists, but we failed to get it: %w; call `nak dekey` to attempt a fix or call this again with --use-direct to bypass", err)
|
||||||
|
}
|
||||||
|
cipher = keyer.NewPlainKeySigner(eSec)
|
||||||
|
}
|
||||||
|
|
||||||
|
recipient := getPubKey(c, "recipient-pubkey")
|
||||||
|
|
||||||
// read event from stdin
|
// read event from stdin
|
||||||
for eventJSON := range getJsonsOrBlank() {
|
for eventJSON := range getJsonsOrBlank() {
|
||||||
if eventJSON == "{}" {
|
if eventJSON == "{}" {
|
||||||
@@ -65,7 +88,7 @@ var gift = &cli.Command{
|
|||||||
|
|
||||||
// create seal
|
// create seal
|
||||||
rumorJSON, _ := easyjson.Marshal(rumor)
|
rumorJSON, _ := easyjson.Marshal(rumor)
|
||||||
encryptedRumor, err := kr.Encrypt(ctx, string(rumorJSON), recipient)
|
encryptedRumor, err := cipher.Encrypt(ctx, string(rumorJSON), recipient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to encrypt rumor: %w", err)
|
return fmt.Errorf("failed to encrypt rumor: %w", err)
|
||||||
}
|
}
|
||||||
@@ -115,20 +138,36 @@ var gift = &cli.Command{
|
|||||||
Usage: "decrypts a gift-wrap event sent by the sender to us and exposes its internal rumor (unsigned event).",
|
Usage: "decrypts a gift-wrap event sent by the sender to us and exposes its internal rumor (unsigned event).",
|
||||||
Description: `example:
|
Description: `example:
|
||||||
nak req -p <my-public-key> -k 1059 dmrelay.com | nak gift unwrap --sec <my-secret-key> --from <sender-public-key>`,
|
nak req -p <my-public-key> -k 1059 dmrelay.com | nak gift unwrap --sec <my-secret-key> --from <sender-public-key>`,
|
||||||
Flags: append(
|
Flags: []cli.Flag{
|
||||||
defaultKeyFlags,
|
|
||||||
&PubKeyFlag{
|
&PubKeyFlag{
|
||||||
Name: "sender-pubkey",
|
Name: "sender-pubkey",
|
||||||
Aliases: []string{"p", "src", "source", "pubkey", "from"},
|
Aliases: []string{"p", "src", "source", "pubkey", "from"},
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
),
|
},
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
kr, _, err := gatherKeyerFromArguments(ctx, c)
|
kr, _, err := gatherKeyerFromArguments(ctx, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get receiver public key (ourselves)
|
||||||
|
receiver, err := kr.GetPublicKey(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cipher nostr.Cipher = kr
|
||||||
|
// use decoupled key if it exists
|
||||||
|
configPath := c.String("config-path")
|
||||||
|
eSec, has, err := getDecoupledEncryptionKey(ctx, configPath, receiver)
|
||||||
|
if has {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decoupled encryption key exists, but we failed to get it: %w; call `nak dekey` to attempt a fix or call this again with --use-direct to bypass", err)
|
||||||
|
}
|
||||||
|
cipher = keyer.NewPlainKeySigner(eSec)
|
||||||
|
}
|
||||||
|
|
||||||
sender := getPubKey(c, "sender-pubkey")
|
sender := getPubKey(c, "sender-pubkey")
|
||||||
|
|
||||||
// read gift-wrapped event from stdin
|
// read gift-wrapped event from stdin
|
||||||
@@ -149,7 +188,7 @@ var gift = &cli.Command{
|
|||||||
ephemeralPubkey := wrap.PubKey
|
ephemeralPubkey := wrap.PubKey
|
||||||
|
|
||||||
// decrypt seal
|
// decrypt seal
|
||||||
sealJSON, err := kr.Decrypt(ctx, wrap.Content, ephemeralPubkey)
|
sealJSON, err := cipher.Decrypt(ctx, wrap.Content, ephemeralPubkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to decrypt seal: %w", err)
|
return fmt.Errorf("failed to decrypt seal: %w", err)
|
||||||
}
|
}
|
||||||
@@ -164,7 +203,7 @@ var gift = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// decrypt rumor
|
// decrypt rumor
|
||||||
rumorJSON, err := kr.Decrypt(ctx, seal.Content, sender)
|
rumorJSON, err := cipher.Decrypt(ctx, seal.Content, sender)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to decrypt rumor: %w", err)
|
return fmt.Errorf("failed to decrypt rumor: %w", err)
|
||||||
}
|
}
|
||||||
@@ -190,3 +229,45 @@ func randomNow() nostr.Timestamp {
|
|||||||
randomOffset := rand.Int63n(twoDays)
|
randomOffset := rand.Int63n(twoDays)
|
||||||
return nostr.Timestamp(now - randomOffset)
|
return nostr.Timestamp(now - randomOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDecoupledEncryptionKey(ctx context.Context, configPath string, pubkey nostr.PubKey) (nostr.SecretKey, bool, error) {
|
||||||
|
relays := sys.FetchWriteRelays(ctx, pubkey)
|
||||||
|
|
||||||
|
keyAnnouncementResult := sys.Pool.FetchManyReplaceable(ctx, relays, nostr.Filter{
|
||||||
|
Kinds: []nostr.Kind{10044},
|
||||||
|
Authors: []nostr.PubKey{pubkey},
|
||||||
|
}, nostr.SubscriptionOptions{Label: "nak-nip4e-gift"})
|
||||||
|
var eSec nostr.SecretKey
|
||||||
|
var ePub nostr.PubKey
|
||||||
|
|
||||||
|
keyAnnouncementEvent, ok := keyAnnouncementResult.Load(nostr.ReplaceableKey{PubKey: pubkey, D: ""})
|
||||||
|
if ok {
|
||||||
|
// get the pub from the tag
|
||||||
|
for _, tag := range keyAnnouncementEvent.Tags {
|
||||||
|
if len(tag) >= 2 && tag[0] == "n" {
|
||||||
|
ePub, _ = nostr.PubKeyFromHex(tag[1])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ePub == nostr.ZeroPK {
|
||||||
|
return [32]byte{}, true, fmt.Errorf("got invalid kind:10044 event, no 'n' tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we have the key
|
||||||
|
eKeyPath := filepath.Join(configPath, "dekey", "p", pubkey.Hex(), "e", ePub.Hex())
|
||||||
|
if data, err := os.ReadFile(eKeyPath); err == nil {
|
||||||
|
log(color.GreenString("- and we have it locally already\n"))
|
||||||
|
eSec, err = nostr.SecretKeyFromHex(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return [32]byte{}, true, fmt.Errorf("invalid main key: %w", err)
|
||||||
|
}
|
||||||
|
if eSec.Public() != ePub {
|
||||||
|
return [32]byte{}, true, fmt.Errorf("stored decoupled encryption key is corrupted: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return eSec, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [32]byte{}, false, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user