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.
239 lines
9.8 KiB
239 lines
9.8 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"`
|
|
// ChainHash is the chain a release object names (spec v0.15, §47.1),
|
|
// when it is not the chain of the pinned Quicknet profile; absent
|
|
// otherwise.
|
|
ChainHash string `json:"chain_hash,omitempty"`
|
|
}
|
|
|
|
// 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"`
|
|
// Seal is, in a fixture with a valid time seal, what a second
|
|
// implementation needs to check it (spec v0.11, §29.11).
|
|
Seal *FixtureSeal `json:"seal,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 and AuthorKey are those of an alg 1 signature.
|
|
SecretSeed string `json:"secret_seed,omitempty"`
|
|
AuthorKey string `json:"author_key,omitempty"`
|
|
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 is the 64 bytes of alg 1, or the DER of the CMS
|
|
// signature of alg 2.
|
|
SignatureValue string `json:"signature"`
|
|
SecurityKey2 string `json:"security_key_2"`
|
|
// Signers is, with alg 2, SIGNERS in hexadecimal, Certificates the DER of
|
|
// the certificates inside the signature, and Results what each required
|
|
// signer gave, in the order of SIGNERS; Foreign are the others.
|
|
Signers string `json:"signers,omitempty"`
|
|
Certificates []string `json:"certificates,omitempty"`
|
|
Results []FixtureSignerResult `json:"signer_results,omitempty"`
|
|
Foreign []FixtureSignerResult `json:"foreign_signers,omitempty"`
|
|
}
|
|
|
|
// FixtureSignerResult is a signer of an alg 2 signature as a reader shows it
|
|
// (spec §29.7, §29.10).
|
|
type FixtureSignerResult struct {
|
|
Holder string `json:"holder"`
|
|
Issuer string `json:"issuer"`
|
|
Result string `json:"result"`
|
|
SealTime string `json:"seal_time,omitempty"`
|
|
Before bool `json:"before_round_time"`
|
|
// SealReason is why a valid seal does not prove that it came before the
|
|
// round time (spec v0.16, §29.7): "late", "no accuracy" or "no
|
|
// accuracy, BTSP"; empty when it does.
|
|
SealReason string `json:"seal_reason,omitempty"`
|
|
}
|
|
|
|
// FixtureSeal describes the seal of seal_type 2 of a format 3 fixture:
|
|
// SEAL_SUBJECT, the token in hexadecimal, the holder of the certificate of
|
|
// the authority as §29.7 shows it, and t.
|
|
type FixtureSeal struct {
|
|
SealType int `json:"seal_type"`
|
|
SealSubject string `json:"seal_subject"`
|
|
Token string `json:"token"`
|
|
Holder string `json:"holder"`
|
|
Time string `json:"time"`
|
|
}
|
|
|
|
// 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)
|
|
}
|