contentsignaturepki

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: 28 Imported by: 1

README

Content Signature

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

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:

{
  "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.md 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.

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.

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

This signer doesn't support any option.

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(client *http.Client, x5u string) (body []byte, certs []*x509.Certificate, err error)

GetX5U retrieves a chain file of certs from upload location, parses and verifies it, then returns a byte slice of the response body and a 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

Types

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

Jump to

Keyboard shortcuts

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