xpi

package
v0.0.0-...-529de6a Latest Latest
Warning

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

Go to latest
Published: Apr 15, 2024 License: MPL-2.0 Imports: 25 Imported by: 1

README

XPI Signing

XPI are zip files that contain Firefox extensions and addons. XPI signing uses the JAR Signing format to produce PKCS7 signatures protecting the integrity of addons.

A full description of how addon signing is implemented in Firefox can be found on the Mozilla wiki. This readme focuses on the autograph implementation.

Configuration

The type of this signer is xpi.

The XPI signer in Autograph supports four types of addons. A signer is configured to issue signatures for a given type using the mode parameter in the autograph configuration:

  • Regular addons use mode add-on
  • Regular addons with recommendations use mode add-on-with-recommendation
  • Mozilla Extensions use mode extension
  • Mozilla Components (aka. System Addons) use mode system add-on
  • Hotfixes use mode hotfix

Each signer must have a type, a mode and the certificate and private key of an intermediate CA issued by either the staging or root PKIs of AMO (refer to internal documentation to issue those, as they require access to private HSMs).

When a signature is requested, autograph will generate a private key and issue an end-entity certificate specifically for the signature request. The certificate is signed by the configured intermediate CA. The private key is thrown away right after the signature is issued. Note that if one XPI signer is including a recommendations file the other XPI signers should reserve that file path.

signers:
  - id: webextensions-rsa
    type: xpi
    mode: add-on
    recommendation:
      path: "recommendation.json"
    certificate: |
        -----BEGIN CERTIFICATE-----
        MIIH0zCCBbugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBvDELMAkGA1UEBhMCVVMx
        ...
        -----END CERTIFICATE-----
    privatekey: |
        -----BEGIN PRIVATE KEY-----
        MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDHV+bKFLr1p5FR
        ...
        -----END PRIVATE KEY-----

The signer can also include optional config params for an RSA key cache and recommendations file:

signers:
  - id: webextensions-rsa
    type: xpi
    mode: add-on
    recommendation:
      path: "recommendation.json"
      states:
        standard: true
        recommended: true
        partner: true
      relative_start: 0h
      duration: 26298h
    # RSA key gen is slow and CPU intensive, so we can optionally
    # pregenerate and cache keys with a worker pool
    rsacacheconfig:
      numkeys: 25
      numgenerators: 2
      generatorsleepduration: 1m
      fetchtimeout: 100ms
      statssamplerate: 1m
    certificate: |
        -----BEGIN CERTIFICATE-----
        MIIH0zCCBbugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBvDELMAkGA1UEBhMCVVMx
        ...
        -----END CERTIFICATE-----
    privatekey: |
        -----BEGIN PRIVATE KEY-----
        MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDHV+bKFLr1p5FR
        ...
        -----END PRIVATE KEY-----

Signature Request

Supports the /sign/data and /sign/file endpoints for data and file signing respectively. /sign/data uses the request format:

[
    {
        "input": "Y2FyaWJvdW1hdXJpY2UK",
        "options": {
            "id": "myaddon@allizom.org"
        },
        "keyid": "some_xpi_signer"
    }
]

and /sign/file uses the format:

[
    {
        "input": "Y2FyaWJvdW1hdXJpY2UK",
        "options": {
            "id": "myaddon@allizom.org",
            "cose_algorithms": [
                "ES256"
            ],
            "recommendations": [
                "standard",
                "recommended"
            ],
            "pkcs7_digest": "SHA256"
        },
        "keyid": "some_xpi_signer"
    }
]

Where options includes the following fields:

  • id is the required ID of the addon to sign for both data and file signing. It must be decided client side, and is generally a string that looks like an email address, but when longer than 64 characters can be the hexadecimal encoding of a sha256 hash. This signer doesn't care about the content of the string, and uses it as received when generating the end-entity signing cert.
  • pkcs7_digest is a required string representing a supported PKCS7 digest algorithm ("SHA1" or "SHA256"). Only /sign/file supports this field.
  • cose_algorithms is an optional array of strings representing supported COSE Algorithms (as of 2018-06-20 one of "ES256", "ES384", "ES512", or "PS256") to sign the XPI with in addition to the PKCS7 signature. Only /sign/file supports this field.
  • recommendations is an optional array of strings representing recommendation states to add to the recommendation file for XPI signers in add-on-with-recommendation mode. Only /sign/file supports this field.

