package mfer import ( "bytes" "crypto/sha256" "errors" "fmt" "time" "github.com/google/uuid" "github.com/klauspost/compress/zstd" "google.golang.org/protobuf/proto" ) // MAGIC is the file format magic bytes prefix (rot13 of "MANIFEST"). const MAGIC string = "ZNAVSRFG" func newTimestampFromTime(t time.Time) *Timestamp { return &Timestamp{ Seconds: t.Unix(), Nanos: int32(t.Nanosecond()), } } func (m *manifest) generate() error { if m.pbInner == nil { return errors.New("internal error: pbInner not set") } 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)) _, 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") } // Use fixed UUID if provided, otherwise generate a new one var manifestUUID uuid.UUID if len(m.fixedUUID) == 16 { copy(manifestUUID[:], m.fixedUUID) } else { manifestUUID = uuid.New() } m.pbInner.Uuid = manifestUUID[:] innerData, err := proto.MarshalOptions{Deterministic: true}.Marshal(m.pbInner) if err != nil { return fmt.Errorf("serialize: marshal inner: %w", err) } // 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 { return fmt.Errorf("serialize: hash write: %w", err) } sha256Hash := h.Sum(nil) m.pbOuter = &MFFileOuter{ InnerMessage: compressedData, Size: int64(len(innerData)), Sha256: sha256Hash, Uuid: manifestUUID[:], Version: MFFileOuter_VERSION_ONE, CompressionType: MFFileOuter_COMPRESSION_ZSTD, } // 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 nil }