README
XPI Signing =========== .. sectnum:: .. contents:: Table of Contents 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. .. _`JAR Signing`: http://download.java.net/jdk7/archive/b125/docs/technotes/tools/solaris/jarsigner.html 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. .. _`on the Mozilla wiki`: https://wiki.mozilla.org/Add-ons/Extension_Signing 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. .. code:: yaml 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: .. code:: yaml 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: .. code:: json [ { "input": "Y2FyaWJvdW1hdXJpY2UK", "options": { "id": "myaddon@allizom.org" }, "keyid": "some_xpi_signer" } ] and `/sign/file` uses the format: .. code:: json [ { "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. .. _`COSE Algorithms`: https://www.iana.org/assignments/cose/cose.xhtml#table-header-algorithm-parameters .. _`Extension Signing Algorithm`: https://wiki.mozilla.org/Add-ons/Extension_Signing#Algorithm 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. .. code:: json [ { "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. .. code:: json [ { "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. .. code:: 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
- func VerifySignedFile(signedFile signer.SignedFile, truststore *x509.CertPool, opts Options) error
- type Metafile
- type Options
- type Recommendation
- type Signature
- type XPISigner
- func (s *XPISigner) Config() signer.Configuration
- func (s *XPISigner) GetDefaultOptions() interface{}
- func (s *XPISigner) MakeEndEntity(cn string, coseAlg *cose.Algorithm) (eeCert *x509.Certificate, eeKey crypto.PrivateKey, err error)
- func (s *XPISigner) ReadAndVerifyRecommendationFile(signedXPI []byte) (recFileBytes []byte, err error)
- func (s *XPISigner) SignData(sigfile []byte, options interface{}) (signer.Signature, error)
- func (s *XPISigner) SignFile(input []byte, options interface{}) (signedFile signer.SignedFile, err error)
Constants ¶
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 ¶
Functions ¶
func VerifySignedFile ¶
VerifySignedFile checks the XPI's PKCS7 signature and COSE signatures if present
Types ¶
type Metafile ¶
Metafile is a file to pack into a JAR at .Name with contents .Body
func (*Metafile) IsNameValid ¶
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 ¶
GetOptions takes a input interface and reflects it into a struct of options
func (*Options) Algorithms ¶
Algorithms validates and returns COSE algorithms
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
type Signature ¶
Signature is a detached PKCS7 signature or COSE SignMessage
func Unmarshal ¶
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 ¶
Marshal returns the base64 representation of a detached PKCS7 signature or COSE Sign Message
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