Files
mfer/mfer/manifest.go
T
clawbot 0baf83deb6
check / check (push) Waiting to run
Sign or timestamp an unchanged tree's manifest in freshen (closes #190)
freshen skipped the write whenever no file was added, changed or
removed, so --sign-key and --include-timestamps on an unchanged tree
did nothing. It now skips only when the manifest would come out the
same: nothing in the tree changed, the manifest is signed by the
--sign-key key (unsigned without one), and it has a createdAt
timestamp exactly when --include-timestamps asks for one. A file whose
recorded mode is not what --include-permissions asks for already
counts as changed.

Deviation: the mfer package gains SecretKeyFingerprint and a loaded
manifest's CreatedAt, which the comparison needs.

Model: opus-5-5
2026-10-14 00:42:44 +02:00

93 lines
2.3 KiB
Go

package mfer
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"github.com/multiformats/go-multihash"
)
var (
errOuterNotSet = errors.New("outer message not set")
errUUIDNotSet = errors.New("UUID not set")
errSHA256NotSet = errors.New("SHA256 hash not set")
)
// manifest holds the internal representation of a manifest file.
// Use NewManifestFromFile or NewManifestFromReader to load an existing
// manifest, or use Builder to create a new one.
//
// Whether this type should be exported is an open design question owned by
// the repository owner; see README design question 13.
type manifest struct {
pbInner *MFFile
pbOuter *MFFileOuter
output *bytes.Buffer
signingOptions *SigningOptions
fixedUUID []byte // if set, use this UUID instead of generating one
}
func (m *manifest) String() string {
count := 0
if m.pbInner != nil {
count = len(m.pbInner.GetFiles())
}
return fmt.Sprintf("<Manifest count=%d>", count)
}
// Files returns all file entries from a loaded manifest.
func (m *manifest) Files() []*MFFilePath {
if m.pbInner == nil {
return nil
}
return m.pbInner.GetFiles()
}
// Signer returns the fingerprint of the key that made a loaded manifest's
// signature, which loading the manifest checked, or nil if the manifest is
// not signed.
func (m *manifest) Signer() []byte {
if len(m.pbOuter.GetSignature()) == 0 {
return nil
}
return m.pbOuter.GetSigner()
}
// CreatedAt returns the createdAt timestamp of a loaded manifest, or nil
// if the manifest has none.
func (m *manifest) CreatedAt() *Timestamp {
return m.pbInner.GetCreatedAt()
}
// signatureString generates the canonical string used for signing/verification.
// Format: MAGIC-UUID-MULTIHASH where UUID and multihash are hex-encoded.
// Requires pbOuter to be set with Uuid and Sha256 fields.
func (m *manifest) signatureString() (string, error) {
if m.pbOuter == nil {
return "", errOuterNotSet
}
if len(m.pbOuter.GetUuid()) == 0 {
return "", errUUIDNotSet
}
if len(m.pbOuter.GetSha256()) == 0 {
return "", errSHA256NotSet
}
mh, err := multihash.Encode(m.pbOuter.GetSha256(), multihash.SHA2_256)
if err != nil {
return "", fmt.Errorf("encode multihash: %w", err)
}
uuidStr := hex.EncodeToString(m.pbOuter.GetUuid())
mhStr := hex.EncodeToString(mh)
return fmt.Sprintf("%s-%s-%s", MAGIC, uuidStr, mhStr), nil
}