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/capsule/encrypt3.go

437 lines
16 KiB

Format 3, step 4: the writer EncryptFiles writes format 3 (spec 29.2 to 29.6, 61, 62, 62.1): the files of a list of Sources, each read twice, with the comment and the declared author. - Before anything is written: the paths and the texts are checked with the rules of the reader, in the words of a writer, naming the rule and the character, and the two paths of an R7 collision (rule 15); the comment has its CR LF and lone CR turned into LF (29.6); L is measured with a head whose salt and SHA-256 are zero, as long as the final one, and the first reading hashes each file, which must have exactly its Size. - The files go in the byte order of their paths (R8), whatever the order of the Sources; the mtime is kept only from 1970 to 9999, never clipped (rule 16); at least one file or a comment (rule 14). - The head, with a fresh salt, the control and the security area are decoded with the rules of the reader before sealing (rule 17), and the frame is checked against L. The area is 512 bytes with the empty security, whatever the options (rule 13). - The second reading writes each file into PAYLOAD_AGE and fails if its size or SHA-256 changed (rule 18). - Encrypt and EncryptFiles share the sealing; Encrypt writes format 2 only with the new TestVectors option (rule 1), and takes no head. The test data generators set it, and so does the CLI until step 5 moves it to EncryptFiles. - Result.Head is the head written. DecodeHead keeps the check of the critical extensions apart, so that the self-check decodes the head as the one of the control does. - The examples and the live test write with EncryptFiles. - The reader tests had a literal U+202E, now escaped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
package capsule
import (
"crypto/rand"
"crypto/sha256"
"errors"
"fmt"
"io"
"slices"
"strings"
"time"
"unicode/utf8"
"g.activething.com/go/DateKeys/internal/pathrule"
)
// Source is a file that EncryptFiles writes into a format 3 capsule.
type Source struct {
// Path is the path of the file in the capsule, relative, with '/'
// between its segments (spec §29.5). It is stored as given: EncryptFiles
// rejects a path that breaks a rule, with a message that names the rule
// and the character, and never corrects it (spec §62.1 rule 15).
Path string
// Size is the number of bytes of the file. EncryptFiles checks it in
// each of its two readings.
Size int64
// ModTime is the modification time of the file at its source, taken
// when it is loaded, as os.FileInfo.ModTime gives it, or the zero Time
// when unknown. It is stored in seconds when it falls from 1970-01-01 to
// 9999-12-31T23:59:59Z, and omitted otherwise, never clipped (spec
// §62.1 rule 16). It is informative: it proves nothing.
ModTime time.Time
// Open returns a reader of the file from its start. EncryptFiles calls
// it twice, and closes each reader.
Open func() (io.ReadCloser, error)
}
// EncryptFiles writes a format 3 .dkc holding the files of sources and the
// comment and declared author of opts (spec §29.2 to §29.6, §61, §62,
// §62.1). It needs no network: the round is resolved locally and tlock uses
// only the pinned public key.
//
// It reads each file twice, and writes nothing to dst before the second
// reading. First it checks the paths and the texts with the rules of the
// reader, measures L with a head whose salt and SHA-256 are zero, as long as
// the final one, and hashes each file. Then it seals the control, with L,
// and streams PAYLOAD_AGE, reading each file again: a file whose size or
// SHA-256 has changed makes it fail (spec §62.1 rule 18), and dst then holds
// a partial capsule that must be discarded and never presented as a capsule
// (rule 9). The files go in the byte order of their paths, whatever the
// order of sources (R8), without the empty folders, which a path cannot
// name.
//
// The head, the control and the security area are decoded with the rules of
// the reader before anything is written (spec §62.1 rule 17), and the
// self-checks of Encrypt apply too. The security area is in an area of
// AreaLen bytes, or LargeAreaLen with opts.LargeArea (rule 13): empty, or
// with the signature of opts.AuthorKey, made before anything is written
// with the commitments of the final control and the head (spec v0.11, §29.8)
// and checked with the strict profile (rule 19).
Format 3, step 4: the writer EncryptFiles writes format 3 (spec 29.2 to 29.6, 61, 62, 62.1): the files of a list of Sources, each read twice, with the comment and the declared author. - Before anything is written: the paths and the texts are checked with the rules of the reader, in the words of a writer, naming the rule and the character, and the two paths of an R7 collision (rule 15); the comment has its CR LF and lone CR turned into LF (29.6); L is measured with a head whose salt and SHA-256 are zero, as long as the final one, and the first reading hashes each file, which must have exactly its Size. - The files go in the byte order of their paths (R8), whatever the order of the Sources; the mtime is kept only from 1970 to 9999, never clipped (rule 16); at least one file or a comment (rule 14). - The head, with a fresh salt, the control and the security area are decoded with the rules of the reader before sealing (rule 17), and the frame is checked against L. The area is 512 bytes with the empty security, whatever the options (rule 13). - The second reading writes each file into PAYLOAD_AGE and fails if its size or SHA-256 changed (rule 18). - Encrypt and EncryptFiles share the sealing; Encrypt writes format 2 only with the new TestVectors option (rule 1), and takes no head. The test data generators set it, and so does the CLI until step 5 moves it to EncryptFiles. - Result.Head is the head written. DecodeHead keeps the check of the critical extensions apart, so that the self-check decodes the head as the one of the control does. - The examples and the live test write with EncryptFiles. - The reader tests had a literal U+202E, now escaped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
//
// opts is as for Encrypt, with the head in Comment, Author and the head
// extensions, and with Length 0: L is the length of BODY.
func EncryptFiles(dst io.Writer, sources []Source, opts EncryptOptions) (*Result, error) {
if opts.Length != 0 {
return nil, errors.New("capsule: EncryptOptions.Length is for Encrypt: EncryptFiles computes L from the files")
}
common := uint32(AreaLen)
switch t := opts.TestAreaLen; {
case t == 0:
case !opts.TestVectors:
return nil, errors.New("capsule: EncryptOptions.TestAreaLen is for generators of test vectors: it needs TestVectors (spec §62.1 rule 13)")
case t%AreaUnit != 0 || t > MaxAreaLen || opts.LargeArea:
return nil, fmt.Errorf("capsule: a test area of %d bytes: a multiple of %d up to %d, without LargeArea", t, AreaUnit, MaxAreaLen)
default:
common = t
}
Format 3, step 4: the writer EncryptFiles writes format 3 (spec 29.2 to 29.6, 61, 62, 62.1): the files of a list of Sources, each read twice, with the comment and the declared author. - Before anything is written: the paths and the texts are checked with the rules of the reader, in the words of a writer, naming the rule and the character, and the two paths of an R7 collision (rule 15); the comment has its CR LF and lone CR turned into LF (29.6); L is measured with a head whose salt and SHA-256 are zero, as long as the final one, and the first reading hashes each file, which must have exactly its Size. - The files go in the byte order of their paths (R8), whatever the order of the Sources; the mtime is kept only from 1970 to 9999, never clipped (rule 16); at least one file or a comment (rule 14). - The head, with a fresh salt, the control and the security area are decoded with the rules of the reader before sealing (rule 17), and the frame is checked against L. The area is 512 bytes with the empty security, whatever the options (rule 13). - The second reading writes each file into PAYLOAD_AGE and fails if its size or SHA-256 changed (rule 18). - Encrypt and EncryptFiles share the sealing; Encrypt writes format 2 only with the new TestVectors option (rule 1), and takes no head. The test data generators set it, and so does the CLI until step 5 moves it to EncryptFiles. - Result.Head is the head written. DecodeHead keeps the check of the critical extensions apart, so that the self-check decodes the head as the one of the control does. - The examples and the live test write with EncryptFiles. - The reader tests had a literal U+202E, now escaped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
s, err := newSealer(opts, 0)
if err != nil {
return nil, err
}
h, order, err := newHead(sources, opts)
if err != nil {
return nil, err
}
// Step 2 of spec §61: L, with a head as long as the final one.
measured, err := EncodeHead(h)
if err != nil {
return nil, err
}
if len(measured) > MaxHeadLen {
return nil, fmt.Errorf("capsule: the head is %d bytes, more than %d: fewer files or shorter paths", len(measured), MaxHeadLen)
}
if err := selfCheckHead(measured); err != nil {
return nil, err
}
var content uint64
if n := len(h.Files); n > 0 {
content = h.Files[n-1].End
}
// newHead bounds content by MaxPayloadLength: the sum does not overflow.
// This L is the first one, with the common area: the signature decides
// whether the area grows (spec §29.8), and prepare returns the final L.
area := common
length := BodyFrameSize + uint64(area) + uint64(len(measured)) + content
Format 3, step 4: the writer EncryptFiles writes format 3 (spec 29.2 to 29.6, 61, 62, 62.1): the files of a list of Sources, each read twice, with the comment and the declared author. - Before anything is written: the paths and the texts are checked with the rules of the reader, in the words of a writer, naming the rule and the character, and the two paths of an R7 collision (rule 15); the comment has its CR LF and lone CR turned into LF (29.6); L is measured with a head whose salt and SHA-256 are zero, as long as the final one, and the first reading hashes each file, which must have exactly its Size. - The files go in the byte order of their paths (R8), whatever the order of the Sources; the mtime is kept only from 1970 to 9999, never clipped (rule 16); at least one file or a comment (rule 14). - The head, with a fresh salt, the control and the security area are decoded with the rules of the reader before sealing (rule 17), and the frame is checked against L. The area is 512 bytes with the empty security, whatever the options (rule 13). - The second reading writes each file into PAYLOAD_AGE and fails if its size or SHA-256 changed (rule 18). - Encrypt and EncryptFiles share the sealing; Encrypt writes format 2 only with the new TestVectors option (rule 1), and takes no head. The test data generators set it, and so does the CLI until step 5 moves it to EncryptFiles. - Result.Head is the head written. DecodeHead keeps the check of the critical extensions apart, so that the self-check decodes the head as the one of the control does. - The examples and the live test write with EncryptFiles. - The reader tests had a literal U+202E, now escaped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
if _, err := PaddedLength(length, s.code); err != nil {
return nil, err
}
// Step 3: the first reading, for the SHA-256 of each file.
for i := range h.Files {
if h.Files[i].SHA256, err = readSource(nil, sources[order[i]], h.Files[i].Size, false); err != nil {
return nil, err
}
}
// Step 12: the head, with a fresh salt, and SECURITY_CBOR, decoded with
// the rules of the reader; write decodes CONTROL_CBOR.
_, _ = rand.Read(h.Salt[:]) // never fails since Go 1.24
head, err := EncodeHead(h)
if err != nil {
return nil, err
}
if len(head) != len(measured) {
return nil, fmt.Errorf("capsule: internal error: the head is %d bytes, measured %d", len(head), len(measured))
}
if err := selfCheckHead(head); err != nil {
return nil, err
}
// SECURITY_CBOR and the frame are final once the control is: the
// signature commits to it (spec v0.11, §29.8). prepare builds them
// before anything is written, and write decodes CONTROL_CBOR.
var security []byte
var fb [BodyFrameSize]byte
prepare := func(c *Control) (uint64, error) {
var err error
if security, err = s.security(c, head); err != nil {
return 0, err
}
// The area is the common one, or the large one only when what was
// signed does not fit and LargeArea allows it (§62.1 rule 13).
switch {
case len(security) <= int(common):
area = common
case !opts.LargeArea:
return 0, fmt.Errorf("capsule: SECURITY_CBOR of %d bytes does not fit in the area of %d bytes: LargeArea lets the writer widen it to %d", len(security), common, LargeAreaLen)
case len(security) <= LargeAreaLen:
area = LargeAreaLen
default:
return 0, fmt.Errorf("capsule: SECURITY_CBOR of %d bytes does not fit in the area of %d bytes, the largest", len(security), LargeAreaLen)
}
final := BodyFrameSize + uint64(area) + uint64(len(head)) + content
if _, err := PaddedLength(final, s.code); err != nil {
return 0, err
}
frame := BodyFrame{AreaLen: area, SecurityLen: uint32(len(security)), HeadLen: uint32(len(head))}
fb = frame.Bytes()
if _, err := ParseBodyFrame(fb[:], final); err != nil {
return 0, fmt.Errorf("capsule: self-check: %w", err)
}
if err := CheckHeadEnd(h, frame.ContentLength(final)); err != nil {
return 0, fmt.Errorf("capsule: self-check: %w", err)
}
return final, nil
Format 3, step 4: the writer EncryptFiles writes format 3 (spec 29.2 to 29.6, 61, 62, 62.1): the files of a list of Sources, each read twice, with the comment and the declared author. - Before anything is written: the paths and the texts are checked with the rules of the reader, in the words of a writer, naming the rule and the character, and the two paths of an R7 collision (rule 15); the comment has its CR LF and lone CR turned into LF (29.6); L is measured with a head whose salt and SHA-256 are zero, as long as the final one, and the first reading hashes each file, which must have exactly its Size. - The files go in the byte order of their paths (R8), whatever the order of the Sources; the mtime is kept only from 1970 to 9999, never clipped (rule 16); at least one file or a comment (rule 14). - The head, with a fresh salt, the control and the security area are decoded with the rules of the reader before sealing (rule 17), and the frame is checked against L. The area is 512 bytes with the empty security, whatever the options (rule 13). - The second reading writes each file into PAYLOAD_AGE and fails if its size or SHA-256 changed (rule 18). - Encrypt and EncryptFiles share the sealing; Encrypt writes format 2 only with the new TestVectors option (rule 1), and takes no head. The test data generators set it, and so does the CLI until step 5 moves it to EncryptFiles. - Result.Head is the head written. DecodeHead keeps the check of the critical extensions apart, so that the self-check decodes the head as the one of the control does. - The examples and the live test write with EncryptFiles. - The reader tests had a literal U+202E, now escaped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
}
// Step 16: BODY, and the second reading of each file.
res, err := s.write(dst, Format3, length, prepare, func(w io.Writer) error {
for _, b := range [][]byte{fb[:], security, make([]byte, int(area)-len(security)), head} {
Format 3, step 4: the writer EncryptFiles writes format 3 (spec 29.2 to 29.6, 61, 62, 62.1): the files of a list of Sources, each read twice, with the comment and the declared author. - Before anything is written: the paths and the texts are checked with the rules of the reader, in the words of a writer, naming the rule and the character, and the two paths of an R7 collision (rule 15); the comment has its CR LF and lone CR turned into LF (29.6); L is measured with a head whose salt and SHA-256 are zero, as long as the final one, and the first reading hashes each file, which must have exactly its Size. - The files go in the byte order of their paths (R8), whatever the order of the Sources; the mtime is kept only from 1970 to 9999, never clipped (rule 16); at least one file or a comment (rule 14). - The head, with a fresh salt, the control and the security area are decoded with the rules of the reader before sealing (rule 17), and the frame is checked against L. The area is 512 bytes with the empty security, whatever the options (rule 13). - The second reading writes each file into PAYLOAD_AGE and fails if its size or SHA-256 changed (rule 18). - Encrypt and EncryptFiles share the sealing; Encrypt writes format 2 only with the new TestVectors option (rule 1), and takes no head. The test data generators set it, and so does the CLI until step 5 moves it to EncryptFiles. - Result.Head is the head written. DecodeHead keeps the check of the critical extensions apart, so that the self-check decodes the head as the one of the control does. - The examples and the live test write with EncryptFiles. - The reader tests had a literal U+202E, now escaped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
if _, err := w.Write(b); err != nil {
return err
}
}
for i := range h.Files {
f := &h.Files[i]
sum, err := readSource(w, sources[order[i]], f.Size, true)
if err != nil {
return err
}
if sum != f.SHA256 {
return fmt.Errorf("capsule: file %q changed after its first reading: its SHA-256 is another", f.Path)
}
}
return nil
})
if err != nil {
return nil, err
}
res.Head, res.Security = h, s.verdicts
Format 3, step 4: the writer EncryptFiles writes format 3 (spec 29.2 to 29.6, 61, 62, 62.1): the files of a list of Sources, each read twice, with the comment and the declared author. - Before anything is written: the paths and the texts are checked with the rules of the reader, in the words of a writer, naming the rule and the character, and the two paths of an R7 collision (rule 15); the comment has its CR LF and lone CR turned into LF (29.6); L is measured with a head whose salt and SHA-256 are zero, as long as the final one, and the first reading hashes each file, which must have exactly its Size. - The files go in the byte order of their paths (R8), whatever the order of the Sources; the mtime is kept only from 1970 to 9999, never clipped (rule 16); at least one file or a comment (rule 14). - The head, with a fresh salt, the control and the security area are decoded with the rules of the reader before sealing (rule 17), and the frame is checked against L. The area is 512 bytes with the empty security, whatever the options (rule 13). - The second reading writes each file into PAYLOAD_AGE and fails if its size or SHA-256 changed (rule 18). - Encrypt and EncryptFiles share the sealing; Encrypt writes format 2 only with the new TestVectors option (rule 1), and takes no head. The test data generators set it, and so does the CLI until step 5 moves it to EncryptFiles. - Result.Head is the head written. DecodeHead keeps the check of the critical extensions apart, so that the self-check decodes the head as the one of the control does. - The examples and the live test write with EncryptFiles. - The reader tests had a literal U+202E, now escaped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
return res, nil
}
// security returns SECURITY_CBOR for the capsule whose final control is c and
// whose head is head: empty, or with the signature of opts.AuthorKey or
// opts.CMSSigner, and the seal of opts.Sealer (spec v0.11, §29.3, §29.8 to
// §29.11). The signature is made first and the seal after it, which seals it.
// It decodes and evaluates what it returns with the rules of the reader, in
// the context of this capsule (§62.1 rules 17, 19 and 21). The caller decides
// the area from its length.
func (s *sealer) security(c *Control, head []byte) ([]byte, error) {
o := s.opts
sc := &SecurityContext{HeadDigest: HeadDigest(head), RoundTime: s.unlock}
if o.AuthorKey == nil && o.CMSSigner == nil && o.Sealer == nil {
security := EncodeSecurity()
v := EvaluateSecurityIn(security, sc)
if v != (Verdicts{Signature: VerdictNoSignature, Seal: VerdictNoSeal}) {
return nil, fmt.Errorf("capsule: self-check: the reader finds the verdicts %s and %s in this security area", v.Signature, v.Seal)
}
s.verdicts = v
return security, nil
}
var err error
if sc.ControlCommit, err = ControlCommit(c, Format3); err != nil {
return nil, err
}
var signature, seal []byte
wantSig := VerdictNoSignature
var key [32]byte
switch {
case o.AuthorKey != nil:
pub := o.AuthorKey.Public()
if len(pub) != 32 {
return nil, fmt.Errorf("capsule: the author key is %d bytes, not 32", len(pub))
}
msg := AuthorMessage(sc.ControlCommit, sc.HeadDigest, SignersDigest(AlgEd25519, nil))
if signature, err = EncodeAuthorSignature(AlgEd25519, pub, o.AuthorKey.Sign(msg)); err != nil {
return nil, err
}
wantSig = VerdictSignedOther
copy(key[:], pub)
case o.CMSSigner != nil:
list, err := EncodeSigners(o.CMSSigner.Signers())
if err != nil {
return nil, err
}
// AUTHOR_MESSAGE is what the person sees and signs elsewhere: the
// callback may take as long as she needs.
msg := AuthorMessage(sc.ControlCommit, sc.HeadDigest, SignersDigest(AlgCMS, list))
der, err := o.CMSSigner.Sign(msg)
if err != nil {
return nil, fmt.Errorf("capsule: signing: %w", err)
}
if signature, err = EncodeAuthorSignature(AlgCMS, list, der); err != nil {
return nil, err
}
wantSig = VerdictSignedComplete
}
wantSeal := VerdictNoSeal
if o.Sealer != nil {
subject := SealSubject(sc.ControlCommit, sc.HeadDigest, SigPart(signature))
token, err := o.Sealer.Seal(subject)
if err != nil {
return nil, fmt.Errorf("capsule: sealing: %w", err)
}
if seal, err = EncodeSeal(SealTypeRFC3161, token); err != nil {
return nil, err
}
wantSeal = VerdictSealed
}
security, err := EncodeSecurityWith(signature, seal)
if err != nil {
return nil, err
}
v := EvaluateSecurityIn(security, sc)
// A seal that proves nothing before the round time (S5) is still a seal
// that verifies: the writer's clock and the authority's may differ, or
// the token may carry no accuracy. Result.Security lets the caller warn
// of it (§62.1 rule 19).
sealOK := v.Seal == wantSeal || wantSeal == VerdictSealed && v.Seal == VerdictSealedLate
if v.Signature != wantSig || !sealOK || v.AuthorKey != key {
return nil, fmt.Errorf("capsule: self-check: the reader finds the verdicts %s and %s in this security area, not %s and %s%s", v.Signature, v.Seal, wantSig, wantSeal, detailText(v.Detail))
}
s.verdicts = v
return security, nil
}
// detailText names the signers that failed, for the error of a writer.
func detailText(d *Detail) string {
if d == nil {
return ""
}
var parts []string
for _, l := range d.Signers {
if l.Result != "valid" {
parts = append(parts, l.Holder+": "+l.Result)
}
}
if len(parts) == 0 {
return ""
}
return " (" + strings.Join(parts, "; ") + ")"
}
Format 3, step 4: the writer EncryptFiles writes format 3 (spec 29.2 to 29.6, 61, 62, 62.1): the files of a list of Sources, each read twice, with the comment and the declared author. - Before anything is written: the paths and the texts are checked with the rules of the reader, in the words of a writer, naming the rule and the character, and the two paths of an R7 collision (rule 15); the comment has its CR LF and lone CR turned into LF (29.6); L is measured with a head whose salt and SHA-256 are zero, as long as the final one, and the first reading hashes each file, which must have exactly its Size. - The files go in the byte order of their paths (R8), whatever the order of the Sources; the mtime is kept only from 1970 to 9999, never clipped (rule 16); at least one file or a comment (rule 14). - The head, with a fresh salt, the control and the security area are decoded with the rules of the reader before sealing (rule 17), and the frame is checked against L. The area is 512 bytes with the empty security, whatever the options (rule 13). - The second reading writes each file into PAYLOAD_AGE and fails if its size or SHA-256 changed (rule 18). - Encrypt and EncryptFiles share the sealing; Encrypt writes format 2 only with the new TestVectors option (rule 1), and takes no head. The test data generators set it, and so does the CLI until step 5 moves it to EncryptFiles. - Result.Head is the head written. DecodeHead keeps the check of the critical extensions apart, so that the self-check decodes the head as the one of the control does. - The examples and the live test write with EncryptFiles. - The reader tests had a literal U+202E, now escaped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
// newHead checks the files and the texts of opts with the rules of spec
// §29.4 to §29.6, in the words of a writer (spec §62.1 rule 15), and returns
// the head with the files in the byte order of their paths, their layout and
// mtime, and a zero salt and zero SHA-256; order[i] is the source of entry
// i. The comment has its CR LF, and any lone CR, turned into LF (§29.6).
func newHead(sources []Source, opts EncryptOptions) (*Head, []int, error) {
comment := strings.ReplaceAll(strings.ReplaceAll(opts.Comment, "\r\n", "\n"), "\r", "\n")
switch {
case len(sources) == 0 && comment == "":
return nil, nil, errors.New("capsule: a format 3 capsule holds at least one file or a comment (spec §62.1 rule 14)")
case len(sources) > MaxFiles:
return nil, nil, fmt.Errorf("capsule: %d files, more than %d", len(sources), MaxFiles)
}
if err := checkHeadText("comment", comment, MaxCommentLen, pathrule.CheckComment); err != nil {
return nil, nil, err
}
if err := checkHeadText("declared author", opts.Author, MaxAuthorLen, pathrule.CheckAuthor); err != nil {
return nil, nil, err
}
order := make([]int, len(sources))
for i := range order {
order[i] = i
}
// R8: the byte order of the paths, which is the order of Go strings.
slices.SortStableFunc(order, func(a, b int) int { return strings.Compare(sources[a].Path, sources[b].Path) })
h := &Head{Comment: comment, Author: opts.Author, Critical: opts.HeadCritical, Noncritical: opts.HeadNoncritical}
paths := make([]string, len(order))
var end uint64
for i, j := range order {
src, p := sources[j], sources[j].Path
switch {
case i > 0 && p == paths[i-1]:
return nil, nil, fmt.Errorf("capsule: path %q given twice", p)
case !utf8.ValidString(p):
return nil, nil, fmt.Errorf("capsule: path %q: R1: not valid UTF-8", p)
case len(p) == 0 || len(p) > MaxPathLen:
return nil, nil, fmt.Errorf("capsule: path %q: R1: %d bytes, not 1 to %d", p, len(p), MaxPathLen)
case src.Size < 0:
return nil, nil, fmt.Errorf("capsule: file %q: negative size %d", p, src.Size)
case src.Open == nil:
return nil, nil, fmt.Errorf("capsule: file %q: Source.Open is nil", p)
case uint64(src.Size) > MaxPayloadLength-end:
return nil, nil, fmt.Errorf("capsule: the files add up to more than %d bytes, the maximum of L", uint64(MaxPayloadLength))
}
if err := pathrule.CheckPath(p); err != nil {
return nil, nil, fmt.Errorf("capsule: path %q: %w", p, err)
}
f := File{Path: p, Size: uint64(src.Size), Start: end, End: end + uint64(src.Size)}
if t := src.ModTime; !t.IsZero() {
if u := t.Unix(); u >= 0 && u <= MaxMTime {
f.MTime, f.HasMTime = uint64(u), true
}
}
h.Files = append(h.Files, f)
paths[i], end = p, f.End
}
if err := pathrule.CheckTree(paths); err != nil {
var e *pathrule.Error
if errors.As(err, &e) && e.Paths[0] > 0 {
return nil, nil, fmt.Errorf("capsule: paths %q and %q: %w", paths[e.Paths[1]-1], paths[e.Paths[0]-1], err)
}
return nil, nil, fmt.Errorf("capsule: paths: %w", err)
}
return h, order, nil
}
// checkHeadText checks the comment or the declared author, when present:
// valid UTF-8, at most max bytes, and the characters of spec §29.6.
func checkHeadText(what, s string, max int, check func(string) error) error {
switch {
case s == "":
return nil
case !utf8.ValidString(s):
return fmt.Errorf("capsule: %s: not valid UTF-8", what)
case len(s) > max:
return fmt.Errorf("capsule: %s: %d bytes, more than %d", what, len(s), max)
}
if err := check(s); err != nil {
return fmt.Errorf("capsule: %s: %w", what, err)
}
return nil
}
// selfCheckHead decodes HEAD_CBOR with the rules of the reader, but for the
// knowledge of its critical extensions, which depends on the reader, as
// selfCheckControl does with the control (spec §62.1 rule 17). A head that
// the reader rejects would only be found after the date.
func selfCheckHead(b []byte) error {
if _, err := decodeHead(b); err != nil {
return fmt.Errorf("capsule: self-check: the reader rejects this head: %w", err)
}
return nil
}
// readSource reads the file of src, which must be exactly size bytes, and
// returns its SHA-256; w, when not nil, receives its bytes. In the second
// reading, a file whose size differs has changed (spec §62.1 rule 18).
func readSource(w io.Writer, src Source, size uint64, second bool) ([32]byte, error) {
var sum [32]byte
mismatch := func(format string, args ...any) error {
if second {
return fmt.Errorf("capsule: file %q changed after its first reading: %s", src.Path, fmt.Sprintf(format, args...))
}
return fmt.Errorf("capsule: file %q: %s", src.Path, fmt.Sprintf(format, args...))
}
rc, err := src.Open()
if err != nil {
return sum, fmt.Errorf("capsule: file %q: %w", src.Path, err)
}
defer rc.Close()
h := sha256.New()
buf := make([]byte, 32<<10)
defer clear(buf)
var n uint64
for {
k, err := rc.Read(buf)
if uint64(k) > size-n {
return sum, mismatch("more than its size of %d bytes", size)
}
h.Write(buf[:k])
if w != nil && k > 0 {
if _, err := w.Write(buf[:k]); err != nil {
return sum, err
}
}
n += uint64(k)
if err == io.EOF {
break
}
if err != nil {
return sum, fmt.Errorf("capsule: file %q: %w", src.Path, err)
}
}
if n != size {
return sum, mismatch("%d bytes, not its size of %d", n, size)
}
h.Sum(sum[:0])
return sum, nil
}

Powered by TurnKey Linux.