Skip to content
go-libfossil Artifact [eb3f4f864b]
Login

Artifact [eb3f4f864b]

Artifact eb3f4f864bc58ebad600607d8c4a2ec0c5d80383d668536fe7827b5b95264e54:


package deck

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
)

// VerifyZ validates a control artifact's trailing Z-card checksum. The blob
// may arrive PGP/SSH clearsigned (historical artifacts, e.g. Fossil's own
// 2007-era check-ins); stripClearsign isolates the inner content the Z card
// is computed over so the last-35-byte check lands on the Z card and not on a
// trailing signature block. The artifact's content-addressed hash is computed
// over the raw blob elsewhere and is unaffected by this stripping.
func VerifyZ(data []byte) error {
	return verifyZ(stripClearsign(data))
}

// HasValidZ reports whether data, clearsign-stripped, ends in a Z card whose
// checksum verifies. Every artifact type requires a Z card (§5.2), so data
// without one can never parse as a deck; this costs one MD5 pass and no card
// allocation.
func HasValidZ(data []byte) bool {
	body := stripClearsign(data)
	n := len(body)
	if n < 35 || body[n-1] != '\n' || body[n-35] != 'Z' || body[n-34] != ' ' {
		return false
	}
	return verifyZ(body) == nil
}

// verifyZ checks the Z card of already-clearsign-stripped artifact content.
// It mirrors fossil's verify_z_card (src/manifest.c): the Z card is located
// as the last 35 bytes beginning "Z ", the checksum covers every byte before
// it, and the stated digest is the 32 bytes at n-33 -- byte n-1, the final
// LF, is never read. §6.2: a body too short to hold a Z card, or one whose
// last 35 bytes are not a Z card, has the Z card absent, which this reports
// as success and leaves the required-card rule (§5.2) to judge; only a
// present-but-wrong checksum is an error here. Framing (§4.1) -- the missing
// final LF, residue after the Z card -- is the caller's to check, so this
// never rejects an input the rule it breaks should.
func verifyZ(body []byte) error {
	n := len(body)
	if n < 35 {
		return nil // no room for a Z card: absent
	}
	if body[n-35] != 'Z' || body[n-34] != ' ' {
		return nil // last 35 bytes are not a Z card: absent
	}
	// §6.2 compares byte-exact against the lowercase computeZ, so an
	// uppercase digest fails verification even though §6.1's grammar admits
	// it. body[n-1] is deliberately not read.
	stated := body[n-33 : n-1]
	computed := computeZ(body[:n-35])
	if computed != string(stated) {
		return fmt.Errorf("deck.VerifyZ: checksum mismatch: stated=%s computed=%s", stated, computed)
	}
	return nil
}

// isHexDigit reports whether c is a base-16 digit in either case. §6.1
// defines the Z payload's grammar as md5-token = 32hexdig-ci, a
// parse-accept grammar that is explicitly case-insensitive, so rejecting
// uppercase would refuse artifacts canonical fossil accepts. The
// case-insensitivity is structural only: §6.2 still requires an uppercase
// token to fail verification, which VerifyZ enforces by comparing
// byte-exact against the lowercase computeZ.
func isHexDigit(c byte) bool {
	switch {
	case c >= '0' && c <= '9':
		return true
	case c >= 'a' && c <= 'f':
		return true
	case c >= 'A' && c <= 'F':
		return true
	default:
		return false
	}
}

func computeZ(data []byte) string {
	h := md5.Sum(data)
	return hex.EncodeToString(h[:])
}