Validate byte-preserving record fields as base64 strings
This commit is contained in:
@@ -3,6 +3,7 @@ package publish
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -52,8 +53,15 @@ func LoadEvidence(path string) (Evidence, error) {
|
||||
}
|
||||
for _, field := range evidenceFields(&evidence) {
|
||||
if raw, ok := fields[field.name]; ok {
|
||||
var bytes []byte
|
||||
if err := json.Unmarshal(raw, &bytes); err != nil {
|
||||
var encoded *string
|
||||
if err := json.Unmarshal(raw, &encoded); err != nil {
|
||||
return Evidence{}, &RecoveryError{Kind: "record_format", RecordPath: path, cause: fmt.Errorf("decode record field %s bytes %q: %w", field.name, path, err)}
|
||||
}
|
||||
if encoded == nil {
|
||||
return Evidence{}, &RecoveryError{Kind: "record_format", RecordPath: path, cause: fmt.Errorf("record field %s bytes %q must be a base64 string", field.name, path)}
|
||||
}
|
||||
bytes, err := base64.StdEncoding.DecodeString(*encoded)
|
||||
if err != nil {
|
||||
return Evidence{}, &RecoveryError{Kind: "record_format", RecordPath: path, cause: fmt.Errorf("decode record field %s bytes %q: %w", field.name, path, err)}
|
||||
}
|
||||
*field.value = string(bytes)
|
||||
|
||||
@@ -2,6 +2,7 @@ package publish
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -14,6 +15,50 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadEvidenceValidatesKnownByteFieldsAsBase64Strings(t *testing.T) {
|
||||
for _, test := range []struct{ name, value, want, failure string }{
|
||||
{"valid", `"djEuMi4z"`, "v1.2.3", ""},
|
||||
{"empty", `""`, "", ""},
|
||||
{"malformed base64", `"not base64!"`, "", "base64"},
|
||||
{"numeric array", `[118,49,46,50,46,51]`, "", "type"},
|
||||
{"object", `{}`, "", "type"},
|
||||
{"boolean", `true`, "", "type"},
|
||||
{"number", `123`, "", "type"},
|
||||
{"null", `null`, "", "schema"},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "record.json")
|
||||
contents := `{"format_version":2,"evidence":{},"evidence_bytes":{"tag":` + test.value + `}}`
|
||||
if err := os.WriteFile(path, []byte(contents), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loaded, err := LoadEvidence(path)
|
||||
if test.failure == "" {
|
||||
if err != nil || loaded.Tag != test.want {
|
||||
t.Fatalf("byte string = %#v, %v", loaded, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
var typed *RecoveryError
|
||||
if !errors.As(err, &typed) || typed.Kind != "record_format" || typed.Unwrap() == nil {
|
||||
t.Fatalf("byte format failure = %v", err)
|
||||
}
|
||||
switch test.failure {
|
||||
case "type":
|
||||
var cause *json.UnmarshalTypeError
|
||||
if !errors.As(err, &cause) {
|
||||
t.Fatalf("lost JSON type cause: %v", err)
|
||||
}
|
||||
case "base64":
|
||||
var cause base64.CorruptInputError
|
||||
if !errors.As(err, &cause) {
|
||||
t.Fatalf("lost base64 cause: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordsRoundTripNonUTF8Evidence(t *testing.T) {
|
||||
repo := recordRepo(t)
|
||||
raw := "path-\xff"
|
||||
|
||||
Reference in New Issue
Block a user