You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
7.9 KiB
197 lines
7.9 KiB
package testkit
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
)
|
|
|
|
// FixtureStanza is the visible part of an age stanza in a fixture.
|
|
type FixtureStanza struct {
|
|
Type string `json:"type"`
|
|
Args []string `json:"args"`
|
|
}
|
|
|
|
// FixtureRelease is the release a fixture opens with.
|
|
type FixtureRelease struct {
|
|
Round uint64 `json:"round"`
|
|
Signature string `json:"signature"`
|
|
}
|
|
|
|
// FixtureStage is the expected result of one step of spec §63.
|
|
type FixtureStage struct {
|
|
Step int `json:"step"`
|
|
Name string `json:"name"`
|
|
OK bool `json:"ok"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// DKCFixture holds the expected values of an official .dkc fixture (spec §67).
|
|
type DKCFixture struct {
|
|
Description string `json:"description"`
|
|
Spec string `json:"spec"`
|
|
// Format is the capsule format, the VERSION of the PRELUDE (spec §22).
|
|
Format int `json:"format"`
|
|
File string `json:"file"`
|
|
SHA256 string `json:"sha256"`
|
|
Release FixtureRelease `json:"release"`
|
|
Prelude string `json:"prelude"`
|
|
PublicHeader string `json:"public_header"`
|
|
DateKey string `json:"datekey"`
|
|
CapsuleID string `json:"capsule_id"`
|
|
AccessPolicy string `json:"access_policy"`
|
|
Structure string `json:"structure"`
|
|
UnlockAt string `json:"unlock_at"`
|
|
HeaderBinding string `json:"header_binding"`
|
|
OuterStanzas []FixtureStanza `json:"outer_stanzas"`
|
|
PayloadStanzas []FixtureStanza `json:"payload_stanzas"`
|
|
InnerStanzas []FixtureStanza `json:"inner_stanzas,omitempty"`
|
|
// AccessKeyStanza and IdentityStanzas are, in a format 2 time_and_key
|
|
// fixture, the index in InnerStanzas of the stanza each credential
|
|
// opens: the .dkk, and each identity of Identities in order. The stanzas
|
|
// no credential opens are dummies. Official vectors are the only place
|
|
// where this is recorded (spec §39, §67).
|
|
AccessKeyStanza *int `json:"access_key_stanza,omitempty"`
|
|
IdentityStanzas []int `json:"identity_stanzas,omitempty"`
|
|
AccessKeyFile string `json:"access_key_file,omitempty"`
|
|
Identities []string `json:"identities,omitempty"`
|
|
ControlCBOR string `json:"control_cbor"`
|
|
PayloadIdentity string `json:"payload_identity"`
|
|
// PayloadLength is L, the length of the content: the plaintext the
|
|
// reader delivers in formats 1 and 2, and BODY in format 3, whose files
|
|
// Files describes. In formats 2 and 3 the plaintext of PAYLOAD_AGE is
|
|
// PaddedLength bytes, P = rule(L), the rule being Padding (spec §29.1,
|
|
// §29.2). PlaintextFile holds those L bytes.
|
|
PayloadLength uint64 `json:"payload_length"`
|
|
Padding int `json:"padding,omitempty"`
|
|
PaddedLength uint64 `json:"padded_length,omitempty"`
|
|
PlaintextFile string `json:"plaintext_file"`
|
|
PlaintextSHA256 string `json:"plaintext_sha256"`
|
|
HeaderExtensions []FixtureExt `json:"header_extensions,omitempty"`
|
|
ControlExt []FixtureExt `json:"control_extensions,omitempty"`
|
|
// The fields of format 3 (spec §29.2 to §29.7): the size of the security
|
|
// area, SECURITY_CBOR and HEAD_CBOR, the salt, the comment and the
|
|
// declared author, the extensions of the head, the offset of CONTENT in
|
|
// BODY, 12 + AREA_LEN + HEAD_LEN, each file, and the verdicts.
|
|
AreaLen uint32 `json:"area_len,omitempty"`
|
|
Security string `json:"security_cbor,omitempty"`
|
|
Head string `json:"head_cbor,omitempty"`
|
|
Salt string `json:"salt,omitempty"`
|
|
Comment string `json:"comment,omitempty"`
|
|
Author string `json:"declared_author,omitempty"`
|
|
HeadExtensions []FixtureExt `json:"head_extensions,omitempty"`
|
|
ContentOffset uint64 `json:"content_offset,omitempty"`
|
|
Files []FixtureFile `json:"files,omitempty"`
|
|
Verdicts *FixtureVerdicts `json:"verdicts,omitempty"`
|
|
// Signature is, in a fixture signed with alg 1, what a second
|
|
// implementation needs to check the signature and to make it again
|
|
// (spec v0.11, §29.8, §29.9).
|
|
Signature *FixtureSignature `json:"signature,omitempty"`
|
|
Stages []FixtureStage `json:"stages"`
|
|
}
|
|
|
|
// FixtureFile is a file of a format 3 fixture: its entry of the head. Its
|
|
// bytes are those of BODY from ContentOffset + Start to ContentOffset + End.
|
|
type FixtureFile struct {
|
|
Path string `json:"path"`
|
|
Size uint64 `json:"size"`
|
|
Start uint64 `json:"start"`
|
|
End uint64 `json:"end"`
|
|
SHA256 string `json:"sha256"`
|
|
MTime *uint64 `json:"mtime,omitempty"`
|
|
}
|
|
|
|
// FixtureVerdicts are the verdicts of the security area of a format 3
|
|
// fixture, and the lines that show them (spec §29.7).
|
|
type FixtureVerdicts struct {
|
|
Signature string `json:"signature"`
|
|
Seal string `json:"seal"`
|
|
Lines []string `json:"lines"`
|
|
// AuthorKey is the dkauthor1… key of a valid signature (F3, F4).
|
|
AuthorKey string `json:"author_key,omitempty"`
|
|
}
|
|
|
|
// FixtureSignature describes the signature of alg 1 of a format 3 fixture.
|
|
// SecretSeed is the 32-byte seed of a test key made for it: Ed25519 is
|
|
// deterministic, so signing AuthorMessage with it gives SignatureValue
|
|
// again. ControlCommit, HeadDigest and SignersDigest are the commitments of
|
|
// spec §29.8, in hexadecimal; AuthorMessage is the ASCII text that is
|
|
// signed, and AuthorCode its code. SecurityKey2 is the exact content of key
|
|
// 2 of SECURITY_CBOR, in hexadecimal.
|
|
type FixtureSignature struct {
|
|
Alg int `json:"alg"`
|
|
SecretSeed string `json:"secret_seed"`
|
|
AuthorKey string `json:"author_key"`
|
|
ControlCommit string `json:"control_commit"`
|
|
HeadDigest string `json:"head_digest"`
|
|
SignersDigest string `json:"signers_digest"`
|
|
AuthorMessage string `json:"author_message"`
|
|
AuthorCode string `json:"author_code"`
|
|
SignatureValue string `json:"signature"`
|
|
SecurityKey2 string `json:"security_key_2"`
|
|
}
|
|
|
|
// FixtureExt is an extension in a fixture.
|
|
type FixtureExt struct {
|
|
Critical bool `json:"critical"`
|
|
ID string `json:"id"`
|
|
Version uint64 `json:"version"`
|
|
Data string `json:"data,omitempty"` // hex of the exact data bytes; absent without data
|
|
}
|
|
|
|
// DKKFixture holds the expected values of an official .dkk fixture (spec §68).
|
|
type DKKFixture struct {
|
|
Description string `json:"description"`
|
|
Spec string `json:"spec"`
|
|
File string `json:"file"`
|
|
SHA256 string `json:"sha256"`
|
|
CredentialID string `json:"credential_id"`
|
|
CapsuleID string `json:"capsule_id"`
|
|
AccessType string `json:"access_type"`
|
|
Material string `json:"access_material"`
|
|
CapsuleDigest string `json:"capsule_digest,omitempty"`
|
|
Extensions []FixtureExt `json:"extensions,omitempty"`
|
|
Capsule string `json:"capsule"`
|
|
ExpectedResult string `json:"expected_result"`
|
|
Stages []FixtureStage `json:"stages,omitempty"`
|
|
}
|
|
|
|
// ReadJSON decodes a JSON file.
|
|
func ReadJSON(path string, v any) error {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.Unmarshal(b, v)
|
|
}
|
|
|
|
// WriteJSON writes v as indented JSON with a trailing newline.
|
|
func WriteJSON(path string, v any) error {
|
|
b, err := json.MarshalIndent(v, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeFile(path, append(b, '\n'))
|
|
}
|
|
|
|
// editPattern is an Edit as json.MarshalIndent spreads it over five lines.
|
|
var editPattern = regexp.MustCompile(`\[\n\s*(\d+),\n\s*(\d+),\n\s*("[0-9a-f]*")\n\s*\]`)
|
|
|
|
// WriteJSONEdits is WriteJSON with every Edit, [at, delete, "hex"], on one
|
|
// line.
|
|
func WriteJSONEdits(path string, v any) error {
|
|
b, err := json.MarshalIndent(v, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeFile(path, append(editPattern.ReplaceAll(b, []byte("[$1, $2, $3]")), '\n'))
|
|
}
|
|
|
|
func writeFile(path string, b []byte) error {
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(path, b, 0o644)
|
|
}
|