package credential import ( "bytes" "crypto/sha256" "encoding/base64" "strings" "testing" "github.com/aritmolab/thothii/tools/dwh-auth/internal/record" ) func TestGenerateProducesCanonicalV1Material(t *testing.T) { random := bytes.NewReader(bytes.Repeat([]byte{0x7f}, 44)) material, err := Generate(random) if err != nil { t.Fatalf("Generate() error = %v", err) } wantValue := strings.Join([]string{ Prefix, "f39_f39_f39_f39_", "f39_f39_f39_f39_f39_f39_f39_f39_f39_f39_f38", }, ".") if got, want := string(material.Value), wantValue; got != want { t.Fatalf("Value = %q, want %q", got, want) } if got, want := len(material.Value), 70; got != want { t.Fatalf("len(Value) = %d, want %d", got, want) } if got, want := material.KeyID, "f39_f39_f39_f39_"; got != want { t.Fatalf("KeyID = %q, want %q", got, want) } segments := strings.Split(string(material.Value), ".") if got, want := len(segments), 3; got != want { t.Fatalf("segment count = %d, want %d", got, want) } if got, want := segments[0], Prefix; got != want { t.Fatalf("prefix = %q, want %q", got, want) } if got, want := len(segments[1]), KeyIDEncodedLength; got != want { t.Fatalf("key ID length = %d, want %d", got, want) } if got, want := len(segments[2]), SecretEncodedLength; got != want { t.Fatalf("secret length = %d, want %d", got, want) } secret, err := base64.RawURLEncoding.DecodeString(segments[2]) if err != nil { t.Fatalf("DecodeString(secret) error = %v", err) } if got, want := len(secret), 32; got != want { t.Fatalf("decoded secret length = %d, want %d", got, want) } wantDigest := sha256.Sum256(material.Value) if material.Digest != record.Digest(wantDigest) { t.Fatalf("Digest = %x, want %x", material.Digest, wantDigest) } } func TestVerifyV1AcceptsGeneratedMaterialAndRejectsChangedByte(t *testing.T) { material, err := Generate(bytes.NewReader(bytes.Repeat([]byte{0x01}, 44))) if err != nil { t.Fatalf("Generate() error = %v", err) } if keyID, ok := VerifyV1(material.Value, material.Digest); !ok || keyID != material.KeyID { t.Fatalf("VerifyV1() = (%q, %t), want (%q, true)", keyID, ok, material.KeyID) } changed := append([]byte(nil), material.Value...) changed[len(changed)-1] ^= 1 if keyID, ok := VerifyV1(changed, material.Digest); ok || keyID != "" { t.Fatalf("VerifyV1(changed) = (%q, %t), want (\"\", false)", keyID, ok) } } func TestVerifyV1RejectsNonCanonicalValues(t *testing.T) { material, err := Generate(bytes.NewReader(bytes.Repeat([]byte{0x02}, 44))) if err != nil { t.Fatalf("Generate() error = %v", err) } for _, value := range [][]byte{ append(append([]byte(nil), material.Value...), ','), []byte(string(material.Value) + "," + string(material.Value)), []byte(string(material.Value) + "." + material.KeyID), []byte(string(material.Value) + "="), []byte(" " + string(material.Value)), bytes.Repeat([]byte{'a'}, MaxHeaderBytes+1), } { if keyID, ok := VerifyV1(value, material.Digest); ok || keyID != "" { t.Fatalf("VerifyV1(%q) = (%q, %t), want (\"\", false)", value, keyID, ok) } } } func TestVerifyLegacyHashesOpaqueRawValueWithoutV1Parsing(t *testing.T) { value := []byte("legacy opaque value /:[]") sum := sha256.Sum256(value) if !VerifyLegacy(value, record.Digest(sum)) { t.Fatal("VerifyLegacy() = false, want true") } if VerifyLegacy([]byte("thtdwh_v1.not-a-valid-v1"), record.Digest(sum)) { t.Fatal("VerifyLegacy() accepted a different raw value") } } func TestVerifyLegacyRejectsInvalidOpaqueValues(t *testing.T) { sum := sha256.Sum256([]byte("legacy")) for _, value := range [][]byte{ nil, bytes.Repeat([]byte{'a'}, MaxHeaderBytes+1), []byte("bad\nvalue"), } { if VerifyLegacy(value, record.Digest(sum)) { t.Fatalf("VerifyLegacy(%q) = true, want false", value) } } }