integrity

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: MIT Imports: 13 Imported by: 0

README

integrity

Parse Subresource Integrity metadata and registry checksums, and verify package streams while they are read. The module supports SHA-256, SHA-384, and SHA-512 using only the Go standard library, and MD5 and SHA-1 for verifying checksums registries already publish.

Install

Requires Go 1.26 or later.

go get github.com/git-pkgs/integrity

Parse and format SRI metadata

metadata, err := integrity.ParseSRI(
	"sha256-LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=",
)
if err != nil {
	return err
}

fmt.Println(metadata[0].Algorithm())
fmt.Println(metadata[0].Hex())
fmt.Println(integrity.FormatSRI(metadata))

ParseSRI accepts surrounding whitespace, metadata lists containing more than one hash, and standard or URL-safe base64 with optional padding. It ignores options because SRI does not define any yet. FormatSRI emits lower-case algorithm names, padded standard base64, and one space between entries. ParseHex constructs a digest from the hexadecimal values commonly stored by package caches and SBOMs.

All constructors check the digest length for its algorithm. Digest.Bytes returns a copy, while Digest.Hex and Digest.SRI return canonical encodings.

Verify a stream

expected, err := integrity.ParseSRI(
	"sha512-19P5Un6URGs8lrxEAotLVa/3dYfU1acO+ddf//wkvY4vz/SLa8BPaKNTTOYu6gjBR5JW8NwZfAdsBMYNWiS41A==",
)
if err != nil {
	return err
}

defer response.Body.Close()

reader, err := integrity.NewReader(response.Body, integrity.SHA256, integrity.SHA512)
if err != nil {
	return err
}

temporary, err := os.CreateTemp(filepath.Dir(destinationPath), ".package-*")
if err != nil {
	return err
}
temporaryPath := temporary.Name()
defer os.Remove(temporaryPath)
defer temporary.Close()

if _, err := io.Copy(temporary, reader); err != nil {
	return err
}

result := reader.Result()
if err := result.Verify(expected); err != nil {
	return err
}
if err := temporary.Close(); err != nil {
	return err
}
if err := os.Rename(temporaryPath, destinationPath); err != nil {
	return err
}
fmt.Println(result.Bytes)

The reader calculates each requested algorithm once and counts every byte returned to the caller. A result becomes complete after the reader observes EOF. Verification before EOF returns ErrIncomplete, including when the source was closed after a partial read.

SRI verification uses the strongest supported algorithm in the metadata list. Any digest using that algorithm can match. A matching weaker digest does not replace a mismatch from a stronger algorithm.

The caller must close the source and keep copied bytes private until verification succeeds. Write cache entries to a temporary file or object, then commit them after Verify returns nil. Reporting failures and cache policy remain with the caller.

Verify a registry checksum

Many registries publish a single hexadecimal checksum rather than SRI, sometimes in MD5 or SHA-1: npm's dist.shasum, Packagist's dist.shasum, Conda's md5, or a Vagrant box's checksum_type and checksum. ParseChecksum parses an algorithm-prefixed value such as sha256-<hex> or sha1:<hex>. ParseHex takes the algorithm separately. Result.VerifyDigest checks a completed stream against that one exact digest.

expected, err := integrity.ParseHex(integrity.SHA1, shasum)
if err != nil {
	return err
}

reader, err := integrity.NewReader(response.Body, expected.Algorithm())
if err != nil {
	return err
}
if _, err := io.Copy(temporary, reader); err != nil {
	return err
}
if err := reader.Result().VerifyDigest(expected); err != nil {
	return err
}

MD5 and SHA-1 are compatibility checks for metadata a publisher already supplied. Both are broken for collision resistance, so prefer a stronger checksum whenever the registry offers one, and never choose them for new integrity metadata. They are not SRI algorithms: ParseSRI rejects them, Digest.SRI returns an empty string for them, FormatSRI leaves them out, and Result.Verify refuses an SRI list containing them. Digest.Checksum gives the canonical <algorithm>-<hex> form for any algorithm.

SRI verification ranks algorithms by an explicit strength order (SHA-512, then SHA-384, then SHA-256), not by the order of the Algorithm constants.

Changes since v0.1.1

Added ParseChecksum, Digest.Checksum, and Result.VerifyDigest for algorithm-prefixed hexadecimal checksums. ParseHex and NewReader now support MD5 and SHA-1 for checking existing publisher metadata; SRI remains limited to SHA-256, SHA-384, and SHA-512. The minimum Go version increased from 1.25.6 to 1.26.

Development

Run the tests and race detector:

go test -race ./...

Run the linters and vulnerability scan:

make lint

Run each fuzz target:

go test -fuzz=FuzzParseSRI -fuzztime=30s
go test -fuzz=FuzzSRIRoundTrip -fuzztime=30s
go test -fuzz=FuzzParseChecksum -fuzztime=30s

Run the benchmarks:

go test -run '^$' -bench . -benchmem

License

MIT.

Documentation

Overview

Package integrity parses Subresource Integrity metadata and verifies streams against cryptographic digests.

It supports SHA-256, SHA-384, and SHA-512 without depending on HTTP, storage, or package-manager types. MD5 and SHA-1 are supported for verification only, to check checksums that registries already publish in those algorithms. They are never valid SRI and must not be chosen for new integrity metadata.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func FormatSRI

func FormatSRI(sri SRI) string

FormatSRI formats a metadata list with lower-case algorithm names, padded standard base64, and one space between entries. MD5 and SHA-1 digests have no SRI encoding and are left out.

Types

