package client import ( "os" "strings" "testing" "lchat/internal/e2ee" ) // The cross-implementation vector: a JS-sealed envelope (fixed DEK) from // home/test/gen-home-vector.mjs — the same generator the Swift HomeCrypto // tests pin against. Opening it byte-for-byte proves the AES-GCM layout, // padding, and EHB1 framing match the browser's. func TestOpenHomeBlobJSVector(t *testing.T) { raw, err := os.ReadFile("testdata/home-vector.txt") if err != nil { t.Fatal(err) } envelope, err := e2ee.B64uDecode(strings.TrimSpace(string(raw))) if err != nil { t.Fatal(err) } dek, err := e2ee.B64uDecode("AAcOFRwjKjE4P0ZNVFtiaXB3foWMk5qhqK-2vcTL0tk") // gen-home-vector.mjs's fixed DEK if err != nil { t.Fatal(err) } v, err := OpenHomeBlob(envelope, dek) if err != nil { t.Fatalf("OpenHomeBlob: %v", err) } if v.V != 1 || len(v.Servers) != 1 || v.Servers[0].URL != "https://test.example.com" || v.Servers[0].Name != "Test" { t.Fatalf("vector contents: %+v", v) } } func TestSealOpenHomeBlobRoundTrip(t *testing.T) { dek, err := e2ee.GenerateKey() if err != nil { t.Fatal(err) } in := &Vault{V: 1, Servers: []VaultServer{ {URL: "https://a.11ch.at", Name: "Alpha", Starred: true, Credentials: &VaultCredentials{Token: "tokA", UserPub: "pubA", UserPriv: "privA", Name: "Paul"}}, {URL: "https://b.11ch.at", Name: "Bare save"}, // credential-less entry {URL: "https://c.11ch.at", Archived: true, Credentials: &VaultCredentials{Token: "tokC", UserPub: "pubC", UserPriv: "privC"}}, }} envelope, err := SealHomeBlob(in, dek, map[string]string{"cred-1": "wrapped-opaque"}) if err != nil { t.Fatal(err) } out, err := OpenHomeBlob(envelope, dek) if err != nil { t.Fatal(err) } if len(out.Servers) != 3 || out.Servers[0].Credentials.Token != "tokA" || !out.Servers[0].Starred || !out.Servers[2].Archived || out.Servers[1].Credentials != nil { t.Fatalf("round trip: %+v", out) } // Session projection: credentials become a live space session; the vault // name backfills when credentials carry none; bare entries yield nil. s := out.Servers[0].Session() if s == nil || s.Base != "https://a.11ch.at" || s.Token != "tokA" || s.Name != "Paul" || !s.LabelPublished { t.Fatalf("session projection: %+v", s) } if got := out.Servers[2].Session(); got == nil || got.Name != "" { t.Fatalf("nameless credentials: %+v", got) } if out.Servers[1].Session() != nil { t.Fatal("bare entry must project no session") } // Wrong key must not open. other, _ := e2ee.GenerateKey() if _, err := OpenHomeBlob(envelope, other); err == nil { t.Fatal("opened with the wrong DEK") } } // OpenHandoff is the CLI's end of restore.js's cli branch: the page seals the // home session to the ephemeral pubkey; only the matching private key + // matching nonce open it. func TestOpenHandoff(t *testing.T) { kp, err := e2ee.GenerateUserKeypair() if err != nil { t.Fatal(err) } sealed, err := e2ee.SealForPub(kp.PublicKey, HomeSession{V: 1, Token: "hometok", CredID: "cred-1", DEK: "dEk"}) if err != nil { t.Fatal(err) } back := e2ee.B64uEncode([]byte(`{"nonce":"n-123","wrapped":"` + sealed + `"}`)) hs, err := OpenHandoff(back, "n-123", kp.PrivateKey) if err != nil { t.Fatalf("OpenHandoff: %v", err) } if hs.Token != "hometok" || hs.CredID != "cred-1" || hs.DEK != "dEk" { t.Fatalf("session: %+v", hs) } if _, err := OpenHandoff(back, "other-nonce", kp.PrivateKey); err == nil { t.Fatal("nonce mismatch accepted") } otherKP, _ := e2ee.GenerateUserKeypair() if _, err := OpenHandoff(back, "n-123", otherKP.PrivateKey); err == nil { t.Fatal("wrong ephemeral key accepted") } if _, err := OpenHandoff("not base64 json", "n", kp.PrivateKey); err == nil { t.Fatal("garbage accepted") } }