The /sign/file endpoint takes a whole XPI encoded in base64. As described in Extension Signing Algorithm, it:

  • unzips the XPI
  • hashes each file to generate the manifest file manifest.mf
  • then when one or more supported COSE algorithms are in the options cose_algorithms field
    • writes the manifest file to cose.manifest
    • creates a COSE Sign Message and for each COSE algorithm:
      • generates an end entity cert and key from the signer's intermediate
      • signs the manifest with the end entity key using the COSE algorithm
      • adds the detached signature to the Sign Message
    • writes the CBOR-encoded Sign Message to cose.sig
    • hashes cose.manifest and cose.sig and adds them to the manifest file manifest.mf
  • hashes the manifest file to generate the signature file mozilla.sf
  • generates an RSA end entity cert from the signer's intermediate
  • uses the generated cert to sign the signature file and create a PKCS7 detached signature mozilla.rsa using the algorithm from pkcs7_digest
  • adds the generated manifest, signature, and detached signature files to the XPI META-INF/
  • repacks and returns the ZIP/XPI

The /sign/data endpoint generates the end entity cert and signs the signature file. The input field must contain the base64 encoding of a mozilla.sf signature file and returns the PKCS7 detached signature mozilla.rsa in the response signature field. The caller is then responsible for repacking the ZIP.

Signature Response

Data Signing

XPI signatures are binary files encoded using the PKCS7 format and stored in the file called mozilla.rsa in the META-INF folder of XPI archives.

Autograph returns the base64 representation of the mozilla.rsa file in its signature responses. Clients must decode the base64 from the autograph response and write it to a mozilla.rsa file.

[
  {
    "ref": "z4cfx4x6qymxsj9hiqbuqvn7",
    "type": "xpi",
    "signer_id": "webextensions-rsa",
    "public_key": "",
    "signature": "MIIRUQYJKoZIhvcNAQcCoIIRQjCCET4CAQExCTAHBgUr..."
  }
]

Note that the public_key field is empty in signature responses because PKCS7 files already contain the public certificate of the end-entity that issued the signature.

File Signing

Like the data signing except the signed XPI is returned in the signed_file field. Clients must decode the base64 from the autograph response and write it to a signed_addon.xpi file.

[
  {
    "ref": "z4cfx4x6qymxsj9hiqbuqvn7",
    "type": "xpi",
    "signer_id": "webextensions-rsa",
    "public_key": "",
    "signed_file": "MIIRUQYJKoZIhvcNAQcCoIIRQjCCET4CAQExCTAHBgUr..."
  }
]
Appendix A: Firefox add-on signature verification

This directed graphs represents the add-ons signature verification path in Firefox.

graph LR
  Firefox-->loadManifest
  loadManifest -->verifySignedState
  verifySignedState-->OpenSignedAppFile
  OpenSignedAppFile-->VerifyPK7Signature
  OpenSignedAppFile-->VerifyCOSESignature
  OpenSignedAppFile == return zip reader and signing cert ==> verifySignedState

  subgraph extension_jsm
  verifySignedState-->verifySignedStateForRoot
  verifySignedStateForRoot == Get signing cert and add-on ID ==>getSignedStatus
  getSignedStatus == if add-on ID != cert CN ==> SIGNEDSTATE_BROKEN
  getSignedStatus == if cert OU is Mozilla Components ==> SIGNEDSTATE_SYSTEM
  getSignedStatus == if cert OU is Mozilla Extensions==> SIGNEDSTATE_PRIVILEGED
  getSignedStatus == if signature valid ==> SIGNEDSTATE_SIGNED
  getSignedStatus == if signature invalid ==> NS_ERROR_SIGNED_JAR_*
  end

  subgraph pkcs7
  VerifyPK7Signature == Extract RSA signature ==> VerifySignature
  VerifyPK7Signature == Extract hash of SF signature file ==> VerifySignature
  VerifySignature == Extract Signing Certificate ==> VerifyCertificate
  VerifyCertificate == Get Trusted Root ==> BuildCertChain
  BuildCertChain == ERROR_EXPIRED_CERTIFICATE ==> Success
  Success --> VerifyPK7Signature
  BuildCertChain == else ==> Error
  Error --> VerifyPK7Signature
  end

  subgraph cose
  VerifyCOSESignature == Extract Signature ==> verify_cose_signature_ffi
  VerifyCOSESignature == Extract SF Signature file ==> verify_cose_signature_ffi
  VerifyCOSESignature == Get Trusted Root ==> verify_cose_signature_ffi
  end

  subgraph verify_manifest
  VerifyCOSESignature == List files to ignore==> VerifyAppManifest
  VerifyPK7Signature == List files to ignore==> VerifyAppManifest
  VerifyAppManifest--> ParseMF
  ParseMF == for each manifest entry ==> VerifyEntryContentDigest
  VerifyEntryContentDigest--> VerifyStreamContentDigest
  VerifyStreamContentDigest == entry digest matches manifest ==> NS_OK
  VerifyStreamContentDigest == else ==> NS_ERROR_SIGNED_JAR_something
  end

