novox/hq ADR 0085, amended: the mesh's root secrets — the store's superuser, the broker's administrator, every secret a module holds for itself — were sealed to a node key and nothing else, so a lost node took them with it. Now the mesh records an operator's public sealing key and seals every own secret to it as well, minted or accepted. The private half is written once by `operator key new` to a file the operator keeps off the mesh; the mesh holds one more blob per secret that it cannot open. `secret recover` opens a secret with that key, to a 0600 file, from the store or from an export; `secret export` writes every operator-sealed copy as ciphertext. A module that `keeps` (the vault) is handed that export as a declared file on its own disk, so recovery survives the store. Secrets made before the key exists have no operator copy and are said so — the plaintext was discarded — until each is issued again.
73 lines
1.9 KiB
Go
73 lines
1.9 KiB
Go
package secrets
|
|
|
|
import "testing"
|
|
|
|
// A secret sealed to the operator as well is opened by the operator's key and by nothing else.
|
|
func TestAThirdRecipientOpensWithItsOwnKeyOnly(t *testing.T) {
|
|
nodePub, nodePriv, err := Keypair()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
opPub, opPriv, err := Keypair()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sealed, more, err := MakeAlso(nodePub, nodePub, opPub)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(more) != 1 {
|
|
t.Fatalf("%d extra blobs for one extra key", len(more))
|
|
}
|
|
fromNode, err := Open(nodePriv, sealed.ForConsumer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fromOperator, err := Open(opPriv, more[0])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(fromNode) != string(fromOperator) {
|
|
t.Fatal("the operator's copy is a different value from the node's")
|
|
}
|
|
if len(fromNode) != 40 {
|
|
t.Fatalf("a minted value is %d characters, not 40", len(fromNode))
|
|
}
|
|
if _, err := Open(nodePriv, more[0]); err == nil {
|
|
t.Fatal("the node's key opened the operator's blob")
|
|
}
|
|
if _, err := Open(opPriv, sealed.ForConsumer); err == nil {
|
|
t.Fatal("the operator's key opened the node's blob")
|
|
}
|
|
}
|
|
|
|
// An accepted value, sealed to the operator, comes back byte for byte.
|
|
func TestAnAcceptedValueRoundTripsThroughTheOperatorKey(t *testing.T) {
|
|
opPub, opPriv, err := Keypair()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
blob, err := Seal(opPub, []byte(" the-superuser's password, spaces and all "))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := Open(opPriv, blob)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(got) != " the-superuser's password, spaces and all " {
|
|
t.Fatalf("got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestAFingerprintNamesAKeyAndIsNotOne(t *testing.T) {
|
|
pub, _, _ := Keypair()
|
|
fp := Fingerprint(pub)
|
|
if len(fp) != len("sha256:")+16 || fp[:7] != "sha256:" {
|
|
t.Fatalf("fingerprint %q", fp)
|
|
}
|
|
if fp == Fingerprint(pub+"x") {
|
|
t.Fatal("two keys, one fingerprint")
|
|
}
|
|
}
|