Back to

Package mar

v0.0.0 (c51ce05)
Latest Go to latest
Published: Jan 24, 2020 | License: MPL-2.0 | Module:


Package mar implements support for the Mozilla ARchive format used by the Application Update Service of Firefox.

The MAR format is specified at

This package is primarily used to sign MARs by first parsing them via the Unmarshal function, then signing them with either RSA or ECDSA keys.

// read a MAR file from disk
input, _ := ioutil.ReadFile("/path/to/firefox.mar")
// parse it
_ = mar.Unmarshal(input, &file)
// prepare a signature using a given RSA key
file.PrepareSignature(rsaKey, rsaKey.Public())
// sign
_ = file.FinalizeSignatures()
// write out the signed mar file
output, _ := file.Marshal()
ioutil.WriteFile("/path/to/signed_firefox.mar", output, 0644)

It can also be used to create new MARs and manipulate existing ones.

// create a new MAR
marFile := mar.New()
// Add data to the content section
marFile.AddContent([]byte("cariboumaurice"), "/foo/bar", 640)
// Add product information to the additional section
m.AddProductInfo("caribou maurice v1.2")
// Add random data to the additional section
m.AddAdditionalSection([]byte("foo bar baz"), uint32(1664))

The MAR data structure exposes all internal fields, including offsets, sizes, etc. Those fields can be manipulated directly, but are ignored and recomputed when marshalling.

The parser is fairly secure and will refuse to parse files that have duplicate content or try to reference the same data chunk multiple times. Doing so requires keeping track of previously parsed sections of a MAR, which induces a significant memory cost. Be mindful of allocated memory if you're going to parse a lot of very large MAR before the garbage collector has a chance to reclaim memory from previously parsed files.

Various limits are enforced, take a look at errors.go for the details.



marFile := mar.New()
marFile.AddContent([]byte("cariboumaurice"), "/foo/bar", 640)

rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
	log.Fatalf("rsa key generation failed with: %v", err)
marFile.PrepareSignature(rsaPrivKey, rsaPrivKey.Public())

ecdsaPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
	log.Fatalf("ecdsa key generation failed with: %v", err)
marFile.PrepareSignature(ecdsaPrivKey, ecdsaPrivKey.Public())

err = marFile.FinalizeSignatures()
if err != nil {
	log.Fatalf("mar signature failed with error: %v", err)

outputMar, err := marFile.Marshal()
if err != nil {
	log.Fatalf("mar marshalling failed with error: %v", err)

// reparse the MAR to make sure it goes through fine
var reparsedMar mar.File
err = mar.Unmarshal(outputMar, &reparsedMar)
if err != nil {
	log.Fatalf("mar unmarshalling failed with error: %v", err)

err = reparsedMar.VerifySignature(rsaPrivKey.Public())
if err != nil {
	log.Fatalf("failed to verify rsa signature: %v", err)
err = reparsedMar.VerifySignature(ecdsaPrivKey.Public())
if err != nil {
	log.Fatalf("failed to verify ecdsa signature: %v", err)

fmt.Printf("MAR file signed and parsed without error")
MAR file signed and parsed without error



Package Files


const (
	// MarIDLen is the length of the MAR ID header.
	// A MAR file starts with 4 bytes containing the MAR ID, typically "MAR1"
	MarIDLen = 4

	// OffsetToIndexLen is the length of the offset to index value.
	// The MAR file continues with the position of the index relative
	// to the beginning of the file
	OffsetToIndexLen = 4

	// FileSizeLen is a uint64 that contains the total size of the MAR in bytes
	FileSizeLen = 8

	// SignaturesHeaderLen is the length of the signatures header that
	// contains the number of signatures in the MAR
	SignaturesHeaderLen = 4

	// SignatureEntryHeaderLen is the length of the header of each signature entry
	// Each signature entry contains an algorithm and a size, each on 4 bytes
	SignatureEntryHeaderLen = 8

	// AdditionalSectionsHeaderLen is the length of the additional sections header
	// Optional additional sections can be added, their number is stored on 4 bytes
	AdditionalSectionsHeaderLen = 4

	// AdditionalSectionsEntryHeaderLen is the length of the header of each
	// additional section, containing a block size and identifier on 4 bytes each
	AdditionalSectionsEntryHeaderLen = 8

	// IndexHeaderLen is the length of the index header
	// The size of the index is stored in a header on 4 bytes
	IndexHeaderLen = 4

	// IndexEntryHeaderLen is the length of the header of each index entry.
	// Each index entry contains a header with an offset to content (relative to
	// the beginning of the file), a content size and permission flags,
	// each on 4 bytes
	IndexEntryHeaderLen = 12

	// BlockIDProductInfo is the ID of a Product Information Block
	// in additional sections
	BlockIDProductInfo = 1
const (
	// SigAlgRsaPkcs1Sha1 is the ID of a signature of type RSA-PKCS1-SHA1
	SigAlgRsaPkcs1Sha1 = 1

	// SigAlgRsaPkcs1Sha384 is the ID of a signature of type RSA-PKCS1-SHA384
	SigAlgRsaPkcs1Sha384 = 2

	// SigAlgEcdsaP256Sha256 is the ID of a signature of type ECDSA on NIST curve P256 with SHA256
	SigAlgEcdsaP256Sha256 = 3

	// SigAlgEcdsaP384Sha384 is the ID of a signature of type ECDSA on NIST curve P384 with SHA384
	SigAlgEcdsaP384Sha384 = 4

Signature types


var FirefoxReleasePublicKeys = map[string]string{

	"release1_sha384": "" /* 799 byte string literal not displayed */,

	"release2_sha384": "" /* 799 byte string literal not displayed */,

	"release1_sha1": "" /* 450 byte string literal not displayed */,

	"release2_sha1": "" /* 450 byte string literal not displayed */,

	"nightly1_sha384": "" /* 799 byte string literal not displayed */,

	"nightly2_sha384": "" /* 799 byte string literal not displayed */,

	"nightly1_sha1": "" /* 450 byte string literal not displayed */,

	"nightly2_sha1": "" /* 450 byte string literal not displayed */,

	"dep1_sha384": "" /* 799 byte string literal not displayed */,

	"dep2_sha384": "" /* 799 byte string literal not displayed */,

	"dep1_sha1": "" /* 450 byte string literal not displayed */,

	"dep2_sha1": "" /* 450 byte string literal not displayed */,

FirefoxReleasePublicKeys contains a map of PEM encoded public keys used to verify signatures on MAR files. This map is automatically generated, do not edit it by hand!

func Hash

func Hash(input []byte, sigalg uint32) (output []byte, h crypto.Hash, err error)

Hash takes an input and a signature algorithm and returns its hashed value

func Sign

func Sign(key crypto.PrivateKey, rand io.Reader, digest []byte, sigalg uint32) (sigData []byte, err error)

Sign signs digest with the private key, possibly using entropy from rand

func Unmarshal

func Unmarshal(input []byte, file *File) error

Unmarshal takes an unparsed MAR file as input and parses it into a File struct. The MAR format is described at but don't believe everything it says, because the format has changed over the years to support more fields, and of course the MarID has not changed since. There's a bit of magic in this function to detect which version of a MAR we're dealing with, and store that in the Revision field of the file. 2005 is an old MAR, 2012 is a current one with signatures and additional sections.

func VerifyHashSignature

func VerifyHashSignature(signature []byte, digest []byte, hashAlg crypto.Hash, key crypto.PublicKey) error

VerifyHashSignature takes a signature, the digest of a signed MAR block, a hash algorithm and a public key and returns nil if a valid signature is found, or an error if it isn't

func VerifySignature

func VerifySignature(input []byte, signature []byte, sigalg uint32, key crypto.PublicKey) error

VerifySignature takes a signed block, a signature, an algorithm id and a public key and returns nil if the signature verifies, or an error if it does not

type AdditionalSection

type AdditionalSection struct {
	AdditionalSectionEntryHeader `json:"additional_section_entry" yaml:"additional_section_entry"`
	// Data contains the additional section data
	Data []byte `json:"data" yaml:"-"`

AdditionalSection is a single additional section on the MAR file

type AdditionalSectionEntryHeader

type AdditionalSectionEntryHeader struct {
	// BlockSize is the size of the additional section in bytes, including
	// the header and the following data. You need to substract the header length
	// to parse just the data..
	BlockSize uint32 `json:"block_size" yaml:"block_size"`
	// BlockID is the identifier of the block.
	// BlockIDProductInfo (1) for Product Information
	BlockID uint32 `json:"block_id" yaml:"block_id"`

AdditionalSectionEntryHeader is the header of each additional section that contains the block size and ID

type AdditionalSectionsHeader

type AdditionalSectionsHeader struct {
	// NumAdditionalSections is the count of additional sections
	NumAdditionalSections uint32 `json:"num_additional_sections" yaml:"num_additional_sections"`

AdditionalSectionsHeader contains the number of additional sections in the MAR file

type Entry

type Entry struct {
	// Data contains the raw data of the entry. It may still be compressed.
	Data []byte `json:"data" yaml:"-"`
	// IsCompressed is set to true if the Data is compressed with xz
	IsCompressed bool `json:"is_compressed" yaml:"-"`

Entry is a single file entry in the MAR file. If IsCompressed is true, the content is compressed with xz

type File

type File struct {
	MarID                    string                   `json:"mar_id" yaml:"mar_id"`
	OffsetToIndex            uint32                   `json:"offset_to_index" yaml:"offset_to_index"`
	Size                     uint64                   `json:"size" yaml:"size"`
	ProductInformation       string                   `json:"product_information,omitempty" yaml:"product_information,omitempty"`
	SignaturesHeader         SignaturesHeader         `json:"signature_header" yaml:"signature_header"`
	Signatures               []Signature              `json:"signatures" yaml:"signatures"`
	AdditionalSectionsHeader AdditionalSectionsHeader `json:"additional_sections_header" yaml:"additional_sections_header"`
	AdditionalSections       []AdditionalSection      `json:"additional_sections" yaml:"additional_sections"`
	IndexHeader              IndexHeader              `json:"index_header" yaml:"index_header"`
	Index                    []IndexEntry             `json:"index" yaml:"index"`
	Content                  map[string]Entry         `json:"-" yaml:"-"`
	Revision                 int                      `json:"revision" yaml:"revision"`
	// contains filtered or unexported fields

File is a parsed MAR file.

func New

func New() *File

New returns an initialized MAR data structure

func (*File) AddAdditionalSection

func (file *File) AddAdditionalSection(data []byte, blockID uint32)

AddAdditionalSection stores data in the additional section of a MAR

func (*File) AddContent

func (file *File) AddContent(data []byte, name string, flags uint32) error

AddContent stores content in a MAR and creates a new entry in the index

func (*File) AddProductInfo

func (file *File) AddProductInfo(productInfo string)

AddProductInfo adds a product information string (typically, the version of firefox) into the additional sections of a MAR

func (*File) FinalizeSignatures

func (file *File) FinalizeSignatures() error

FinalizeSignatures calculates RSA signatures on a MAR file and stores them in the Signatures slice

func (*File) Marshal

func (file *File) Marshal() ([]byte, error)

Marshal returns an []byte of the marshalled MAR file that follows the expected MAR binary format. It expects a properly constructed MAR object with the index and content already in place. It also should already be signed, as the output of this function can no longer be modified.

func (*File) MarshalForSignature

func (file *File) MarshalForSignature() ([]byte, error)

MarshalForSignature returns an []byte of the data to be signed, or verified

func (*File) PrepareSignature

func (file *File) PrepareSignature(key crypto.PrivateKey, pubkey crypto.PublicKey) error

PrepareSignature adds a new signature header to a MAR file but does not sign yet. You have to call FinalizeSignature to actually sign the MAR file.

func (*File) VerifySignature

func (file *File) VerifySignature(key crypto.PublicKey) error

VerifySignature attempts to verify signatures in the MAR file using the provided public key until one of them passes. A valid signature is indicated by returning a nil error.

func (*File) VerifyWithFirefoxKeys

func (file *File) VerifyWithFirefoxKeys() (keys []string, isSigned bool, err error)

VerifyWithFirefoxKeys checks each signature in the MAR file against the list of known Firefox signing keys, and returns isSigned = true if at least one signature validates against a known key. It also returns the names of the signing keys in an []string

type IndexEntry

type IndexEntry struct {
	IndexEntryHeader `json:"index_entry" yaml:"index_entry"`
	// Filename is the name of the file being indexed
	FileName string `json:"file_name" yaml:"file_name"`

IndexEntry is a single index entry in the MAR index

type IndexEntryHeader

type IndexEntryHeader struct {
	// OffsetToContent is the position in bytes of the entry data relative
	// to the start of the MAR file
	OffsetToContent uint32 `json:"offset_to_content" yaml:"offset_to_content"`
	// Size is the size of the data in bytes
	Size uint32 `json:"size" yaml:"size"`
	// Flags is the file permission bits in standard unix-style format
	Flags uint32 `json:"flags" yaml:"flags"`

IndexEntryHeader is the header of each index entry that contains the offset to content, size and flags

type IndexHeader

type IndexHeader struct {
	// Size is the size of the index entries, in bytes
	Size uint32 `json:"size" yaml:"size"`

IndexHeader is the size of the index section of the MAR file, in bytes

type Signature

type Signature struct {
	SignatureEntryHeader `json:"signature_entry" yaml:"signature_entry"`
	// Algorithm is a string that represents the signing algorithm name
	Algorithm string `json:"algorithm" yaml:"algorithm"`
	// Data is the signature bytes
	Data []byte `json:"data" yaml:"-"`
	// contains filtered or unexported fields

Signature is a single signature on the MAR file

type SignatureEntryHeader

type SignatureEntryHeader struct {
	// AlgorithmID is either SigAlgRsaPkcs1Sha1 (1) or SigAlgRsaPkcs1Sha384 (2)
	AlgorithmID uint32 `json:"algorithm_id" yaml:"algorithm_id"`
	// Size is the size of the signature data in bytes
	Size uint32 `json:"size" yaml:"size"`

SignatureEntryHeader is the header of each signature entry that contains the Algorithm ID and Size

type SignaturesHeader

type SignaturesHeader struct {
	// NumSignatures is the count of signatures
	NumSignatures uint32 `json:"num_signatures" yaml:"num_signatures"`

SignaturesHeader contains the number of signatures in the MAR file

Documentation was rendered with GOOS=linux and GOARCH=amd64.

Jump to identifier

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to identifier