207 lines
8.9 KiB
Go
207 lines
8.9 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
|
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
|
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
|
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
|
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay"
|
|
"git.novox.be/novox/mesh-sdk/go/seal"
|
|
)
|
|
|
|
// signersState is the vault's state `signers` in memory, counting writes.
|
|
type signersState struct {
|
|
keys map[string][]byte
|
|
writes int
|
|
}
|
|
|
|
func (s *signersState) Get(key string) ([]byte, error) { return s.keys[key], nil }
|
|
func (s *signersState) Put(key string, value []byte) error {
|
|
s.keys[key] = append([]byte(nil), value...)
|
|
s.writes++
|
|
return nil
|
|
}
|
|
|
|
// L3 s2 to s5 (novox/hq ADR 0550 §2): the vault makes its signing key and authority once, with their operator copies and
|
|
// no other holder; publishes their public halves only when they differ; signs a node's certificate for that node's
|
|
// node-engine alone, from its placement's name and its record's serving key; and no answer, event or record carries a
|
|
// key.
|
|
func TestTheVaultSigns(t *testing.T) {
|
|
v := newVault(t, nil)
|
|
state := &signersState{keys: map[string][]byte{}}
|
|
v.Signers = state
|
|
servingPub, _, _ := ed25519.GenerateKey(rand.Reader)
|
|
placements := map[string]placing.Placement{
|
|
"ace": {Node: "ace", Decided: placing.NodeDecided{Address: "10.77.0.3"}},
|
|
"g14": {Node: "g14", Decided: placing.NodeDecided{Address: "10.77.0.4"}},
|
|
"plain": {Node: "plain"},
|
|
}
|
|
// Research 072 row 37: ace and plain in their keys per module and node, g14 still in the form before.
|
|
v.Placement = PlacementReader{Read: func(key string) ([]byte, error) {
|
|
node, _, kind, err := assignerclient.ParseKey(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p, ok := placements[node]
|
|
if !ok || (node == "g14") != (kind == assignerclient.LegacyKind) {
|
|
return nil, nil
|
|
}
|
|
if kind == assignerclient.LegacyKind {
|
|
return assignerclient.EncodePlacement(p)
|
|
}
|
|
values, err := assignerclient.PlacementValues(p)
|
|
return values[key], err
|
|
}}
|
|
records := map[string]string{"ace": base64.StdEncoding.EncodeToString(servingPub), "plain": base64.StdEncoding.EncodeToString(servingPub)}
|
|
v.Haves = func(key string) ([]byte, error) {
|
|
node, _, _ := strings.Cut(key, ".")
|
|
serving, ok := records[node]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
return engineclient.EncodeHaveNode(nodeseat.HaveByNodeNodeValue{Record: &nodeseat.NodeRecord{Node: node, ServingKey: serving}})
|
|
}
|
|
|
|
// s2: made once, kept with their operator copies; a second pass makes nothing new and writes nothing again (s5).
|
|
if err := v.SigningPass(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var seeds []string
|
|
for _, name := range []string{SigningKeySecret, AuthorityKeySecret} {
|
|
r, held, err := v.Store.Read(name)
|
|
if err != nil || !held || r.Generation != 1 {
|
|
t.Fatalf("%s: held %v at generation %d, %v", name, held, r.Generation, err)
|
|
}
|
|
value, _ := seal.Open(r.AtRest, v.Own.PrivateKey)
|
|
if got := openOperator(t, v.operatorPublic, v.operatorPrivate, r.Operator.Sealed); got != value {
|
|
t.Errorf("%s: the operator copy does not open to the value held", name)
|
|
}
|
|
seeds = append(seeds, value)
|
|
}
|
|
key1, auth1, err := v.SignersValues()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
writes := state.writes
|
|
if writes != 2 {
|
|
t.Errorf("signers written %d times at the first pass, want its two keys", writes)
|
|
}
|
|
if err := v.SigningPass(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
key2, auth2, _ := v.SignersValues()
|
|
if key2 != key1 || auth2 != auth1 || state.writes != writes {
|
|
t.Errorf("a second pass changed the keys or wrote signers again (%d writes)", state.writes)
|
|
}
|
|
if _, err := vaultclient.DecodeSignerKey(state.keys[meshvault.SignersByKeyKey()]); err != nil {
|
|
t.Errorf("signers' key: %v", err)
|
|
}
|
|
authority, err := vaultclient.DecodeSignerAuthority(state.keys[meshvault.SignersByAuthorityKey()])
|
|
if err != nil {
|
|
t.Fatalf("signers' authority: %v", err)
|
|
}
|
|
// No other holder: `get` hands neither, even to an asker a rule names for it.
|
|
asker, _ := seal.GenerateKeyPair()
|
|
v.Askers = AllowList{"node.ace": {SigningKeySecret, AuthorityKeySecret}}
|
|
for _, name := range []string{SigningKeySecret, AuthorityKeySecret} {
|
|
if _, err := v.Get("node.ace", name, asker.PublicKey); err == nil {
|
|
t.Errorf("get handed %s", name)
|
|
}
|
|
}
|
|
v.Askers = nil
|
|
if forced, err := v.PublishSigners(true); err != nil || len(forced) != 2 {
|
|
t.Errorf("signers_reassert wrote %v, %v", forced, err)
|
|
}
|
|
|
|
// s3, s4: who may ask which kind, and the certificate built from bus state.
|
|
for _, row := range []struct {
|
|
name, caller, kind, node, refused string
|
|
}{
|
|
{"a module is not a node-engine", "ace.mesh-grants", vaultclient.KindNodeCertificate, "", "not a node-engine"},
|
|
{"another node's certificate", "node.g14", vaultclient.KindNodeCertificate, "ace", "its own alone"},
|
|
{"a node not on the private network", "node.plain", vaultclient.KindNodeCertificate, "", "not on the private network"},
|
|
{"a node that states no serving key", "node.g14", vaultclient.KindNodeCertificate, "", "no record"},
|
|
{"an enrolment has no caller yet", "node.ace", vaultclient.KindEnrolment, "", "ADR 0561 §5"},
|
|
{"the authority is the vault's own", "node.ace", vaultclient.KindAuthority, "", "the vault's own"},
|
|
{"an unproven caller", "", vaultclient.KindNodeCertificate, "", "proved"},
|
|
{"the node's own certificate", "node.ace", vaultclient.KindNodeCertificate, "ace", ""},
|
|
} {
|
|
v.bus.raw = nil
|
|
got, err := v.Sign(row.caller, SignArgs{Kind: row.kind, Node: row.node})
|
|
if row.refused != "" {
|
|
if err == nil || !strings.Contains(err.Error(), row.refused) {
|
|
t.Errorf("%s: %v, want refused saying %q", row.name, err, row.refused)
|
|
}
|
|
if len(v.bus.raw) != 0 {
|
|
t.Errorf("%s: refused, and said %v", row.name, v.bus.raw)
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", row.name, err)
|
|
}
|
|
if err := vaultclient.CheckNodeCertificate(got.Certificate, []string{authority.Certificate}, overlay.InternalName("ace"),
|
|
servingPub, authority.Made.Add(1)); err != nil {
|
|
t.Errorf("%s: the certificate does not check against signers' authority: %v", row.name, err)
|
|
}
|
|
if got.Signer != authority.Fingerprint || got.Digest == "" {
|
|
t.Errorf("%s: signer %s, digest %q", row.name, got.Signer, got.Digest)
|
|
}
|
|
// Said once as signed, kept in the record, and no key in any of it.
|
|
answer, _ := json.Marshal(got)
|
|
said := strings.Join(v.bus.raw, "\n")
|
|
if !strings.Contains(said, `"event":"signed"`) || !strings.Contains(said, got.Digest) {
|
|
t.Errorf("%s: signed not said: %s", row.name, said)
|
|
}
|
|
rows, _ := v.Signatures(10)
|
|
if len(rows) != 1 || rows[0].Caller != "node.ace" || rows[0].Kind != vaultclient.KindNodeCertificate || rows[0].Digest != got.Digest {
|
|
t.Errorf("%s: vault_signed lists %+v", row.name, rows)
|
|
}
|
|
status, _ := json.Marshal(v.SigningState())
|
|
for _, seed := range seeds {
|
|
raw, _ := base64.StdEncoding.DecodeString(seed)
|
|
priv := ed25519.NewKeyFromSeed(raw)
|
|
for _, text := range []string{string(answer), said, string(status), string(state.keys[meshvault.SignersByKeyKey()]),
|
|
string(state.keys[meshvault.SignersByAuthorityKey()])} {
|
|
if strings.Contains(text, seed) || strings.Contains(text, base64.StdEncoding.EncodeToString(priv)) {
|
|
t.Errorf("%s: a signing key's private half is in what the vault says: %s", row.name, text)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// A serving key changed in the record is the one certified next, never the one before.
|
|
other, _, _ := ed25519.GenerateKey(rand.Reader)
|
|
records["ace"] = base64.StdEncoding.EncodeToString(other)
|
|
got, err := v.Sign("node.ace", SignArgs{Kind: vaultclient.KindNodeCertificate})
|
|
if err != nil || vaultclient.CheckNodeCertificate(got.Certificate, []string{authority.Certificate}, overlay.InternalName("ace"),
|
|
servingPub, authority.Made.Add(1)) == nil {
|
|
t.Errorf("a certificate for a serving key the record no longer states: %v", err)
|
|
}
|
|
|
|
// s2: one secret lost beside the other, or both lost while signers publishes a key, makes nothing.
|
|
lost := newVault(t, nil)
|
|
lost.Signers = &signersState{keys: map[string][]byte{meshvault.SignersByKeyKey(): state.keys[meshvault.SignersByKeyKey()]}}
|
|
if made, err := lost.EnsureSigning(); made || err == nil || !strings.Contains(err.Error(), "MISSING") {
|
|
t.Errorf("keys made while signers publishes one: %v, %v", made, err)
|
|
}
|
|
half := newVault(t, nil)
|
|
if _, err := half.Put(SigningKeySecret, seeds[0], "the test"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if made, err := half.EnsureSigning(); made || err == nil || !strings.Contains(err.Error(), "MISSING") {
|
|
t.Errorf("an authority made beside a held signing key: %v, %v", made, err)
|
|
}
|
|
if _, held, _ := half.Store.Read(AuthorityKeySecret); held {
|
|
t.Error("the authority was made all the same")
|
|
}
|
|
}
|