package auth import ( "strings" "testing" ) func TestMintParseRoundTrip(t *testing.T) { seen := map[string]bool{} for i := 0; i < 100; i++ { token, keyID, hash, err := Mint() if err != nil { t.Fatalf("Mint: %v", err) } if seen[keyID] { t.Fatalf("Mint reused key id %q", keyID) } seen[keyID] = true if !strings.HasPrefix(token, Prefix+"_") { t.Errorf("token %q lacks the %q prefix", token, Prefix) } gotID, secret, err := Parse(token) if err != nil { t.Fatalf("Parse(%q): %v", token, err) } if gotID != keyID { t.Errorf("Parse key id = %q, want %q", gotID, keyID) } if len(gotID) != KeyIDLen { t.Errorf("key id length = %d, want %d", len(gotID), KeyIDLen) } if !SecretMatches(secret, hash[:]) { t.Error("minted secret does not match its own hash") } if strings.Contains(token, keyID+"_"+keyID) { t.Error("secret must not repeat the key id") } if !ValidKeyID(keyID) { t.Errorf("ValidKeyID rejected a minted id %q", keyID) } } } func TestParseRejectsMalformed(t *testing.T) { good, keyID, _, err := Mint() if err != nil { t.Fatal(err) } _, secret, err := Parse(good) if err != nil { t.Fatal(err) } cases := []struct { name string token string }{ {"empty", ""}, {"no prefix", keyID + "_" + secret}, {"wrong prefix", "xyz_" + keyID + "_" + secret}, {"prefix only", "pgs_"}, {"no separator", "pgs_" + keyID + secret}, {"short key id", "pgs_" + keyID[:15] + "_" + secret}, {"long key id", "pgs_" + keyID + "a_" + secret}, {"short secret", "pgs_" + keyID + "_" + secret[:42]}, {"long secret", "pgs_" + keyID + "_" + secret + "a"}, {"uppercase key id", "pgs_" + strings.ToUpper(keyID) + "_" + secret}, {"key id with 0 (not in base32)", "pgs_0" + keyID[1:] + "_" + secret}, {"key id with 1 (not in base32)", "pgs_1" + keyID[1:] + "_" + secret}, {"secret with padding", "pgs_" + keyID + "_" + secret[:42] + "="}, {"secret with slash", "pgs_" + keyID + "_" + secret[:42] + "/"}, {"secret with plus", "pgs_" + keyID + "_" + secret[:42] + "+"}, {"embedded NUL", "pgs_" + keyID + "_" + secret[:42] + "\x00"}, {"leading space", " " + good}, {"newline", good + "\n"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if _, _, err := Parse(tc.token); err == nil { t.Errorf("Parse(%q) accepted a malformed token", tc.token) } }) } // The parse error must never quote the input: these errors reach logs. if _, _, err := Parse(good[:len(good)-1] + "x"); err != nil { if strings.Contains(err.Error(), secret[:20]) { t.Error("parse error leaks part of the presented secret") } } } func TestSecretMatches(t *testing.T) { hash := HashSecret("correct horse battery staple") if !SecretMatches("correct horse battery staple", hash[:]) { t.Error("matching secret rejected") } if SecretMatches("correct horse battery stapl", hash[:]) { t.Error("truncated secret accepted") } if SecretMatches("", hash[:]) { t.Error("empty secret accepted") } if SecretMatches("correct horse battery staple", nil) { t.Error("nil stored hash accepted") } if SecretMatches("correct horse battery staple", hash[:16]) { t.Error("truncated stored hash accepted") } } func TestValidKeyID(t *testing.T) { cases := []struct { in string want bool }{ {"abcdefghijklmnop", true}, {"234567234567abcd", true}, {"", false}, {"abcdefghijklmno", false}, // 15 {"abcdefghijklmnopq", false}, // 17 {"ABCDEFGHIJKLMNOP", false}, {"abcdefghijklmno0", false}, {"abcdefghijklmno1", false}, {"abcdefghijklmno8", false}, {"abcdefghijklmno-", false}, {"abcdefghijklmn/p", false}, } for _, tc := range cases { if got := ValidKeyID(tc.in); got != tc.want { t.Errorf("ValidKeyID(%q) = %v, want %v", tc.in, got, tc.want) } } }