package instancekey import ( "strings" "testing" ) // Finding 4: an empty own value never shadows a configured key; a // non-empty own value always wins. func TestEffectiveNeverHandsOutAnEmptyKeyWhileOneExists(t *testing.T) { for _, tc := range []struct { name string config, own string want string layer Layer }{ {"own wins", Var + "=cfg\n", Var + "=own\n", "own", LayerOwn}, {"empty own, configured", Var + "=cfg\n", Var + "=\n", "cfg", LayerConfig}, {"no own, configured", Var + "=cfg\n", "A=1\n", "cfg", LayerConfig}, {"own only", "", Var + "=own\n", "own", LayerOwn}, {"both empty", Var + "=\n", Var + "=\n", "", LayerNone}, {"exported is not the key", "", "export " + Var + "=own\n", "", LayerNone}, } { t.Run(tc.name, func(t *testing.T) { got, layer := Effective(ParseEnv([]byte(tc.config)), ParseEnv([]byte(tc.own))) if got != tc.want || layer != tc.layer { t.Fatalf("Effective = %q, %v; want %q, %v", got, layer, tc.want, tc.layer) } }) } } // Ruling 20 (R-B): a record binds one writer. Another session, another // generation, another installation or another host never matches, so a // later release that picks its own key, a rollback and a reinstall never // inherit an earlier writer's adoption. func TestAdoptionRecordBindsOneWriter(t *testing.T) { key := strings.Repeat("A", 43) + "=" w := Binding{Host: "shop.example.com", Incarnation: "inc-1", Session: 3, Generation: "g3"} rec := AdoptionRecord(w, SourcePlatform, Digest(key)) if strings.Contains(string(rec), key) { t.Fatal("the record holds the key itself") } a, err := ParseAdoption(rec) if err != nil || !a.Binds(w) || a.Source != SourcePlatform || a.SHA256 != Digest(key) { t.Fatalf("ParseAdoption = %+v, %v", a, err) } for name, other := range map[string]Binding{ "an earlier session (an earlier release)": {Host: w.Host, Incarnation: w.Incarnation, Session: 2, Generation: "g2"}, "a later session (a rollback)": {Host: w.Host, Incarnation: w.Incarnation, Session: 4, Generation: "g4"}, "the same session, another generation": {Host: w.Host, Incarnation: w.Incarnation, Session: 3, Generation: "g3b"}, "another installation (a reinstall)": {Host: w.Host, Incarnation: "inc-2", Session: 3, Generation: "g3"}, "another host": {Host: "other.example.com", Incarnation: w.Incarnation, Session: 3, Generation: "g3"}, "no writer": {}, } { if a.Binds(other) { t.Errorf("%s: the record matched", name) } } for name, raw := range map[string]string{ "version 1": `{"v":1,"var":"CARLOS_INSTANCE_KEY","sha256":"` + Digest(key) + `"}`, "no binding": `{"v":2,"var":"CARLOS_INSTANCE_KEY","source":"platform","sha256":"` + Digest(key) + `"}`, "unknown source": `{"v":2,"var":"CARLOS_INSTANCE_KEY","host":"h","incarnation":"i","session":1,"generation":"g","source":"own","sha256":"` + Digest(key) + `"}`, "an unknown field": `{"v":2,"var":"CARLOS_INSTANCE_KEY","host":"h","incarnation":"i","session":1,"generation":"g","source":"config","sha256":"` + Digest(key) + `","key":"x"}`, "trailing data": string(rec) + `{}`, "garbage": "not json", // The canonical bytes are the only record (review minor 1): // encoding/json alone accepts each of these. "a trailing }": string(rec) + `}`, "a trailing ]": string(rec) + `]`, "an upper-case key": strings.Replace(string(rec), `"sha256"`, `"SHA256"`, 1), "a key given twice": strings.Replace(string(rec), `"source":"platform"`, `"source":"config","source":"platform"`, 1), "reordered fields": strings.Replace(string(rec), `{"v":2,"var":"CARLOS_INSTANCE_KEY",`, `{"var":"CARLOS_INSTANCE_KEY","v":2,`, 1), "an upper-case digest": strings.Replace(string(rec), Digest(key), strings.ToUpper(Digest(key)), 1), "a short digest": strings.Replace(string(rec), Digest(key), Digest(key)[:63], 1), "another variable": strings.Replace(string(rec), `"var":"CARLOS_INSTANCE_KEY"`, `"var":"OTHER"`, 1), } { if raw == string(rec) { t.Fatalf("%s: the case did not change the record", name) } if _, err := ParseAdoption([]byte(raw)); err == nil { t.Errorf("%s: parsed", name) } } } // ParseEnv is exactly the activator's old parseEnvBytes, so what a reader // parses is what a child received: quotes kept, whole-line comments only, // the last duplicate winning, CRLF and surrounding whitespace trimmed. func TestParseEnvMatchesWhatAChildReceives(t *testing.T) { 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")) want := map[string]string{"A": `"quoted"`, "B": "'single'", "E": "1 # kept", "F": "2", "G": "spaced", "H": "a=b", "I": "1"} if len(got) != len(want) { t.Fatalf("ParseEnv = %q, want %q", got, want) } for k, v := range want { if got[k] != v { t.Fatalf("ParseEnv = %q, want %q", got, want) } } } // O3': the install refuses env files that would deliver any key but the // pinned one, including a configuration changed since the pin. func TestVerifyDeliveredRefusesAnyOtherKey(t *testing.T) { a, b := strings.Repeat("A", 43)+"=", strings.Repeat("B", 43)+"=" for _, tc := range []struct { name, config, own string ok bool }{ {"the configured key, unchanged", Var + "=" + a + "\n", "T=1\n", true}, {"the configuration changed since the pin", Var + "=" + b + "\n", "T=1\n", false}, {"the configured key removed", "", "T=1\n", false}, {"an empty override over the configured key", Var + "=" + a + "\n", Var + "=\n", true}, {"the platform key, restored", "", Var + "=" + a + "\n", true}, {"a platform key over the pinned configured one", Var + "=" + a + "\n", Var + "=" + b + "\n", false}, } { if err := VerifyDelivered([]byte(tc.config), []byte(tc.own), Digest(a)); (err == nil) != tc.ok { t.Errorf("%s: err = %v", tc.name, err) } } } // Ruling 26: the pre-roll check's verified home keys, as the box reads // them. A list that may be wrong verifies nothing, and its errors never // echo an entry, which may hold a digest (Astra round 5 finding 3). func TestParseHomeKeys(t *testing.T) { a, b := Digest("home-key-a"), Digest("home-key-b") got, err := ParseHomeKeys(" shop-au.example.com=" + a + ", shop-uk.example.com=" + b + ",") if err != nil || len(got) != 2 || got["shop-au.example.com"] != a || got["shop-uk.example.com"] != b { t.Fatalf("ParseHomeKeys = %v, %v", got, err) } if got, err := ParseHomeKeys(""); err != nil || len(got) != 0 { t.Fatalf("empty = %v, %v", got, err) } for _, tc := range []struct{ value, want string }{ {a, "entry 1: no \"replica-host=\""}, {"shop-au.example.com=" + a + "," + b, "entry 2: no \"replica-host=\""}, {"shop-au.example.com=" + a[:40], "entry 1: the digest is not"}, {"=" + a, "entry 1: the host is empty"}, {a[:20] + "=" + a, "entry 1: the host is empty or not a host name"}, {"shop-au.example.com=" + a + ",shop-au.example.com=" + b, "entry 2: its host is listed"}, } { _, err := ParseHomeKeys(tc.value) if err == nil || !strings.Contains(err.Error(), tc.want) { t.Errorf("%q: err = %v, want %q", tc.value, err, tc.want) continue } if strings.Contains(err.Error(), a[:16]) || strings.Contains(err.Error(), b[:16]) || strings.Contains(err.Error(), "shop-") { t.Errorf("the error echoes its input: %v", err) } } }