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.
DateKeys/internal/testkit/fixture.go

243 lines
10 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"`
// WordsText is the text of the words of a key of words, as the person
// types it (spec §38.1, annex 79.7 of v0.16); the identity it gives is
// in Identities.
WordsText string `json:"words_text,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)
}

Powered by TurnKey Linux.