package main import ( "bytes" "context" "crypto/rand" "encoding/base64" "encoding/json" "io" "os" "path/filepath" "strings" "testing" "golang.org/x/crypto/nacl/box" ) // 774/S51/AC1, AC2 (novox/hq ADR 0587 §6, §8): a token sealed to a test operator key (as mesh-vault seals it: the text // of the token, anonymous box, base64) is opened with the private half and printed as a token the engine's parser reads // (base64url of version 1 JSON with node, broker, fingerprint, signer and secret), and nothing is asked of the engine's // socket, which here is a path nobody listens on; the wrong key prints nothing and says the token is not sealed to it. func TestEnrolOpenOpensASealedTokenLocallyAndOnlyWithItsKey(t *testing.T) { t.Setenv("MESH_VERB", "") token := base64.RawURLEncoding.EncodeToString([]byte(`{"v":1,"node":"new1","broker":"tls://10.77.0.1:4222",` + `"fingerprint":"sha256:bus","signer":"` + base64.StdEncoding.EncodeToString(make([]byte, 32)) + `","secret":"one-time"}`)) pub, priv, _ := box.GenerateKey(rand.Reader) _, otherPriv, _ := box.GenerateKey(rand.Reader) sealed, err := box.SealAnonymous(nil, []byte(token), pub, rand.Reader) if err != nil { t.Fatal(err) } dir := t.TempDir() keyFile := func(name string, k *[32]byte) string { p := filepath.Join(dir, name) if err := os.WriteFile(p, []byte(base64.StdEncoding.EncodeToString(k[:])+"\n"), 0o600); err != nil { t.Fatal(err) } return p } before, beforeIn := socket, standardInput socket = filepath.Join(dir, "nobody-listens.sock") t.Cleanup(func() { socket, standardInput = before, beforeIn }) for _, c := range []struct { name string args []string stdin string code int out string err string }{ {"the right key", []string{"enrol", "open", "--key", keyFile("op", priv)}, base64.StdEncoding.EncodeToString(sealed) + "\n", 0, token + "\n", ""}, {"the key as --key=", []string{"enrol", "open", "--key=" + keyFile("op2", priv)}, base64.StdEncoding.EncodeToString(sealed), 0, token + "\n", ""}, {"the wrong key", []string{"enrol", "open", "--key", keyFile("other", otherPriv)}, base64.StdEncoding.EncodeToString(sealed), 1, "", "not sealed to that key"}, {"no key", []string{"enrol", "open"}, base64.StdEncoding.EncodeToString(sealed), 2, "", "nox enrol open --key"}, {"not a sealed token", []string{"enrol", "open", "--key", keyFile("op3", priv)}, "@@@", 1, "", "not a sealed token"}, } { standardInput = func() (io.Reader, bool) { return strings.NewReader(c.stdin), false } var out, errOut bytes.Buffer code := run(context.Background(), c.args, &out, &errOut) if code != c.code || out.String() != c.out || !strings.Contains(errOut.String(), c.err) { t.Errorf("%s: exit %d, stdout %q, stderr %q", c.name, code, out.String(), errOut.String()) } } // What it prints is a token the engine's parser reads. var parsed struct { V int `json:"v"` Node string `json:"node"` Secret string `json:"secret"` } raw, _ := base64.RawURLEncoding.DecodeString(token) if json.Unmarshal(raw, &parsed) != nil || parsed.V != 1 || parsed.Node != "new1" || parsed.Secret != "one-time" { t.Errorf("the token is %+v", parsed) } // Standard input a terminal is never read. standardInput = func() (io.Reader, bool) { return strings.NewReader("x"), true } var out, errOut bytes.Buffer if code := run(context.Background(), []string{"enrol", "open", "--key", keyFile("op4", priv)}, &out, &errOut); code != 2 || out.Len() != 0 { t.Errorf("a terminal on standard input: exit %d, stdout %q", code, out.String()) } }