Documentation

Index

Constants

View Source
const (
	// Type of this signer is "xpi"
	Type = "xpi"

	// ModeAddOn represents a signer that issues signatures for
	// regular firefox add-ons and web extensions developed by anyone
	ModeAddOn = "add-on"

	// ModeAddOnWithRecommendation represents a signer that issues
	// signatures for regular firefox add-ons and web extensions
	// developed by anyone including a recommendation file
	ModeAddOnWithRecommendation = "add-on-with-recommendation"

	// ModeExtension represents a signer that issues signatures for
	// internal extensions developed by Mozilla
	ModeExtension = "extension"

	// ModeSystemAddOn represents a signer that issues signatures for
	// System Add-Ons developed by Mozilla
	ModeSystemAddOn = "system add-on"

	// ModeHotFix represents a signer that issues signatures for
	// Firefox HotFixes
	ModeHotFix = "hotfix"
)

Variables

This section is empty.

Functions

func VerifySignedFile

func VerifySignedFile(signedFile signer.SignedFile, truststore *x509.CertPool, opts Options, verificationTime time.Time) error

VerifySignedFile checks the XPI's PKCS7 signature and COSE signatures if present

Types

type Metafile

type Metafile struct {
	Name string
	Body []byte
}

Metafile is a file to pack into a JAR at .Name with contents .Body

func (*Metafile) IsNameValid

func (m *Metafile) IsNameValid() bool

IsNameValid checks whether a Metafile.Name is non-nil and begins with "META-INF/" functions taking Metafile args should validate names before reading or writing them to JARs

type Options

type Options struct {
	// ID is the add-on ID which is stored in the end-entity subject CN
	ID string `json:"id"`

	// COSEAlgorithms is an optional list of strings referring to IANA algorithms to use for COSE signatures
	COSEAlgorithms []string `json:"cose_algorithms"`

	// PKCS7Digest is a string required for /sign/file referring to algorithm to use for the PKCS7 signature digest
	PKCS7Digest string `json:"pkcs7_digest"`

	// Recommendations is an optional list of strings referring to
	// recommended states to add to the recommendations file
	// for signers in ModeAddOnWithRecommendation
	Recommendations []string `json:"recommendations"`
}

Options contains specific parameters used to sign XPIs

func GetOptions

func GetOptions(input interface{}) (options Options, err error)

GetOptions takes a input interface and reflects it into a struct of options

func (*Options) Algorithms

func (o *Options) Algorithms() (algs []*cose.Algorithm, err error)

Algorithms validates and returns COSE algorithms

func (*Options) CN

func (o *Options) CN(s *XPISigner) (cn string, err error)

CN returns the common name

func (*Options) PK7Digest

func (o *Options) PK7Digest() (asn1.ObjectIdentifier, error)

PK7Digest validates and return an ASN OID for a PKCS7 digest algorithm or an error

func (*Options) RecommendationStates

func (o *Options) RecommendationStates(allowedRecommendationStates map[string]bool) (states []string, err error)

RecommendationStates validates and returns allowed recommendation states algorithms from the request

type Recommendation

type Recommendation struct {
	// AddOnID is the ID of the extension this recommendation is
	// for. Must match the ID in the extension’s manifest.json
	AddOnID string `json:"addon_id"`

	// States is a list of strings for each state of an addon that
	// firefox understands
	States []string `json:"states"`

	// Validity is a pair of timestamps to expire a recommendation
	// after an appropriate amount of time, since the
	// recommendation is for a given version of the addon and it
	// will need to be reissued for new versions.
	Validity map[string]time.Time `json:"validity"`

	// SchemaVersion is a uint to allow gradual upgrades of the
	// recommendation file
	SchemaVersion int `json:"schema_version"`
}

