mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
deploy / build (push) Waiting to run
base had its own key derivations and no dependency on hanzoai/cek, the
primitive that is meant to be the only one. Two HKDFs in the platform
plugin, two HMACs beside them for S3, and a two-level HMAC hierarchy in
the vault. None agreed with cek, and the comments describing them had
drifted off the code: org_db and platform.go both said HMAC where the
code ran HKDF, and the vault drew a KEK hierarchy in its package doc.
All of them are now cek.DeriveKey(master, namespace, subsystem):
org database org/{org} "org"
user database org/{org}/{user} "user"
org objects org/{org} "s3"
user objects org/{org}/{user} "s3"
vault shard org/{org}/{user} "vault"
shared vault org/{org}/{vault} "vault-shared"
A per-org and a per-user key are one derivation over two namespaces,
which is cek's argument: the key is bound to the name that renders the
path. namespace.Key refuses a user namespace, but that is about LAYOUT
and DeriveKey does not use it — a person's data is named org/{org}/{user}
here, which is what namespace.Of already builds, and it is what keeps
acme/alice and globex/alice on different keys. The vault reaches the same
namespace through OrgProject, because its ids are DIDs and a colon is not
legal in a segment; Sanitize is injective, so two DIDs cannot fold onto
one vault.
The vault's org KEK is gone. It was there to be rotatable without
re-encrypting user data, nothing ever rotated it, and the org is in the
namespace now — the property it was actually providing.
store/keyring.go keeps its own HKDF, and now says why. It WRAPS a random
age identity rather than deriving a key, so Rotate re-wraps one sidecar
instead of rewriting every byte of ciphertext, and its secret is a
per-org KMS KEK rather than one master. cek has no wrap by design. A
different shape, left alone rather than bent into one.
Tests now compare each derivation against cek.DeriveKey directly. A
comment claiming a derivation cannot fail when the derivation drifts,
which is exactly how the one deleted last time survived.
A master key of the wrong length is now an error instead of silently no
key, and the resolver's encKey, computed and never read, is gone.
deps: cek v0.2.3 and namespace v1.2.0, plus tasks v1.52.4 -> v1.52.9,
which is what moves hanzoai/sqlite to v0.5.0 — tasks below v1.52.9 still
calls the wrapped-DEK API that v0.5.0 removed.
Co-authored-by: Hanzo Dev <[email protected]>
100 lines
2.8 KiB
Go
100 lines
2.8 KiB
Go
package vault
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"github.com/hanzoai/cek"
|
|
"github.com/hanzoai/namespace"
|
|
)
|
|
|
|
// TestVaultKey_IsCEKDerivation states the derivation as an assertion rather
|
|
// than as a comment. This plugin's package doc has claimed a key hierarchy in
|
|
// prose before while the code did something else; prose cannot go red.
|
|
func TestVaultKey_IsCEKDerivation(t *testing.T) {
|
|
master := testMasterKey()
|
|
const org, user = "did:lux:org:acme", "did:lux:user:alice"
|
|
|
|
ns, err := namespace.OrgProject(org, user)
|
|
if err != nil {
|
|
t.Fatalf("namespace.OrgProject: %v", err)
|
|
}
|
|
want, err := cek.DeriveKey(master, ns, userSubsystem)
|
|
if err != nil {
|
|
t.Fatalf("cek.DeriveKey: %v", err)
|
|
}
|
|
|
|
got, err := vaultKey(master, org, user, userSubsystem)
|
|
if err != nil {
|
|
t.Fatalf("vaultKey: %v", err)
|
|
}
|
|
if !bytes.Equal(got, want) {
|
|
t.Fatalf("user DEK is not the cek derivation:\n got %x\nwant %x", got, want)
|
|
}
|
|
if len(got) != cek.KeyLen {
|
|
t.Fatalf("expected a %d-byte key, got %d", cek.KeyLen, len(got))
|
|
}
|
|
}
|
|
|
|
// The org is in the namespace, which is what replaced the org KEK. Without it,
|
|
// two orgs that use the same user id would open each other's vaults.
|
|
func TestVaultKey_BoundToOrg(t *testing.T) {
|
|
master := testMasterKey()
|
|
const user = "did:lux:user:alice"
|
|
|
|
a, err := vaultKey(master, "did:lux:org:acme", user, userSubsystem)
|
|
if err != nil {
|
|
t.Fatalf("vaultKey: %v", err)
|
|
}
|
|
b, err := vaultKey(master, "did:lux:org:globex", user, userSubsystem)
|
|
if err != nil {
|
|
t.Fatalf("vaultKey: %v", err)
|
|
}
|
|
if bytes.Equal(a, b) {
|
|
t.Fatal("the same user id in two orgs must not derive the same key")
|
|
}
|
|
}
|
|
|
|
// A user vault and a shared vault of the same name are different files and must
|
|
// be different keys; the subsystem is what separates them.
|
|
func TestVaultKey_UserVsShared(t *testing.T) {
|
|
master := testMasterKey()
|
|
const org, name = "did:lux:org:acme", "widgets"
|
|
|
|
user, err := vaultKey(master, org, name, userSubsystem)
|
|
if err != nil {
|
|
t.Fatalf("vaultKey: %v", err)
|
|
}
|
|
shared, err := vaultKey(master, org, name, sharedSubsystem)
|
|
if err != nil {
|
|
t.Fatalf("vaultKey: %v", err)
|
|
}
|
|
if bytes.Equal(user, shared) {
|
|
t.Fatal("a user vault and a shared vault of the same name must not share a key")
|
|
}
|
|
}
|
|
|
|
// Sanitize is injective, and this is the property that matters for a key: two
|
|
// DIDs that differ only in punctuation must not fold onto one vault.
|
|
func TestVaultKey_DistinctIDsDistinctKeys(t *testing.T) {
|
|
master := testMasterKey()
|
|
const org = "did:lux:org:acme"
|
|
|
|
seen := map[string]string{}
|
|
for _, id := range []string{
|
|
"did:lux:user:alice",
|
|
"did-lux-user-alice",
|
|
"did:lux:user:Alice",
|
|
"did:lux:user:alice2",
|
|
} {
|
|
k, err := vaultKey(master, org, id, userSubsystem)
|
|
if err != nil {
|
|
t.Fatalf("vaultKey(%q): %v", id, err)
|
|
}
|
|
if prev, dup := seen[string(k)]; dup {
|
|
t.Fatalf("%q and %q derived the same key", prev, id)
|
|
}
|
|
seen[string(k)] = id
|
|
}
|
|
}
|