package token import ( "crypto/ed25519" "encoding/json" "strings" "testing" ) func complete(t *testing.T) Token { t.Helper() public, _, err := ed25519.GenerateKey(nil) if err != nil { t.Fatal(err) } return Token{ Broker: "192.0.2.10:5671", Fingerprint: "sha256:" + strings.Repeat("ab", 32), Signer: public, Secret: "a-one-time-secret", } } func TestATokenSurvivesBeingCarried(t *testing.T) { // It is copied by hand out of a terminal and into a machine. Whatever comes back must be // exactly what went in, including the key — a signing key that changed in transit is a node // that refuses every declaration it is later sent. original := complete(t) encoded, err := original.Encode() if err != nil { t.Fatal(err) } if strings.ContainsAny(encoded, " \n\t") { t.Error("the encoded token contains whitespace; it is copied by hand as one line") } back, err := Decode(encoded) if err != nil { t.Fatal(err) } if back.Broker != original.Broker || back.Fingerprint != original.Fingerprint || back.Secret != original.Secret || string(back.Signer) != string(original.Signer) { t.Errorf("the token changed in transit:\n sent %+v\n got %+v", original, back) } } func TestSurroundingWhitespaceIsTolerated(t *testing.T) { // It arrives pasted. A trailing newline is not a corrupted token, and refusing one would // send somebody hunting for a fault that is not there. encoded, err := complete(t).Encode() if err != nil { t.Fatal(err) } if _, err := Decode(" " + encoded + "\n"); err != nil { t.Errorf("a pasted token was refused: %v", err) } } func TestGarbageIsRefusedAsNotBeingAToken(t *testing.T) { for _, bad := range []string{"", "not-base64-!!!", "aGVsbG8"} { if _, err := Decode(bad); err == nil { t.Errorf("%q was accepted as a token", bad) } } } func TestATokenFromAnotherVersionIsRefusedClearly(t *testing.T) { // The host must tell "this is not from the mesh I joined" apart from "this is malformed" // (novox/hq ADR 0004). A version it does not understand is the first case. future := complete(t) encoded, err := future.Encode() if err != nil { t.Fatal(err) } // Re-encode by hand at a version this build does not know. raw := strings.Replace(string(mustDecodeBase64(t, encoded)), `"v":1`, `"v":99`, 1) if _, err := Decode(encodeBase64(raw)); err == nil { t.Fatal("a token from an unknown version was accepted") } } func TestEveryMissingPartIsNamed(t *testing.T) { // "This token cannot be used" is not something anybody can act on. "It has no broker // address" is. And all of them at once, not the first: fixing one at a time turns a single // decision into four. empty := Token{} missing := empty.Missing() if len(missing) != 4 { t.Fatalf("an empty token named %d missing parts, expected 4: %v", len(missing), missing) } if empty.Complete() { t.Error("an empty token reported itself complete") } } func TestAShortSigningKeyIsNotASigningKey(t *testing.T) { // The one that would pass a nil check and fail at the moment a declaration is verified — // which is on a node, in production, long after this. t1 := complete(t) t1.Signer = []byte("too short") if t1.Complete() { t.Error("a truncated signing key was accepted as present") } } func TestACompleteTokenIsComplete(t *testing.T) { if got := complete(t); !got.Complete() { t.Errorf("a token with all four parts reported missing: %v", got.Missing()) } } func mustDecodeBase64(t *testing.T, s string) []byte { t.Helper() raw, err := base64Decode(s) if err != nil { t.Fatal(err) } return raw } func TestTheWireFormatIsExactlyTheseFieldNames(t *testing.T) { // The contract with the host, which defines this format separately because it requires // nothing present and does not import this (novox/hq ADR 0005). There is a matching test on // that side. Rename a field on either and both fail — the alternative is a rename that only // shows up at enrolment, on a real machine. raw, err := json.Marshal(complete(t)) if err != nil { t.Fatal(err) } var fields map[string]any if err := json.Unmarshal(raw, &fields); err != nil { t.Fatal(err) } for _, want := range []string{"v", "broker", "fingerprint", "signer", "secret"} { if _, ok := fields[want]; !ok { t.Errorf("the token has no %q field; the host reads that name", want) } } if len(fields) != 5 { t.Errorf("the token has %d fields, expected 5: %v", len(fields), fields) } }