README

Content Signature
=================

.. sectnum::
.. contents:: Table of Contents

Rationale
---------

As we rapidly increase the number of services that send configuration data to
Firefox agents, we also increase the probability of a service being
compromised to serve fraudulent data to our users. Content Signature implements
a signing protocol to protect the information sent from backend services to Firefox
user-agents.

Content signature adds a layer to TLS and certificate pinning. 
As we grow our service infrastructure, the risk of a vulnerability on our public 
endpoints increases, and an attacker could exploit a vulnerability to serve bad 
data from trusted sites directly. TLS with certificate pinning prevents bad actors
from creating fraudulent Firefox services, but does not reduce the impact a break-in
would have on our users. Content signature provides this extra layer.

Finally, content signature helps us use Content Delivery Networks (CDN) without
worrying that a compromise would end-up serving bad data to our users.
Signing content at the source reduces pressure on the infrastructure
and allows us to rely on vendors without worrying about data integrity.

For more information, refer to Julien Vehent's presentation linked below:

.. image:: https://img.youtube.com/vi/b2kPo8YdLTw/0.jpg
   :target: https://www.youtube.com/watch?v=b2kPo8YdLTw

Signature
---------

Content signatures are computed on data and served to Firefox either via a HTTP
response header or through a separate signature field in the data being transported.

Content signature have three main components: a signature mode (**mode**), an
ecdsa signature encoded with Base64 URL (**signature**) and the URL to a chain
of certificates that link to a trusted root (**x5u**). The example below shows
the JSON representation of a content signature:

.. code:: json

	{
	  "mode": "p384ecdsa",
	  "signature": "gZimwQAsuCj_JcgxrIjw1wzON8WYN9YKp3I5I9NmOgnGLOJJwHDxjOA2QEnzN7bXBGWFgn8HJ7fGRYxBy1SHiDMiF8VX7V49KkanO9MO-RRN1AyC9xmghuEcF4ndhQaI",
    "x5u": "https://foo.example.com/chains/certificates.pem"
	}

* **mode** is a suite of algorithms used to issue the signature. Two modes are supported:

  * **p384ecdsa** is the default used by firefox. It calculates signatures on the P-384
    NIST curve and uses SHA2-384 for hashes.

  * **p256ecdsa** uses the P-256 NIST curve and SHA256 for hashes

* **signature** contains the base64_url of the signature, computed using an elliptic
  curve and a hash algorithm that depends on the mode. The signature is issued by
  the private key of the end-entity cert referenced in the X5U. The decoded base64
  contains a binary string that is a DL/ECSSA representation of the R and S values
  (IEEE Std 1363-2000). This format concatenates R and S into a single value. To
  retrieve R and S, split the decoded base64 in the middle, and take R on the left
  and S on the right.

* **x5u** contains the location of the chain of trust that issued the signature.
  In practice, this file usually contains three certificates: the end-entity that
  issues the content signature, the intermediate issuer and the root of the
  Firefox private PKI. Firefox is configured to only accept signatures from the
  private PKI, as controlled via the `security.content.signature.root_hash`
  preference, where the value is the hexadecimal of the sha256 of the DER of the
  root certificate.

When Firefox verifies a content signature, it first retrieves the X5U and checks
the signature validity using the end-entity certificate, the signature, and the
content being protected. Firefox then verifies the chain of trust of the
end-entity links to a root cert with a hash matching the one in Firefox.
Finally, to prevent application A from signing content for application B,
Firefox verifies the subject alternate name of the end-entity certificate
matches the one it expects. This is hardcoded for each component that uses
content signature. Normandy, for example, uses the namespace
`normandy.content-signature.mozilla.org` and only end-entity certificates that
have this subject alternate name can issue signatures for the Normandy service.

Configuration
-------------

The type of this signer is **contentsignaturepki**.

Unlike the original **contentsignature** signer which was entirely manual, this
signer automates the generation of end-entity certificates at runtime,
and uploads chains to a pre-determined location (typically an S3 bucket).

To achieve this, it makes use of a Postgres database which must be configured
in the main autograph configuration file (refer to *docs/configuration.rst* for
details).
The database allows multiple autograph instances in a cluster to collaborate in
creating only one end-entity at a time.
Without a database, Autograph will also create new end-entity certificates at
startup because it has no way of knowing if one already exists.

This signer needs a PKI that has been previously initialized (ideally in an HSM
but it will also work with local keys). You can make a PKI using the *genpki* tool
under *tools/*.

When initialized (when autograph starts), it will looks into the database for an
end-entity that is currently valid (using the *validity* parameter) or create one
if none is found.
The end-entity public cert will be valid for *validity*+*clockskewtolerance*
amount of time. This is done to accomodate clients that may have bad clocks.
The standard is to use a validity of 30 days and another 30 days ahead and after
the validity period for tolerance, effectively creating certificates that are
valid for 90 days (30d of clock skew in the past, 30 days of validity, 30 days
of clock skew in the future).

Once the end-entity created, it is concatenated to the public certificate of the
intermediate and root of the PKI, then uploaded to *chainuploadlocation*, and
retrieved from *x5u* (these two locations may actually be different when we upload
to an S3 bucket but download from a CDN).

If this entire procedure succeeds, the signer is initialized with the end-entity
and starts processing requests.

.. code:: yaml

	signers:
    - id: normandy
      type: contentsignaturepki

      # rotate certs every 29.5 days, a lunar month
      validity: 708h

      # give +/- 30d of validity room for clients with bad clocks
      clockskewtolerance: 10m

      # upload cert chains to this location (file:// is really just for local dev)
      chainuploadlocation: file:///tmp/chains/
      # when using S3, make sure the relevant AWS credentials are set in the
      # environment that autograph runs in
      #chainuploadlocation: s3://net-mozaws-dev-content-signature/chains/

      # x5u is the path to the public dir where chains are stored. This MUST end
      # with a trailing slash because filenames will be appended to it.
      # x5u: https://s3.amazonaws.com/net-mozaws-dev-content-signature/chains/
      x5u: file:///tmp/chains/

      # label of the intermediate's private key in the HSM
      issuerprivkey: csinter1550858489

      # public certificate of the intermediate
      issuercert: |
        -----BEGIN CERTIFICATE-----
        MIICXDCCAeKgAwIBAgIIFYXBlGIHbWAwCgYIKoZIzj0EAwMwXzELMAkGA1UEBhMC
        VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRAwDgYDVQQK
        EwdNb3ppbGxhMRkwFwYDVQQDExBjc3Jvb3QxNTUwODU4NDg5MB4XDTE4MTIyMTE4
        MDEyOVoXDTI5MDIyMjE4MDEyOVowYDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNB
        MRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKEwdNb3ppbGxhMRowGAYD
        VQQDExFjc2ludGVyMTU1MDg1ODQ4OTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLW8
        5oxfe3iBBaw/tvb/DrBfzCL3i3fHxngkahC2LASsEfUhKPQEwE88pOyREcAjCXCo
        FSrv34Cx7H9FiItOpu837Z5d+Qax1tWHJg2qrNTm3A5VL0F14RbHbc665H0WQaNq
        MGgwDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA8GA1UdEwEB
        /wQFMAMBAf8wMAYDVR0eAQH/BCYwJKAiMCCCHi5jb250ZW50LXNpZ25hdHVyZS5t
        b3ppbGxhLm9yZzAKBggqhkjOPQQDAwNoADBlAjAyFx5dWkW1CMmAAatNH3tlFMuv
        UqjZk9QGiisGU7LGpsEs2GFK4k7Qs1fFNVVzHicCMQCX5GfEa/zBc7fJL+IP+XIZ
        AhaDpVhf9tReXSzilurgSy4u4gAE6nwdUFezm9iOsFg=
        -----END CERTIFICATE-----

      # public certificate of the root CA
      cacert: |
        -----BEGIN CERTIFICATE-----
        MIICKDCCAa+gAwIBAgIIFYXBlGCX7CAwCgYIKoZIzj0EAwMwXzELMAkGA1UEBhMC
        VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRAwDgYDVQQK
        EwdNb3ppbGxhMRkwFwYDVQQDExBjc3Jvb3QxNTUwODU4NDg5MB4XDTE4MTIyMDE4
        MDEyOVoXDTQ5MDIyMjE4MDEyOVowXzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNB
        MRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKEwdNb3ppbGxhMRkwFwYD
        VQQDExBjc3Jvb3QxNTUwODU4NDg5MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEVtXP
        Dx+XtUydct/YtvcOZDtndtLGu5kQtelIOS9TNISxbFbeJpa2dwuDQ+fvQ1Q1WNMY
        BHiOgWIoTKc+387yp6uijDxZBXAppIWUsMamdHKDiAyVHzFXpAiaXp69+Gvzozgw
        NjAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDwYDVR0TAQH/
        BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAuO8xbda+w4dq8iATflp4H5/0ubUcr
        9F24ABbpLdWtoMfyBJWeWPO61Qn0W+dNmqoCMHwSYgZMDvZK+uy9nqIyf+1h2eA4
        2OqlM2hZQeI/FpHm2ZevdMYcyqmQD0uBE1DTcg==
        -----END CERTIFICATE-----

Signature requests
------------------

This signer support both the `/sign/data` and `/sign/hash` endpoints. When
signing data, the base64 of the data being signed must be passed in the `input`
field of the JSON signing request. When signing hashes, the `input` field must
contain the base64 of the hash being signed.

.. code:: json

	[
		{
			"input": "Y2FyaWJvdW1hdXJpY2UK",
			"keyid": "some_content_signer"
		}
	]

This signer doesn't support any option.
Expand ▾ Collapse ▴

Documentation

Index

Constants

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

	// P256ECDSA defines an ecdsa content signature on the P-256 curve
	P256ECDSA = "p256ecdsa"

	// P256ECDSABYTESIZE defines the bytes length of a P256ECDSA signature
	P256ECDSABYTESIZE = 64

	// P384ECDSA defines an ecdsa content signature on the P-384 curve
	P384ECDSA = "p384ecdsa"

	// P384ECDSABYTESIZE defines the bytes length of a P384ECDSA signature
	P384ECDSABYTESIZE = 96

	// SignaturePrefix is a string preprended to data prior to signing
	SignaturePrefix = "Content-Signature:\x00"

	// CSNameSpace is a string that contains the namespace on which
	// content signature certificates are issued
	CSNameSpace = ".content-signature.mozilla.org"
)

Variables

This section is empty.

Functions

func GetX5U

func GetX5U(x5u string) (certs []*x509.Certificate, err error)

    GetX5U retrieves a chain of certs from upload location, parses and verifies it, then returns the slice of parsed certificates.

    func MakeTemplatedHash

    func MakeTemplatedHash(data []byte, curvename string) (alg string, out []byte)

      MakeTemplatedHash returns the templated sha384 of the input data. The template adds the string "Content-Signature:\x00" before the input data prior to calculating the sha384.

      The name of the hash function is returned, followed by the hash bytes

      func Verify

      func Verify(x5u, signature string, input []byte) error

        Verify takes the location of a cert chain (x5u), a signature in its raw base64_url format and input data. It then performs a verification of the signature on the input data using the end-entity certificate of the chain, and returns an error if it fails, or nil on success.

        Types

        type ContentSignature

        type ContentSignature struct {
        	R, S     *big.Int // fields must be exported for ASN.1 marshalling
        	HashName string
        	Mode     string
        	X5U      string
        	ID       string
        	Len      int
        	Finished bool
        }

          ContentSignature contains the parsed representation of a signature

          func Unmarshal

          func Unmarshal(signature string) (sig *ContentSignature, err error)

            Unmarshal parses a base64 url encoded content signature and returns it into a ContentSignature structure that can be verified.

            Note this function does not set the X5U value of a signature.

            func (*ContentSignature) Marshal

            func (sig *ContentSignature) Marshal() (str string, err error)

              Marshal returns the R||S signature is encoded in base64 URL safe, following DL/ECSSA format spec from IEEE Std 1363-2000.

              func (*ContentSignature) String

              func (sig *ContentSignature) String() string

              func (*ContentSignature) VerifyData

              func (sig *ContentSignature) VerifyData(input []byte, pubKey *ecdsa.PublicKey) bool

                VerifyData verifies a signatures on its raw, untemplated, input using a public key

                func (*ContentSignature) VerifyHash

                func (sig *ContentSignature) VerifyHash(hash []byte, pubKey *ecdsa.PublicKey) bool

                  VerifyHash verifies a signature on its templated hash using a public key

                  type ContentSigner

                  type ContentSigner struct {
                  	signer.Configuration
                  	IssuerPrivKey, IssuerPubKey string
                  	// contains filtered or unexported fields
                  }

                    ContentSigner implements an issuer of content signatures

                    func New

                    func New(conf signer.Configuration) (s *ContentSigner, err error)

                      New initializes a ContentSigner using a signer configuration

                      func (*ContentSigner) Config

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

                        Config returns the configuration of the current signer

                        func (*ContentSigner) GetDefaultOptions

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

                          GetDefaultOptions returns nil because this signer has no option

                          func (*ContentSigner) SignData

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

                            SignData takes input data, templates it, hashes it and signs it. The returned signature is of type ContentSignature and ready to be Marshalled.

                            func (*ContentSigner) SignHash

                            func (s *ContentSigner) SignHash(input []byte, options interface{}) (signer.Signature, error)

                              SignHash takes an input hash and returns a signature. It assumes the input data has already been hashed with something like sha384