key.go raw
1 package tunnel
2
3 import (
4 "crypto/rand"
5 "encoding/hex"
6 "errors"
7 "fmt"
8 "os"
9 "path/filepath"
10 "strings"
11
12 "git.smesh.lol/nostrino/pkg/crypto/secp256k1"
13 "git.smesh.lol/nostrino/pkg/nostr"
14 )
15
16 // ParseKey reads a 64-hex-character secret key.
17 func ParseKey(raw string) ([32]byte, error) {
18 var key [32]byte
19 if err := nostr.Decode32(strings.TrimSpace(raw), key[:]); err != nil {
20 return key, errors.New("tunnel: secret key must be 64 lowercase hex characters")
21 }
22 if _, ok := secp256k1.PubKey(key); !ok {
23 return key, errors.New("tunnel: secret key is out of range")
24 }
25 return key, nil
26 }
27
28 // LoadOrCreateKey reads a key file, creating it with fresh key material on
29 // first use. The file accepts either 32 raw bytes or 64 hex characters, and
30 // is written as hex with mode 0600.
31 func LoadOrCreateKey(path string) ([32]byte, error) {
32 var key [32]byte
33 body, err := os.ReadFile(path)
34 switch {
35 case err == nil:
36 if len(body) == 32 {
37 copy(key[:], body)
38 } else if err := nostr.Decode32(strings.TrimSpace(string(body)), key[:]); err != nil {
39 return key, fmt.Errorf("tunnel: key file %s: %w", path, err)
40 }
41 if _, ok := secp256k1.PubKey(key); !ok {
42 return key, fmt.Errorf("tunnel: key file %s: invalid secret key", path)
43 }
44 return key, nil
45 case !os.IsNotExist(err):
46 return key, fmt.Errorf("tunnel: read key file: %w", err)
47 }
48 for {
49 if _, err := rand.Read(key[:]); err != nil {
50 return key, fmt.Errorf("tunnel: generate key: %w", err)
51 }
52 if _, ok := secp256k1.PubKey(key); ok {
53 break
54 }
55 }
56 if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
57 return key, fmt.Errorf("tunnel: key directory: %w", err)
58 }
59 tmp := path + ".tmp"
60 if err := os.WriteFile(tmp, []byte(hex.EncodeToString(key[:])+"\n"), 0o600); err != nil {
61 return key, fmt.Errorf("tunnel: write key file: %w", err)
62 }
63 if err := os.Rename(tmp, path); err != nil {
64 return key, fmt.Errorf("tunnel: install key file: %w", err)
65 }
66 return key, nil
67 }
68