mar: go.mozilla.org/mar Index | Examples | Files

package mar

import "go.mozilla.org/mar"

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

The MAR format is specified at https://wiki.mozilla.org/Software_Update:MAR

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.

Code:

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

// make a new rsa key and add it for signature
rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
    log.Fatalf("rsa key generation failed with: %v", err)
}
marFile.PrepareSignature(rsaPrivKey, rsaPrivKey.Public())

// make a new ecdsa key and add it for signature
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())

// once both keys are added to the file, finalize the signature
err = marFile.FinalizeSignatures()
if err != nil {
    log.Fatalf("mar signature failed with error: %v", err)
}

// write out the MAR file
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)
}

// verify the signatures
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")

Output:

MAR file signed and parsed without error

Index

Examples

Package Files

doc.go errors.go firefoxkeys.go mar.go parser.go sign.go verify.go

Constants

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

Variables

var FirefoxReleasePublicKeys = map[string]string{

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

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

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

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

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

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

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

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

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

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

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

    "dep2_sha1": "" /* 452 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 Uses

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 Uses

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 Uses

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 https://wiki.mozilla.org/Software_Update:MAR 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 Uses

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 Uses

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 Uses

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 Uses

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 Uses

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 Uses

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 Uses

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 Uses

func New() *File

New returns an initialized MAR data structure

func (*File) AddAdditionalSection Uses

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

AddAdditionalSection stores data in the additional section of a MAR

func (*File) AddContent Uses

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 Uses

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 Uses

func (file *File) FinalizeSignatures() error

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

func (*File) Marshal Uses

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 Uses

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

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

func (*File) PrepareSignature Uses

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 Uses

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 Uses

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 Uses

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 Uses

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 Uses

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 Uses

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 Uses

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 Uses

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

Package mar imports 20 packages (graph) and is imported by 1 packages. Updated 2018-11-28. Refresh now. Tools for package owners.