| 1 | package instancekey |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | ) |
| 7 | |
| 8 | // Finding 4: an empty own value never shadows a configured key; a |
| 9 | // non-empty own value always wins. |
| 10 | func TestEffectiveNeverHandsOutAnEmptyKeyWhileOneExists(t *testing.T) { |
| 11 | for _, tc := range []struct { |
| 12 | name string |
| 13 | config, own string |
| 14 | want string |
| 15 | layer Layer |
| 16 | }{ |
| 17 | {"own wins", Var + "=cfg\n", Var + "=own\n", "own", LayerOwn}, |
| 18 | {"empty own, configured", Var + "=cfg\n", Var + "=\n", "cfg", LayerConfig}, |
| 19 | {"no own, configured", Var + "=cfg\n", "A=1\n", "cfg", LayerConfig}, |
| 20 | {"own only", "", Var + "=own\n", "own", LayerOwn}, |
| 21 | {"both empty", Var + "=\n", Var + "=\n", "", LayerNone}, |
| 22 | {"exported is not the key", "", "export " + Var + "=own\n", "", LayerNone}, |
| 23 | } { |
| 24 | t.Run(tc.name, func(t *testing.T) { |
| 25 | got, layer := Effective(ParseEnv([]byte(tc.config)), ParseEnv([]byte(tc.own))) |
| 26 | if got != tc.want || layer != tc.layer { |
| 27 | t.Fatalf("Effective = %q, %v; want %q, %v", got, layer, tc.want, tc.layer) |
| 28 | } |
| 29 | }) |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | // Ruling 20 (R-B): a record binds one writer. Another session, another |
| 34 | // generation, another installation or another host never matches, so a |
| 35 | // later release that picks its own key, a rollback and a reinstall never |
| 36 | // inherit an earlier writer's adoption. |
| 37 | func TestAdoptionRecordBindsOneWriter(t *testing.T) { |
| 38 | key := strings.Repeat("A", 43) + "=" |
| 39 | w := Binding{Host: "shop.example.com", Incarnation: "inc-1", Session: 3, Generation: "g3"} |
| 40 | rec := AdoptionRecord(w, SourcePlatform, Digest(key)) |
| 41 | if strings.Contains(string(rec), key) { |
| 42 | t.Fatal("the record holds the key itself") |
| 43 | } |
| 44 | a, err := ParseAdoption(rec) |
| 45 | if err != nil || !a.Binds(w) || a.Source != SourcePlatform || a.SHA256 != Digest(key) { |
| 46 | t.Fatalf("ParseAdoption = %+v, %v", a, err) |
| 47 | } |
| 48 | for name, other := range map[string]Binding{ |
| 49 | "an earlier session (an earlier release)": {Host: w.Host, Incarnation: w.Incarnation, Session: 2, Generation: "g2"}, |
| 50 | "a later session (a rollback)": {Host: w.Host, Incarnation: w.Incarnation, Session: 4, Generation: "g4"}, |
| 51 | "the same session, another generation": {Host: w.Host, Incarnation: w.Incarnation, Session: 3, Generation: "g3b"}, |
| 52 | "another installation (a reinstall)": {Host: w.Host, Incarnation: "inc-2", Session: 3, Generation: "g3"}, |
| 53 | "another host": {Host: "other.example.com", Incarnation: w.Incarnation, Session: 3, Generation: "g3"}, |
| 54 | "no writer": {}, |
| 55 | } { |
| 56 | if a.Binds(other) { |
| 57 | t.Errorf("%s: the record matched", name) |
| 58 | } |
| 59 | } |
| 60 | for name, raw := range map[string]string{ |
| 61 | "version 1": `{"v":1,"var":"CARLOS_INSTANCE_KEY","sha256":"` + Digest(key) + `"}`, |
| 62 | "no binding": `{"v":2,"var":"CARLOS_INSTANCE_KEY","source":"platform","sha256":"` + Digest(key) + `"}`, |
| 63 | "unknown source": `{"v":2,"var":"CARLOS_INSTANCE_KEY","host":"h","incarnation":"i","session":1,"generation":"g","source":"own","sha256":"` + Digest(key) + `"}`, |
| 64 | "an unknown field": `{"v":2,"var":"CARLOS_INSTANCE_KEY","host":"h","incarnation":"i","session":1,"generation":"g","source":"config","sha256":"` + Digest(key) + `","key":"x"}`, |
| 65 | "trailing data": string(rec) + `{}`, |
| 66 | "garbage": "not json", |
| 67 | // The canonical bytes are the only record (review minor 1): |
| 68 | // encoding/json alone accepts each of these. |
| 69 | "a trailing }": string(rec) + `}`, |
| 70 | "a trailing ]": string(rec) + `]`, |
| 71 | "an upper-case key": strings.Replace(string(rec), `"sha256"`, `"SHA256"`, 1), |
| 72 | "a key given twice": strings.Replace(string(rec), `"source":"platform"`, `"source":"config","source":"platform"`, 1), |
| 73 | "reordered fields": strings.Replace(string(rec), `{"v":2,"var":"CARLOS_INSTANCE_KEY",`, `{"var":"CARLOS_INSTANCE_KEY","v":2,`, 1), |
| 74 | "an upper-case digest": strings.Replace(string(rec), Digest(key), strings.ToUpper(Digest(key)), 1), |
| 75 | "a short digest": strings.Replace(string(rec), Digest(key), Digest(key)[:63], 1), |
| 76 | "another variable": strings.Replace(string(rec), `"var":"CARLOS_INSTANCE_KEY"`, `"var":"OTHER"`, 1), |
| 77 | } { |
| 78 | if raw == string(rec) { |
| 79 | t.Fatalf("%s: the case did not change the record", name) |
| 80 | } |
| 81 | if _, err := ParseAdoption([]byte(raw)); err == nil { |
| 82 | t.Errorf("%s: parsed", name) |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | // ParseEnv is exactly the activator's old parseEnvBytes, so what a reader |
| 88 | // parses is what a child received: quotes kept, whole-line comments only, |
| 89 | // the last duplicate winning, CRLF and surrounding whitespace trimmed. |
| 90 | func TestParseEnvMatchesWhatAChildReceives(t *testing.T) { |
| 91 | got := ParseEnv([]byte("A=\"quoted\"\nB='single'\n# C=1\n # D=1\nE=1 # kept\nF=1\nF=2\n G = spaced \nH=a=b\nI=1\r\nnoequals\n\n")) |
| 92 | want := map[string]string{"A": `"quoted"`, "B": "'single'", "E": "1 # kept", "F": "2", "G": "spaced", "H": "a=b", "I": "1"} |
| 93 | if len(got) != len(want) { |
| 94 | t.Fatalf("ParseEnv = %q, want %q", got, want) |
| 95 | } |
| 96 | for k, v := range want { |
| 97 | if got[k] != v { |
| 98 | t.Fatalf("ParseEnv = %q, want %q", got, want) |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | // O3': the install refuses env files that would deliver any key but the |
| 104 | // pinned one, including a configuration changed since the pin. |
| 105 | func TestVerifyDeliveredRefusesAnyOtherKey(t *testing.T) { |
| 106 | a, b := strings.Repeat("A", 43)+"=", strings.Repeat("B", 43)+"=" |
| 107 | for _, tc := range []struct { |
| 108 | name, config, own string |
| 109 | ok bool |
| 110 | }{ |
| 111 | {"the configured key, unchanged", Var + "=" + a + "\n", "T=1\n", true}, |
| 112 | {"the configuration changed since the pin", Var + "=" + b + "\n", "T=1\n", false}, |
| 113 | {"the configured key removed", "", "T=1\n", false}, |
| 114 | {"an empty override over the configured key", Var + "=" + a + "\n", Var + "=\n", true}, |
| 115 | {"the platform key, restored", "", Var + "=" + a + "\n", true}, |
| 116 | {"a platform key over the pinned configured one", Var + "=" + a + "\n", Var + "=" + b + "\n", false}, |
| 117 | } { |
| 118 | if err := VerifyDelivered([]byte(tc.config), []byte(tc.own), Digest(a)); (err == nil) != tc.ok { |
| 119 | t.Errorf("%s: err = %v", tc.name, err) |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | // Ruling 26: the pre-roll check's verified home keys, as the box reads |
| 125 | // them. A list that may be wrong verifies nothing, and its errors never |
| 126 | // echo an entry, which may hold a digest (Astra round 5 finding 3). |
| 127 | func TestParseHomeKeys(t *testing.T) { |
| 128 | a, b := Digest("home-key-a"), Digest("home-key-b") |
| 129 | got, err := ParseHomeKeys(" shop-au.example.com=" + a + ", shop-uk.example.com=" + b + ",") |
| 130 | if err != nil || len(got) != 2 || got["shop-au.example.com"] != a || got["shop-uk.example.com"] != b { |
| 131 | t.Fatalf("ParseHomeKeys = %v, %v", got, err) |
| 132 | } |
| 133 | if got, err := ParseHomeKeys(""); err != nil || len(got) != 0 { |
| 134 | t.Fatalf("empty = %v, %v", got, err) |
| 135 | } |
| 136 | for _, tc := range []struct{ value, want string }{ |
| 137 | {a, "entry 1: no \"replica-host=\""}, |
| 138 | {"shop-au.example.com=" + a + "," + b, "entry 2: no \"replica-host=\""}, |
| 139 | {"shop-au.example.com=" + a[:40], "entry 1: the digest is not"}, |
| 140 | {"=" + a, "entry 1: the host is empty"}, |
| 141 | {a[:20] + "=" + a, "entry 1: the host is empty or not a host name"}, |
| 142 | {"shop-au.example.com=" + a + ",shop-au.example.com=" + b, "entry 2: its host is listed"}, |
| 143 | } { |
| 144 | _, err := ParseHomeKeys(tc.value) |
| 145 | if err == nil || !strings.Contains(err.Error(), tc.want) { |
| 146 | t.Errorf("%q: err = %v, want %q", tc.value, err, tc.want) |
| 147 | continue |
| 148 | } |
| 149 | if strings.Contains(err.Error(), a[:16]) || strings.Contains(err.Error(), b[:16]) || strings.Contains(err.Error(), "shop-") { |
| 150 | t.Errorf("the error echoes its input: %v", err) |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | |