go-ssb-room/internal/maybemod/keys/keys.go

170 lines
4.7 KiB
Go

// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021
//
// SPDX-License-Identifier: MIT
// Package keys could be it's own thing between go-ssb and this but not today
package keys
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/ssbc/go-secretstream/secrethandshake"
refs "github.com/ssbc/go-ssb-refs"
"go.cryptoscope.co/nocomment"
)
var SecretPerms = os.FileMode(0600)
// KeyPair contains a seret handshake keypair and the assosicated feed
type KeyPair struct {
Feed refs.FeedRef
Pair secrethandshake.EdKeyPair
}
// the format of the .ssb/secret file as defined by the js implementations
type ssbSecret struct {
Curve string `json:"curve"`
ID refs.FeedRef `json:"id"`
Private string `json:"private"`
Public string `json:"public"`
}
// IsValidFeedFormat checks if the passed FeedRef is for one of the two supported formats,
// legacy/crapp or GabbyGrove.
func IsValidFeedFormat(r refs.FeedRef) error {
if r.Algo() != refs.RefAlgoFeedSSB1 && r.Algo() != refs.RefAlgoFeedGabby {
return fmt.Errorf("ssb: unsupported feed format:%s", r.Algo())
}
return nil
}
// NewKeyPair generates a fresh KeyPair using the passed io.Reader as a seed.
// Passing nil is fine and will use crypto/rand.
func NewKeyPair(r io.Reader) (*KeyPair, error) {
// generate new keypair
kp, err := secrethandshake.GenEdKeyPair(r)
if err != nil {
return nil, fmt.Errorf("ssb: error building key pair: %w", err)
}
feed, err := refs.NewFeedRefFromBytes(kp.Public[:], refs.RefAlgoFeedSSB1)
if err != nil {
return nil, fmt.Errorf("ssb: error building key pair: %w", err)
}
keyPair := KeyPair{
Feed: feed,
Pair: *kp,
}
return &keyPair, nil
}
// SaveKeyPair serializes the passed KeyPair to path.
// It errors if path already exists.
func SaveKeyPair(kp KeyPair, path string) error {
if err := IsValidFeedFormat(kp.Feed); err != nil {
return err
}
if _, err := os.Stat(path); err == nil {
return fmt.Errorf("ssb.SaveKeyPair: key already exists:%q", path)
}
err := os.MkdirAll(filepath.Dir(path), 0700)
if err != nil && !os.IsExist(err) {
return fmt.Errorf("failed to create folder for keypair: %w", err)
}
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, SecretPerms)
if err != nil {
return fmt.Errorf("ssb.SaveKeyPair: failed to create file: %w", err)
}
if err := EncodeKeyPairAsJSON(kp, f); err != nil {
return err
}
if err := f.Close(); err != nil {
return fmt.Errorf("ssb.SaveKeyPair: failed to close file: %w", err)
}
return nil
}
// EncodeKeyPairAsJSON serializes the passed Keypair into the writer w
func EncodeKeyPairAsJSON(kp KeyPair, w io.Writer) error {
var sec = ssbSecret{
Curve: "ed25519",
ID: kp.Feed,
Private: base64.StdEncoding.EncodeToString(kp.Pair.Secret[:]) + ".ed25519",
Public: base64.StdEncoding.EncodeToString(kp.Pair.Public[:]) + ".ed25519",
}
err := json.NewEncoder(w).Encode(sec)
if err != nil {
return fmt.Errorf("ssb.EncodeKeyPairAsJSON: encoding failed: %w", err)
}
return nil
}
// LoadKeyPair opens fname, ignores any line starting with # and passes it ParseKeyPair
func LoadKeyPair(fname string) (*KeyPair, error) {
f, err := os.Open(fname)
if err != nil {
if os.IsNotExist(err) {
return nil, err
}
return nil, fmt.Errorf("ssb.LoadKeyPair: could not open key file %s: %w", fname, err)
}
defer f.Close()
info, err := f.Stat()
if err != nil {
return nil, fmt.Errorf("ssb.LoadKeyPair: could not stat key file %s: %w", fname, err)
}
if perms := info.Mode().Perm(); perms != SecretPerms {
return nil, fmt.Errorf("ssb.LoadKeyPair: expected key file permissions %s, but got %s", SecretPerms, perms)
}
return ParseKeyPair(nocomment.NewReader(f))
}
// ParseKeyPair json decodes an object from the reader.
// It expects std base64 encoded data under the `private` and `public` fields.
func ParseKeyPair(r io.Reader) (*KeyPair, error) {
var s ssbSecret
if err := json.NewDecoder(r).Decode(&s); err != nil {
return nil, fmt.Errorf("ssb.Parse: JSON decoding failed: %w", err)
}
if err := IsValidFeedFormat(s.ID); err != nil {
return nil, err
}
public, err := base64.StdEncoding.DecodeString(strings.TrimSuffix(s.Public, ".ed25519"))
if err != nil {
return nil, fmt.Errorf("ssb.Parse: base64 decode of public part failed: %w", err)
}
private, err := base64.StdEncoding.DecodeString(strings.TrimSuffix(s.Private, ".ed25519"))
if err != nil {
return nil, fmt.Errorf("ssb.Parse: base64 decode of private part failed: %w", err)
}
pair, err := secrethandshake.NewKeyPair(public, private)
if err != nil {
return nil, fmt.Errorf("ssb.Parse: base64 decode of private part failed: %w", err)
}
ssbkp := KeyPair{
Feed: s.ID,
Pair: *pair,
}
return &ssbkp, nil
}