package tunnel import ( "crypto/rand" "encoding/hex" "errors" "fmt" "os" "path/filepath" "strings" "git.smesh.lol/nostrino/pkg/crypto/secp256k1" "git.smesh.lol/nostrino/pkg/nostr" ) // ParseKey reads a 64-hex-character secret key. func ParseKey(raw string) ([32]byte, error) { var key [32]byte if err := nostr.Decode32(strings.TrimSpace(raw), key[:]); err != nil { return key, errors.New("tunnel: secret key must be 64 lowercase hex characters") } if _, ok := secp256k1.PubKey(key); !ok { return key, errors.New("tunnel: secret key is out of range") } return key, nil } // LoadOrCreateKey reads a key file, creating it with fresh key material on // first use. The file accepts either 32 raw bytes or 64 hex characters, and // is written as hex with mode 0600. func LoadOrCreateKey(path string) ([32]byte, error) { var key [32]byte body, err := os.ReadFile(path) switch { case err == nil: if len(body) == 32 { copy(key[:], body) } else if err := nostr.Decode32(strings.TrimSpace(string(body)), key[:]); err != nil { return key, fmt.Errorf("tunnel: key file %s: %w", path, err) } if _, ok := secp256k1.PubKey(key); !ok { return key, fmt.Errorf("tunnel: key file %s: invalid secret key", path) } return key, nil case !os.IsNotExist(err): return key, fmt.Errorf("tunnel: read key file: %w", err) } for { if _, err := rand.Read(key[:]); err != nil { return key, fmt.Errorf("tunnel: generate key: %w", err) } if _, ok := secp256k1.PubKey(key); ok { break } } if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { return key, fmt.Errorf("tunnel: key directory: %w", err) } tmp := path + ".tmp" if err := os.WriteFile(tmp, []byte(hex.EncodeToString(key[:])+"\n"), 0o600); err != nil { return key, fmt.Errorf("tunnel: write key file: %w", err) } if err := os.Rename(tmp, path); err != nil { return key, fmt.Errorf("tunnel: install key file: %w", err) } return key, nil }