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.
Documentation
Index ¶
Constants ¶
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 ¶
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 ¶
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 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