Update golangci-lint to v2.12.2 with canonical config (closes #60)
check / check (push) Has been cancelled
check / check (push) Has been cancelled
Adopts golangci-lint v2.12.2 and the canonical .golangci.yml (default: all), and fixes all resulting findings across the tree. Two intended behavior changes: absent MFFilePath.Mtime is handled explicitly in freshen, list and export rather than dereferenced (main panicked); gpg positional key IDs now follow an explicit -- end-of-options marker. All twelve reworded user-visible error messages restored to byte-identical parity with main and pinned by tests.
This commit was merged in pull request #59.
This commit is contained in:
+79
-28
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -15,47 +16,80 @@ import (
|
||||
// MAGIC is the file format magic bytes prefix (rot13 of "MANIFEST").
|
||||
const MAGIC string = "ZNAVSRFG"
|
||||
|
||||
var (
|
||||
// errInnerNotSet is returned by generate when the inner manifest is
|
||||
// missing.
|
||||
errInnerNotSet = errors.New("internal error: pbInner not set")
|
||||
// errInternal is returned by generateOuter for the same condition.
|
||||
// The two messages differ, and both are load-bearing for callers that
|
||||
// match on text, so they are kept distinct.
|
||||
errInternal = errors.New("internal error")
|
||||
)
|
||||
|
||||
// nanosecondsInt32 converts t's nanosecond component to int32.
|
||||
// time.Time.Nanosecond is documented to return a value in [0, 999999999],
|
||||
// so the conversion cannot overflow. This sits directly in the manifest
|
||||
// content path: silently substituting a default would zero every entry's
|
||||
// mtime nanos and change the serialized bytes and their hash, so an
|
||||
// out-of-contract value is a programming error and panics rather than
|
||||
// being papered over.
|
||||
func nanosecondsInt32(t time.Time) int32 {
|
||||
n := t.Nanosecond()
|
||||
if n < 0 || n > math.MaxInt32 {
|
||||
panic(fmt.Sprintf(
|
||||
"mfer: time.Time.Nanosecond out of contract: %d", n))
|
||||
}
|
||||
|
||||
return int32(n)
|
||||
}
|
||||
|
||||
func newTimestampFromTime(t time.Time) *Timestamp {
|
||||
return &Timestamp{
|
||||
Seconds: t.Unix(),
|
||||
Nanos: int32(t.Nanosecond()),
|
||||
Nanos: nanosecondsInt32(t),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manifest) generate() error {
|
||||
if m.pbInner == nil {
|
||||
return errors.New("internal error: pbInner not set")
|
||||
return errInnerNotSet
|
||||
}
|
||||
|
||||
if m.pbOuter == nil {
|
||||
e := m.generateOuter()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
dat, err := proto.MarshalOptions{Deterministic: true}.Marshal(m.pbOuter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("serialize: marshal outer: %w", err)
|
||||
}
|
||||
m.output = bytes.NewBuffer([]byte(MAGIC))
|
||||
|
||||
m.output = bytes.NewBufferString(MAGIC)
|
||||
|
||||
_, err = m.output.Write(dat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("serialize: write output: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manifest) generateOuter() error {
|
||||
if m.pbInner == nil {
|
||||
return errors.New("internal error")
|
||||
return errInternal
|
||||
}
|
||||
|
||||
// Use fixed UUID if provided, otherwise generate a new one
|
||||
var manifestUUID uuid.UUID
|
||||
if len(m.fixedUUID) == 16 {
|
||||
if len(m.fixedUUID) == uuidLength {
|
||||
copy(manifestUUID[:], m.fixedUUID)
|
||||
} else {
|
||||
manifestUUID = uuid.New()
|
||||
}
|
||||
|
||||
m.pbInner.Uuid = manifestUUID[:]
|
||||
|
||||
innerData, err := proto.MarshalOptions{Deterministic: true}.Marshal(m.pbInner)
|
||||
@@ -65,23 +99,29 @@ func (m *manifest) generateOuter() error {
|
||||
|
||||
// Compress the inner data
|
||||
idc := new(bytes.Buffer)
|
||||
|
||||
zw, err := zstd.NewWriter(idc, zstd.WithEncoderLevel(zstd.SpeedBestCompression))
|
||||
if err != nil {
|
||||
return fmt.Errorf("serialize: create compressor: %w", err)
|
||||
}
|
||||
|
||||
_, err = zw.Write(innerData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("serialize: compress: %w", err)
|
||||
}
|
||||
|
||||
_ = zw.Close()
|
||||
|
||||
compressedData := idc.Bytes()
|
||||
|
||||
// Hash the compressed data for integrity verification before decompression
|
||||
h := sha256.New()
|
||||
if _, err := h.Write(compressedData); err != nil {
|
||||
|
||||
_, err = h.Write(compressedData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("serialize: hash write: %w", err)
|
||||
}
|
||||
|
||||
sha256Hash := h.Sum(nil)
|
||||
|
||||
m.pbOuter = &MFFileOuter{
|
||||
@@ -95,29 +135,40 @@ func (m *manifest) generateOuter() error {
|
||||
|
||||
// Sign the manifest if signing options are provided
|
||||
if m.signingOptions != nil && m.signingOptions.KeyID != "" {
|
||||
sigString, err := m.signatureString()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate signature string: %w", err)
|
||||
}
|
||||
|
||||
sig, err := gpgSign([]byte(sigString), m.signingOptions.KeyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sign manifest: %w", err)
|
||||
}
|
||||
m.pbOuter.Signature = sig
|
||||
|
||||
fingerprint, err := gpgGetKeyFingerprint(m.signingOptions.KeyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get key fingerprint: %w", err)
|
||||
}
|
||||
m.pbOuter.Signer = fingerprint
|
||||
|
||||
pubKey, err := gpgExportPublicKey(m.signingOptions.KeyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to export public key: %w", err)
|
||||
}
|
||||
m.pbOuter.SigningPubKey = pubKey
|
||||
return m.signOuter()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// signOuter signs the outer message with the configured GPG key and
|
||||
// embeds the signature, signer fingerprint, and public key.
|
||||
func (m *manifest) signOuter() error {
|
||||
sigString, err := m.signatureString()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate signature string: %w", err)
|
||||
}
|
||||
|
||||
sig, err := gpgSign([]byte(sigString), m.signingOptions.KeyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sign manifest: %w", err)
|
||||
}
|
||||
|
||||
m.pbOuter.Signature = sig
|
||||
|
||||
fingerprint, err := gpgGetKeyFingerprint(m.signingOptions.KeyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get key fingerprint: %w", err)
|
||||
}
|
||||
|
||||
m.pbOuter.Signer = fingerprint
|
||||
|
||||
pubKey, err := gpgExportPublicKey(m.signingOptions.KeyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to export public key: %w", err)
|
||||
}
|
||||
|
||||
m.pbOuter.SigningPubKey = pubKey
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user