diff --git a/tools/dwh-auth/internal/record/record.go b/tools/dwh-auth/internal/record/record.go index 414e242b..acd13b77 100644 --- a/tools/dwh-auth/internal/record/record.go +++ b/tools/dwh-auth/internal/record/record.go @@ -111,6 +111,9 @@ func validInstallationID(value string) bool { } func validDigest(value string) bool { + if len(value) != 43 { + return false + } decoded, err := base64.RawURLEncoding.DecodeString(value) return err == nil && len(decoded) == 32 && base64.RawURLEncoding.EncodeToString(decoded) == value } diff --git a/tools/dwh-auth/internal/record/record_test.go b/tools/dwh-auth/internal/record/record_test.go index 150e924a..2be43df3 100644 --- a/tools/dwh-auth/internal/record/record_test.go +++ b/tools/dwh-auth/internal/record/record_test.go @@ -60,6 +60,7 @@ func TestValidateRejectsInvalidSchemaIdentifierAndDigest(t *testing.T) { "identifier too long": func(r *Record) { r.InstallationID = "a" + strings.Repeat("b", 63) }, "digest padding": func(r *Record) { r.SecretSHA256 += "=" }, "digest short": func(r *Record) { r.SecretSHA256 = "AAAA" }, + "digest oversized": func(r *Record) { r.SecretSHA256 = strings.Repeat("A", 1024) }, } { t.Run(name, func(t *testing.T) { r := validRecord() @@ -71,10 +72,21 @@ func TestValidateRejectsInvalidSchemaIdentifierAndDigest(t *testing.T) { } } +func TestValidDigestRejectsOversizedInputWithoutDecoding(t *testing.T) { + oversized := strings.Repeat("A", 1024) + if validDigest(oversized) { + t.Fatal("validDigest() = true, want false") + } + if allocations := testing.AllocsPerRun(1, func() { validDigest(oversized) }); allocations != 0 { + t.Fatalf("validDigest() allocations = %v, want 0 for oversized input", allocations) + } +} + func TestValidateRejectsInvalidMetadata(t *testing.T) { for name, mutate := range map[string]func(*Record){ - "description control": func(r *Record) { r.Description = "bad\nmetadata" }, - "description too long": func(r *Record) { r.Description = strings.Repeat("é", 161) }, + "description control": func(r *Record) { r.Description = "bad\nmetadata" }, + "description invalid utf8": func(r *Record) { r.Description = string([]byte{0xff}) }, + "description too long": func(r *Record) { r.Description = strings.Repeat("é", 161) }, "reason control": func(r *Record) { now := r.CreatedAt.Add(time.Hour) r.RevokedAt = &now @@ -98,11 +110,15 @@ func TestValidateRejectsInvalidMetadata(t *testing.T) { func TestValidateRejectsInvalidTimesExpiryAndRevocation(t *testing.T) { for name, mutate := range map[string]func(*Record){ + "created zero": func(r *Record) { r.CreatedAt = time.Time{} }, + "expiry before creation": func(r *Record) { at := r.CreatedAt.Add(-time.Second); r.ExpiresAt = &at }, + "expiry zero": func(r *Record) { at := time.Time{}; r.ExpiresAt = &at }, "created non UTC": func(r *Record) { r.CreatedAt = r.CreatedAt.In(time.FixedZone("CET", 3600)) }, "expiry non UTC": func(r *Record) { at := r.CreatedAt.Add(time.Hour).In(time.FixedZone("CET", 3600)); r.ExpiresAt = &at }, "expiry equals created": func(r *Record) { at := r.CreatedAt; r.ExpiresAt = &at }, "revoked without reason": func(r *Record) { at := r.CreatedAt.Add(time.Hour); r.RevokedAt = &at }, "reason without revoked": func(r *Record) { r.RevocationReason = "synthetic" }, + "revoked zero": func(r *Record) { at := time.Time{}; r.RevokedAt = &at; r.RevocationReason = "synthetic" }, "revoked non UTC": func(r *Record) { at := r.CreatedAt.Add(time.Hour).In(time.FixedZone("CET", 3600)) r.RevokedAt = &at