Files
mesh-catalog/modules/mesh-cli/cmd/mesh-cli/enrol.go
T
jschoubben 9249ca7b57 mesh-cli: nox enrol open opens a sealed enrolment token locally with the operator's private key
The seat mesh-enrolment's tokens answers the token sealed to the operator key; the operator opens it at their own
terminal. The subcommand reads the sealed token on standard input and the private key from a file, prints the token text
a joining machine takes, asks nothing of the node-engine's socket, and says the token is not sealed to a wrong key
(ADR 0587 §6, §8). New: one table-style test function; mesh-cli had 6, so none was merged or deleted.

(issue 774, 774/S51)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 01:42:19 +01:00

98 lines
3.5 KiB
Go

package main
// `nox enrol open`: the operator opens an enrolment token at their own terminal (novox/hq ADR 0587 §6, §8).
//
// The seat mesh-enrolment's `tokens` answers the token sealed to the operator key, which only the operator's private key
// opens; the mesh never holds it. This subcommand is local: it asks nothing of the node-engine's socket and nothing of
// the controller or the bus. It reads the sealed token (the base64 of the sealed box) on standard input and the private
// key from a file (`--key`, the raw X25519 key in base64 that `operator key make` wrote), opens the box, and prints the
// token text a joining machine's `enrol` takes. The key is read from the file named and nowhere else, and never printed.
import (
"crypto/ecdh"
"encoding/base64"
"errors"
"fmt"
"io"
"os"
"strings"
"golang.org/x/crypto/nacl/box"
)
const enrolUsage = `nox enrol open --key <operator private key file>
Opens an enrolment token sealed to the operator key (the answer of the seat mesh-enrolment's tokens) and prints the token
to type on the new machine. The sealed token is read on standard input; this runs here and asks nothing of the mesh.
`
// maxSealedToken bounds what is read of standard input: a token is a few hundred bytes.
const maxSealedToken = 16 << 10
// enrolOpen runs `enrol open`: args are what follows `enrol open`.
func enrolOpen(args []string, stdin io.Reader, stdinIsTerminal bool, stdout, stderr io.Writer) int {
keyFile := ""
for i := 0; i < len(args); i++ {
switch {
case args[i] == "--key" && i+1 < len(args):
keyFile = args[i+1]
i++
case strings.HasPrefix(args[i], "--key="):
keyFile = strings.TrimPrefix(args[i], "--key=")
default:
fmt.Fprint(stderr, enrolUsage)
return 2
}
}
if keyFile == "" {
fmt.Fprint(stderr, enrolUsage)
return 2
}
if stdinIsTerminal {
fmt.Fprintln(stderr, "mesh-cli: give the sealed token on standard input (a pipe or a file); nothing was opened")
return 2
}
raw, err := os.ReadFile(keyFile)
if err != nil {
fmt.Fprintf(stderr, "mesh-cli: the operator key could not be read: %v\n", err)
return 1
}
sealed, err := io.ReadAll(io.LimitReader(stdin, maxSealedToken+1))
if err != nil || len(sealed) > maxSealedToken {
fmt.Fprintln(stderr, "mesh-cli: the sealed token could not be read from standard input, or is longer than a token is")
return 1
}
text, err := openSealed(strings.TrimSpace(string(raw)), strings.TrimSpace(string(sealed)))
if err != nil {
fmt.Fprintf(stderr, "mesh-cli: %v\n", err)
return 1
}
fmt.Fprintln(stdout, text)
return 0
}
// openSealed opens a value sealed to the operator key (the sealed box of mesh-vault's operator copies, box.SealAnonymous
// in base64) with the private key, the raw X25519 key in base64.
func openSealed(privateKey, sealed string) (string, error) {
private, err := base64.StdEncoding.DecodeString(privateKey)
if err != nil || len(private) != 32 {
return "", errors.New("that is not an operator private key (the raw X25519 key in base64)")
}
key, err := ecdh.X25519().NewPrivateKey(private)
if err != nil {
return "", fmt.Errorf("that is not a usable operator private key: %w", err)
}
blob, err := base64.StdEncoding.DecodeString(sealed)
if err != nil {
return "", fmt.Errorf("this is not a sealed token: %w", err)
}
var pub, priv [32]byte
copy(pub[:], key.PublicKey().Bytes())
copy(priv[:], private)
out, ok := box.OpenAnonymous(nil, blob, &pub, &priv)
if !ok {
return "", errors.New("this token is not sealed to that key; nothing is printed")
}
return strings.TrimSpace(string(out)), nil
}