oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1package home
2
3import (
4 "context"
5 "encoding/base64"
6 "encoding/hex"
7 "os"
8 "strings"
9 "testing"
10
11 family "amadan.net/rastrillo/rastrillo/crypto"
12 "amadan.net/rastrillo/rastrillo/migrate"
13)
14
15func rosterPrivate(t *testing.T) (*family.Keypair, string) {
16 t.Helper()
17 k, e := family.Generate()
18 if e != nil {
19 t.Fatal(e)
20 }
21 b, e := family.MarshalKeypair(k)
22 if e != nil {
23 t.Fatal(e)
24 }
25 return k, base64.StdEncoding.EncodeToString(b)
26}
27func TestRosterRuntimeModeKeyAndRoleMatrix(t *testing.T) {
28 credential, keys := runtimeEnv(t)
29 _, private := rosterPrivate(t)
30 t.Setenv("HOME_ROLE", "directory")
31 t.Setenv("HOME_ASSERTION_KEYS", keys)
32 cfg, e := LoadRuntime("home.db", quietLogger())
33 if e != nil || cfg.roster.mode != "disabled" || cfg.roster.signer != nil {
34 t.Fatal("default roster not disabled", e)
35 }
36 for _, tc := range []struct {
37 mode, key string
38 ok bool
39 }{{"disabled", "", false}, {"serve", "", false}, {"serve", private, true}, {"serve", credential, false}, {"typo", private, false}, {"", private, false}} {
40 t.Setenv("HOME_ROSTER_MODE", tc.mode)
41 t.Setenv("HOME_ROSTER_KEY", tc.key)
42 cfg, e = LoadRuntime("home.db", quietLogger())
43 if (e == nil) != tc.ok {
44 t.Fatal(tc.mode, "key matrix", e)
45 }
46 if e == nil && (cfg.signer != nil || !cfg.roster.valid("directory", cfg.keys)) {
47 t.Fatal("role/key boundary")
48 }
49 }
50 os.Unsetenv("HOME_ROSTER_KEY")
51 t.Setenv("HOME_ROSTER_MODE", "disabled")
52 if _, e = LoadRuntime("home.db", quietLogger()); e != nil {
53 t.Fatal(e)
54 }
55 for _, role := range []string{"", "credential"} {
56 t.Setenv("HOME_ROLE", role)
57 if _, e = LoadRuntime("home.db", quietLogger()); e != errExchangeConfig {
58 t.Fatal("roster config crossed role", role, e)
59 }
60 }
61}
62func TestRosterRuntimeStrictPrivateEncoding(t *testing.T) {
63 _, keys := runtimeEnv(t)
64 _, private := rosterPrivate(t)
65 t.Setenv("HOME_ROLE", "directory")
66 t.Setenv("HOME_ASSERTION_KEYS", keys)
67 t.Setenv("HOME_ROSTER_MODE", "serve")
68 raw, _ := base64.StdEncoding.DecodeString(private)
69 invalid := []string{"", "not-base64", private + "\n", strings.Repeat("A", 4097)}
70 for _, s := range []string{strings.TrimSuffix(string(raw), "}") + `,"unknown":1}`, strings.TrimSuffix(string(raw), "}") + `,"sign_priv_d":"` + strings.Repeat("1", 64) + `"}`, `{"sign_priv_d":"` + strings.Repeat("0", 64) + `","box_priv_d":"` + strings.Repeat("1", 64) + `"}`, `{"sign_priv_d":"` + strings.Repeat("f", 64) + `","box_priv_d":"` + strings.Repeat("1", 64) + `"}`, `{"sign_priv_d":"` + strings.Repeat("A", 64) + `","box_priv_d":"` + strings.Repeat("1", 64) + `"}`, `{"sign_priv_d":"01","box_priv_d":"01"}`} {
71 invalid = append(invalid, base64.StdEncoding.EncodeToString([]byte(s)))
72 }
73 for _, bad := range invalid {
74 t.Setenv("HOME_ROSTER_KEY", bad)
75 if _, e := LoadRuntime("home.db", quietLogger()); e != errExchangeConfig {
76 t.Fatal("bad private encoding accepted", e)
77 }
78 }
79}
80func TestRosterDirectConstructorsAndEmptySchema(t *testing.T) {
81 p := newExchangePair(t)
82 base := *p.directory.runtime
83 k, _ := rosterPrivate(t)
84 for _, r := range []rosterRuntime{{mode: "serve"}, {mode: "disabled", signer: k}, {mode: "serve", signer: k, keyID: "wrong"}, {mode: "typo"}} {
85 cfg := base
86 cfg.roster = r
87 if _, e := NewRuntime(cfg, p.directory.db, quietLogger()); e != errExchangeConfig {
88 t.Fatal("unchecked runtime", e)
89 }
90 if _, e := newDirectory(cfg, p.directory.db, quietLogger()); e != errExchangeConfig {
91 t.Fatal("unchecked directory", e)
92 }
93 }
94 cfg := base
95 cfg.roster = rosterRuntime{mode: "serve", signer: k, keyID: rosterKeyID(k)}
96 if _, e := NewRuntime(cfg, p.directory.db, quietLogger()); e != nil {
97 t.Fatal(e)
98 }
99 cfg.keys = map[string][]byte{hex.EncodeToString([]byte("key")): k.SignPub()}
100 if _, e := NewRuntime(cfg, p.directory.db, quietLogger()); e != errExchangeConfig {
101 t.Fatal("credential key reuse", e)
102 }
103 cfg = *p.credential.runtime
104 cfg.roster = rosterRuntime{mode: "serve", signer: k, keyID: rosterKeyID(k)}
105 if _, e := NewRuntime(cfg, p.credential.db, quietLogger()); e != errExchangeConfig {
106 t.Fatal("private roster crossed role", e)
107 }
108 for i := 0; i < 2; i++ {
109 if _, e := migrate.Apply(context.Background(), p.directory.db, directorySchema); e != nil {
110 t.Fatal(e)
111 }
112 if _, e := NewRuntime(base, p.directory.db, quietLogger()); e != nil {
113 t.Fatal(e)
114 }
115 }
116 for _, table := range []string{"roster_generation", "roster_consumers", "roster_tokens", "roster_operator_receipts"} {
117 var n int
118 if e := p.directory.db.Writer().QueryRow(`SELECT count(*) FROM ` + table).Scan(&n); e != nil || n != 0 {
119 t.Fatal("authority initialized", table, n, e)
120 }
121 }
122 for _, app := range []*App{p.credential.App, newTestApp(t).App} {
123 var n int
124 if e := app.db.Writer().QueryRow(`SELECT count(*) FROM sqlite_master WHERE name LIKE 'roster_%' OR name LIKE 'memberships_epoch_%'`).Scan(&n); e != nil || n != 0 {
125 t.Fatal("roster schema crossed role", n, e)
126 }
127 }
128}
129