Recommendation represents an Addon Recommendation file

func Recommend

func Recommend(addonID string, states []string, notBefore, notAfter time.Time) *Recommendation

Recommend returns a Recommendation for param addonID with param states

func UnmarshalRecommendation

func UnmarshalRecommendation(input []byte) (r *Recommendation, err error)

UnmarshalRecommendation parses a recommendation file from JSON

func (*Recommendation) Marshal

func (r *Recommendation) Marshal() ([]byte, error)

Marshal serializes a Recommendation to JSON

func (*Recommendation) Validate

func (r *Recommendation) Validate(allowedRecommendationStates map[string]bool) error

Validate checks a Recommendation's validity fields and state is in the allowed states

type Signature

type Signature struct {
	Data     []byte
	Finished bool
	// contains filtered or unexported fields
}

Signature is a detached PKCS7 signature or COSE SignMessage

func Unmarshal

func Unmarshal(signature string, content []byte) (sig *Signature, err error)

Unmarshal parses a PKCS7 struct from the base64 representation of a PKCS7 detached and content of the signed data or it parses a COSE Sign Message struct from the base64 representation of a CBOR encoded Sign Message

func (*Signature) Marshal

func (sig *Signature) Marshal() (string, error)

Marshal returns the base64 representation of a detached PKCS7 signature or COSE Sign Message

func (*Signature) String

func (sig *Signature) String() string

String returns a PEM encoded PKCS7 block

func (*Signature) VerifyWithChain

func (sig *Signature) VerifyWithChain(truststore *x509.CertPool) error

VerifyWithChain verifies an xpi signature using the provided truststore

func (*Signature) VerifyWithChainAt

func (sig *Signature) VerifyWithChainAt(truststore *x509.CertPool, verificationTime time.Time) error

VerifyWithChainAt verifies an xpi signature using the provided truststore at a given time.

When truststore is not nil, it also verifies the chain of trust of the end-entity signer cert to one of the root in the truststore.

type XPISigner

type XPISigner struct {
	signer.Configuration

	// OU is the organizational unit of the end-entity certificate
	// generated for each operation performed by this signer
	OU string

	// EndEntityCN is the subject CN of the end-entity certificate generated
	// for each operation performed by this signer. Most of the time
	// the ID will be left blank and provided by the requester of the
	// signature, but for hotfix signers, it is set to a specific value.
	EndEntityCN string
	// contains filtered or unexported fields
}

An XPISigner is configured to issue detached PKCS7 and COSE signatures for Firefox Add-ons of various types.

func New

func New(conf signer.Configuration, stats *signer.StatsClient) (s *XPISigner, err error)

New initializes an XPI signer using a configuration

func (*XPISigner) Config

func (s *XPISigner) Config() signer.Configuration

Config returns the configuration of the current signer

func (*XPISigner) GetDefaultOptions

func (s *XPISigner) GetDefaultOptions() interface{}

GetDefaultOptions returns default options of the signer

func (*XPISigner) MakeEndEntity

func (s *XPISigner) MakeEndEntity(cn string, coseAlg *cose.Algorithm) (eeCert *x509.Certificate, eeKey crypto.PrivateKey, err error)

MakeEndEntity generates a private key and certificate ready to sign a given XPI.

The subject CN of the certificate is taken from the `cn` string argument.

The key type is identical to the key type of the signer that issues the certificate when the optional `coseAlg` argument is nil. For example, if the signer uses an RSA 2048 key, so will the end-entity. When `coseAlg` is not nil, a key type of the COSE algorithm is generated.

The signature expiration date is copied over from the issuer.

The signed x509 certificate and private key are returned.

func (*XPISigner) ReadAndVerifyRecommendationFile

func (s *XPISigner) ReadAndVerifyRecommendationFile(signedXPI []byte) (recFileBytes []byte, err error)

ReadAndVerifyRecommendationFile reads and verifies the recommendation file from an XPI for a signer's config and returns the file bytes and an error when verification fails

func (*XPISigner) SignData

func (s *XPISigner) SignData(sigfile []byte, options interface{}) (signer.Signature, error)

SignData takes an input signature file and returns a PKCS7 or COSE detached signature

func (*XPISigner) SignFile

func (s *XPISigner) SignFile(input []byte, options interface{}) (signedFile signer.SignedFile, err error)

SignFile takes an unsigned zipped XPI file and returns a signed XPI file

Jump to

Keyboard shortcuts

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