112 lines
4.5 KiB
Go
112 lines
4.5 KiB
Go
package link
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/ed25519"
|
|
"encoding/base64"
|
|
"errors"
|
|
"git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshnodeenrollerclient"
|
|
"git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
|
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// signedBy is an answer for ace, signed under the enrolment context by the key seeded, naming that key as its signer.
|
|
func signedBy(t *testing.T, seed byte) (meshnodeenrollerclient.RedeemAnswer, ed25519.PublicKey) {
|
|
t.Helper()
|
|
priv := ed25519.NewKeyFromSeed(bytes.Repeat([]byte{seed}, ed25519.SeedSize))
|
|
pub := priv.Public().(ed25519.PublicKey)
|
|
a := meshnodeenrollerclient.RedeemAnswer{Node: "ace", Login: "node.ace", Account: `{"sealed":"box"}`, Certificate: "PEM", Signer: base64.StdEncoding.EncodeToString(pub)}
|
|
ctx, err := meshvaultclient.SignContext(meshvaultclient.KindEnrolment)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
a.Signature = base64.StdEncoding.EncodeToString(meshvaultclient.SignMessage(priv, ctx, a.SignedBytes()))
|
|
return a, pub
|
|
}
|
|
|
|
type standInConfirming struct {
|
|
steps *[]string
|
|
nl meshvault.NodeLoginsByNodeValue
|
|
}
|
|
|
|
func (c standInConfirming) Login(context.Context) (meshvault.NodeLoginsByNodeValue, error) {
|
|
*c.steps = append(*c.steps, "read node-logins")
|
|
return c.nl, nil
|
|
}
|
|
func (c standInConfirming) Confirm(context.Context) error {
|
|
*c.steps = append(*c.steps, "confirm")
|
|
return nil
|
|
}
|
|
func (c standInConfirming) Close() {}
|
|
|
|
// 774/S64/AC1 and AC2 (ADR 0587 §6): an answer not signed by the key the token carries (or naming another) is refused and
|
|
// nothing is kept or dialled; a signed one is kept, then the engine dials as the login it names, reads its key of node-logins,
|
|
// and confirms; a key naming another login than the answer's is not confirmed.
|
|
func TestTheEngineKeepsOnlyAnAnswerSignedByTheKeyItsTokenCarries(t *testing.T) {
|
|
ctx := context.Background()
|
|
good, pub := signedBy(t, 1)
|
|
forged, _ := signedBy(t, 2)
|
|
badNode := good
|
|
badNode.Node = "g14"
|
|
for name, c := range map[string]struct {
|
|
answer meshnodeenrollerclient.RedeemAnswer
|
|
want string
|
|
}{
|
|
"signed by another key": {forged, "signing key other than the one the token carries"},
|
|
"another node": {badNode, `is for "g14"`},
|
|
"altered after signing": {func() meshnodeenrollerclient.RedeemAnswer { a := good; a.Account = "other"; return a }(), "not signed by the key the token carries"},
|
|
} {
|
|
var steps []string
|
|
j := Joining{Node: "ace", Signer: pub,
|
|
Redeem: func(context.Context) (meshnodeenrollerclient.RedeemAnswer, error) { return c.answer, nil },
|
|
Open: func(meshnodeenrollerclient.RedeemAnswer) (string, error) {
|
|
steps = append(steps, "open")
|
|
return "pw", nil
|
|
},
|
|
Keep: func(meshnodeenrollerclient.RedeemAnswer, string) error { steps = append(steps, "keep"); return nil },
|
|
Dial: func(context.Context, string, string) (Confirming, error) {
|
|
steps = append(steps, "dial")
|
|
return nil, errors.New("must not dial")
|
|
}}
|
|
if _, err := j.Run(ctx); err == nil || !strings.Contains(err.Error(), c.want) || len(steps) != 0 {
|
|
t.Errorf("%s: %v, steps %v", name, err, steps)
|
|
}
|
|
}
|
|
var steps []string
|
|
login := "node.ace"
|
|
j := Joining{Node: "ace", Signer: pub,
|
|
Redeem: func(context.Context) (meshnodeenrollerclient.RedeemAnswer, error) {
|
|
steps = append(steps, "redeem")
|
|
return good, nil
|
|
},
|
|
Open: func(meshnodeenrollerclient.RedeemAnswer) (string, error) {
|
|
steps = append(steps, "open")
|
|
return "pw", nil
|
|
},
|
|
Keep: func(a meshnodeenrollerclient.RedeemAnswer, pw string) error {
|
|
steps = append(steps, "keep "+a.Login+" "+pw)
|
|
return nil
|
|
},
|
|
Dial: func(_ context.Context, l, pw string) (Confirming, error) {
|
|
steps = append(steps, "dial "+l+" "+pw)
|
|
return standInConfirming{steps: &steps, nl: meshvault.NodeLoginsByNodeValue{Node: "ace", Login: login}}, nil
|
|
}}
|
|
if _, err := j.Run(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if want := []string{"redeem", "open", "keep node.ace pw", "dial node.ace pw", "read node-logins", "confirm"}; !slices.Equal(steps, want) {
|
|
t.Errorf("steps %v, want %v", steps, want)
|
|
}
|
|
steps, login = nil, "node.ace--b"
|
|
if _, err := j.Run(ctx); err == nil || slices.Contains(steps, "confirm") {
|
|
t.Errorf("a key naming another login was confirmed: %v, %v", err, steps)
|
|
}
|
|
if CalledSeatSubject("mesh-enrolment", "redeem", "enrol.ace") != "mesh.seat.mesh-enrolment.call.redeem.enrol~ace" {
|
|
t.Error("the call's subject does not name its caller as the composer grants it")
|
|
}
|
|
}
|