type Algorithm

type Algorithm uint8

Algorithm identifies a supported digest algorithm.

const (
	// SHA256 identifies SHA-256 digests.
	SHA256 Algorithm = iota
	// SHA384 identifies SHA-384 digests.
	SHA384
	// SHA512 identifies SHA-512 digests.
	SHA512
	// MD5 identifies MD5 digests. It is a compatibility check for checksums a
	// publisher already supplied and is not a Subresource Integrity
	// algorithm. Do not choose it for new integrity metadata.
	MD5
	// SHA1 identifies SHA-1 digests. It is a compatibility check for
	// checksums a publisher already supplied and is not a Subresource
	// Integrity algorithm. Do not choose it for new integrity metadata.
	SHA1
)

func (Algorithm) String

func (a Algorithm) String() string

String returns the lower-case name for the algorithm, as SRI and algorithm-prefixed checksums spell it.

type Digest

type Digest struct {
	// contains filtered or unexported fields
}

Digest is an immutable algorithm and digest-byte pair.

Use ParseHex, ParseChecksum or ParseSRI to construct a Digest. Bytes returns a copy of the stored bytes.

func ParseChecksum added in v0.2.0

func ParseChecksum(value string) (Digest, error)

ParseChecksum parses a single algorithm-prefixed hexadecimal checksum, such as "sha256-<hex>" or "sha1:<hex>", the form many registries publish instead of SRI. It accepts every supported algorithm, MD5 and SHA-1 included, and surrounding whitespace. Verify the result with Result.VerifyDigest.

func ParseHex

func ParseHex(algorithm Algorithm, value string) (Digest, error)

ParseHex parses a hexadecimal digest for algorithm. It accepts every supported algorithm, MD5 and SHA-1 included, for callers that already know which algorithm a registry's checksum uses.

func (Digest) Algorithm

func (d Digest) Algorithm() Algorithm

Algorithm returns the digest algorithm.

func (Digest) Bytes

func (d Digest) Bytes() []byte

Bytes returns a copy of the raw digest bytes.

func (Digest) Checksum added in v0.2.0

func (d Digest) Checksum() string

Checksum returns the canonical algorithm-prefixed hexadecimal encoding, which ParseChecksum accepts.

func (Digest) Equal

func (d Digest) Equal(other Digest) bool

Equal reports whether two digests use the same algorithm and contain the same raw bytes. The byte comparison takes constant time for equal-length values.

func (Digest) Hex

func (d Digest) Hex() string

Hex returns the canonical lower-case hexadecimal encoding.

func (Digest) SRI

func (d Digest) SRI() string

SRI returns the canonical Subresource Integrity encoding, or "" for an MD5 or SHA-1 digest, which SRI does not allow.

type ErrIncomplete

type ErrIncomplete struct{}

ErrIncomplete reports that verification was requested before the reader observed EOF.

func (*ErrIncomplete) Error

func (*ErrIncomplete) Error() string

Error implements error.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader calculates requested digests as bytes pass through it. Bytes remain unverified until a completed Result passes Verify.

Example
package main

import (
	"fmt"
	"io"
	"strings"

	"github.com/git-pkgs/integrity"
)

func main() {
	expected, err := integrity.ParseSRI("sha256-LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=")
	if err != nil {
		panic(err)
	}
	reader, err := integrity.NewReader(strings.NewReader("hello"), integrity.SHA256)
	if err != nil {
		panic(err)
	}
	if _, err := io.Copy(io.Discard, reader); err != nil {
		panic(err)
	}

	result := reader.Result()
	fmt.Println(result.Bytes, result.Complete)
	fmt.Println(result.Verify(expected) == nil)
}
Output:
5 true
true

func NewReader

func NewReader(source io.Reader, algorithms ...Algorithm) (*Reader, error)

NewReader returns a reader that calculates each requested algorithm once.

func (*Reader) Read

func (r *Reader) Read(p []byte) (int, error)

Read passes bytes from the source through each requested digest calculator.

func (*Reader) Result

func (r *Reader) Result() Result

Result returns a snapshot. Calling Result does not finish or reset the reader.

type Result

type Result struct {
	Digests  []Digest
	Bytes    int64
	Complete bool
}

Result is a snapshot of a Reader's calculated digests and progress.

func (Result) Verify

func (r Result) Verify(expected SRI) error

Verify applies the W3C SRI matching rule to a completed result. Only the strongest algorithm in expected is considered, and any digest using that algorithm may match. expected must hold only SRI algorithms; use VerifyDigest for an MD5 or SHA-1 checksum.

func (Result) VerifyDigest added in v0.2.0

func (r Result) VerifyDigest(expected Digest) error

VerifyDigest checks a completed result against one exact digest, such as a registry checksum from ParseChecksum or ParseHex. It accepts every supported algorithm, MD5 and SHA-1 included, and requires that the reader calculated that algorithm.

type SRI

type SRI []Digest

SRI is a Subresource Integrity metadata list.

func ParseSRI

func ParseSRI(value string) (SRI, error)

ParseSRI parses a whitespace-separated Subresource Integrity metadata list. It accepts standard and URL-safe base64 with optional padding and ignores options, which SRI currently leaves undefined.

Example
package main

import (
	"fmt"

	"github.com/git-pkgs/integrity"
)

func main() {
	metadata, err := integrity.ParseSRI("sha256-LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=")
	if err != nil {
		panic(err)
	}

	fmt.Println(metadata[0].Algorithm())
	fmt.Println(metadata[0].Hex())
}
Output:
sha256
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL