package secrets import ( "crypto/ecdh" "crypto/rand" "encoding/base64" "strings" "testing" ) // managerKey is the manager node's key pair, as the node would hold it: the public half reported to // the mesh, the private half kept and used only here. func managerKey(t *testing.T) (public, private string) { t.Helper() priv, err := ecdh.X25519().GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } return base64.StdEncoding.EncodeToString(priv.PublicKey().Bytes()), base64.StdEncoding.EncodeToString(priv.Bytes()) } // The whole carve-out in one test: the manager, and only the manager, reads its refresh token back. func TestOnlyTheManagerOpensAnAtRestValue(t *testing.T) { pub, priv := managerKey(t) const refresh = "rt-a-real-looking-refresh-token" at, err := SealAtRest(refresh, pub) if err != nil { t.Fatal(err) } got, err := OpenAtRest(at, pub, priv) if err != nil { t.Fatal(err) } if got != refresh { t.Fatalf("the refresh token did not survive: %q", got) } // Another node's key pair cannot open it — the wrapped data key is closed to the manager alone. otherPub, otherPriv := managerKey(t) if _, err := OpenAtRest(at, otherPub, otherPriv); err == nil { t.Fatal("a different node opened the manager's refresh token") } } // The database on its own — the ciphertext and the wrapped key, and nothing else — carries neither // the refresh token nor the symmetric key that would open it. This is the property a plain // encrypted-at-rest column does not have, and the reason the carve-out is bounded to the manager. func TestTheEnvelopeAloneRevealsNothing(t *testing.T) { pub, _ := managerKey(t) const refresh = "rt-the-long-lived-secret" at, err := SealAtRest(refresh, pub) if err != nil { t.Fatal(err) } for what, field := range map[string]string{ "the ciphertext": at.Token, "the wrapped key": at.WrappedKey, } { if strings.Contains(field, refresh) { t.Fatalf("%s holds the refresh token in the clear", what) } } // Two seals of one token look nothing alike: a fresh data key and nonce each time. again, err := SealAtRest(refresh, pub) if err != nil { t.Fatal(err) } if at.Token == again.Token { t.Fatal("two seals of the same token are identical, so the storage says they are the same") } } // A tampered ciphertext does not open. secretbox authenticates, so a flipped byte is caught rather // than yielding a quietly wrong token. func TestATamperedEnvelopeIsRefused(t *testing.T) { pub, priv := managerKey(t) at, err := SealAtRest("rt-value", pub) if err != nil { t.Fatal(err) } raw, err := base64.StdEncoding.DecodeString(at.Token) if err != nil { t.Fatal(err) } raw[len(raw)-1] ^= 0x01 at.Token = base64.StdEncoding.EncodeToString(raw) if _, err := OpenAtRest(at, pub, priv); err == nil { t.Fatal("a tampered refresh token opened as though it were intact") } } // Nothing to seal, and no key to seal to, are both refused rather than stored as a working envelope. func TestSealAtRestRefusesTheEmptyCases(t *testing.T) { pub, _ := managerKey(t) if _, err := SealAtRest("", pub); err == nil { t.Fatal("an empty value was sealed at rest") } if _, err := SealAtRest("rt-value", ""); err == nil { t.Fatal("a refresh token was sealed to a manager with no key") } }