check / check (push) Waiting to run
docs/FORMAT.md now says a .mf is always a whole file, readers ignore fields they do not know, and paths are compared byte for byte with no Unicode normalization. Deterministic Serialization describes what the writer does: entry order, protobuf encoding, what each entry holds, zstd settings and how the UUID is chosen, and no longer claims two runs give the same bytes without --seed or with --include-timestamps. A signed test vector made once by the code, with a key used for nothing else, is checked in as mfer/testdata/test-vector.mf, shown in the document, and pinned by its SHA-256 and verified by TestFormatTestVector. Model: opus-5-5
93 lines
2.9 KiB
Go
93 lines
2.9 KiB
Go
//nolint:testpackage // white-box tests exercise unexported internals
|
|
package mfer
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"os"
|
|
"testing"
|
|
"uuid"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
// An inner message whose version is not VERSION_ONE, whether version 0 or a
|
|
// later one, is refused with the same error as an outer message's.
|
|
func TestDeserializeRefusesUnknownInnerVersion(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, version := range []MFFile_Version{MFFile_VERSION_NONE, MFFile_VERSION_ONE + 1} {
|
|
t.Run(version.String(), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
id := uuid.NewV4()
|
|
inner, err := proto.Marshal(&MFFile{Version: version, Uuid: id[:]})
|
|
require.NoError(t, err)
|
|
|
|
_, err = NewManifestFromReader(bytes.NewReader(wrapInner(t, id, inner)))
|
|
require.ErrorIs(t, err, errUnknownVersion)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestReadAtMost gives readAtMost exactly its maximum, which it must
|
|
// return whole, and twice its maximum, which it must refuse after reading
|
|
// one byte past the maximum, and no more. NewManifestFromReader reads
|
|
// through it with MaxManifestSize; the test uses 64 KiB, since reading
|
|
// MaxManifestSize under the race detector takes gigabytes of memory.
|
|
func TestReadAtMost(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const maxSize = 64 << 10
|
|
|
|
dat, err := readAtMost(bytes.NewReader(make([]byte, maxSize)), maxSize)
|
|
require.NoError(t, err)
|
|
assert.Len(t, dat, maxSize)
|
|
|
|
input := bytes.NewReader(make([]byte, 2*maxSize))
|
|
|
|
_, err = readAtMost(input, maxSize)
|
|
require.ErrorIs(t, err, errManifestTooLarge)
|
|
require.EqualError(t, err,
|
|
"file exceeds maximum allowed size of 65536 bytes")
|
|
assert.Equal(t, maxSize-1, input.Len(), "bytes left unread")
|
|
}
|
|
|
|
// TestFormatTestVector checks the test vector that docs/FORMAT.md shows: the
|
|
// SHA-256 of the whole file, so that any change to the file fails, then loads
|
|
// it, verifies its signature, and checks the canonical string and the signer
|
|
// the document gives for it.
|
|
func TestFormatTestVector(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
path = "testdata/test-vector.mf"
|
|
fileSHA256 = "5864187d671b2a55f8e015f858248f92c51956b549cf548b6bb54477c2407fd4"
|
|
signer = "5E962A37590D855B467D20294947A60319611E6F"
|
|
)
|
|
|
|
data, err := os.ReadFile(path)
|
|
require.NoError(t, err)
|
|
|
|
sum := sha256.Sum256(data)
|
|
require.Equal(t, fileSHA256, hex.EncodeToString(sum[:]))
|
|
|
|
m, err := NewManifestFromFile(&ManifestFromFileOptions{Path: path})
|
|
require.NoError(t, err)
|
|
|
|
canonicalString, err := m.signatureString()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "ZNAVSRFG-92197fd532bc40908aa9f86e5518b56c-"+
|
|
"12202353553205f036a88cefee3d0d9049ccd8e68bb24011a0b462b5867f1fcc6352",
|
|
canonicalString)
|
|
|
|
keyFingerprint, err := verifySignature([]byte(canonicalString),
|
|
m.pbOuter.GetSignature(), m.pbOuter.GetSigningPubKey())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, signer, keyFingerprint)
|
|
assert.Equal(t, signer, string(m.pbOuter.GetSigner()))
|
|
}
|