Client Update Protocol (CUP)

CUP is an HTTP protocol extension used to secure communication with CUP-capable servers when TLS is untrustworthy or impractical for other reasons.

CUP provides:

  • Integrity of the client's request body.
  • Integrity of the server's response body.
  • Freshness of the server's response body.

CUP does not provide:

  • Integrity of the client's request headers or request line.
  • Integrity of the server's response headers.
  • Confidentiality of the client's request.
  • Confidentiality of the server's response.
  • Freshness of the client's request.

CUP requires:

  • Control of both the client and server.

Current CUP communications are secured by an ECDSA key pair. The client has the public key hardcoded into the application binary. The server signs request-response pairs with the private key, and the client verifies using the public key.

In Chromium, CUP keys are rotated annually. Each key pair has a corresponding version number. To interoperate with old clients, the server must keep all historical versions of the key, and be prepared to sign with any of them. The client should only keep a single version (whichever was most recent at compile time).

Details

Description

The server publishes an elliptic curve field/equation and a public key curve point to be used by the client. In practice, these values are hardcoded into each client at compile time.

For each request, the client assembles three components:

  1. The message body (the request body to be sent to the server).
  2. A small random number to be used as a client nonce for freshness (at least 32 bits).
  3. A code to identify the public key the client will use to verify this request. The client converts the public key id and nonce to a string: the public key is converted to decimal, and the nonce to hexadecimal (lowercase a-f).

The client stores the request body in a buffer, in UTF-8 format; it appends the keyid/nonce string to this buffer. It calculates a SHA-256 hash of this combined buffer, which it stores for validation later. It sends the request and the keyid/nonce string to the server.

The server receives the request body, public key id, and nonce; it performs the same appending operation, and computes the SHA-256 hash of the received data buffer.

The server attempts to find a matching ECDSA private key for the specified public key id, returning an HTTP error if no such private key exists. Finally, it assembles the response body.

Before sending, the server stores the response body (also in UTF-8) in a buffer. It appends the computed SHA-256 hash of the request body+keyid+nonce to the buffer. It then calculates an ECDSA signature over that combined buffer, using the server’s private key. It sends the ECDSA signature and the response body + client hash back to the user.

The client receives the response body, observed client hash, and ECDSA signature. The client compares the observed client hash to its stored request hash. If there is a mismatch, then either the request or response have been tampered with and the response is rejected.

The client concatenates the request hash to the response body, and verifies the signature using its public key. If verification fails, then either the request or response have been tampered with and the response is rejected.

HTTP Implementation

The request body is a POST body. The key ID and nonce, are transmitted in a query parameter appended to the requested URL, using the format &cup2key=%d:%u where the first parameter is the key ID, the second is the freshness nonce.

For debugging purposes, the request hash is sent to the server using a query parameter appended to the requested URL, using the format &cup2hreq=%s where %s is the lowercase hexadecimal value of the hash (in big-endian format).

The server returns the ECDSA signature and the request hash it computed in at least one of three forms:

  1. The X-Cup-Server-Proof HTTP header, with the value in the format signature:hash.
  2. The ETag HTTP header, with the value in the format of W/"signature:hash".
  3. The ETag HTTP header, with the value in the format of signature:hash.

If multiple forms are present in the response, clients should prefer form 1, falling back to form 2 only if form 1 is not present, and falling back to form 3 only if form 1 and 2 are not present.

In practice, multiple forms allow the communication to navigate different types of proxies that mutate request headers.

In all forms, signature is a DER-encoded ASN.1 sequence of “R” and “S”, rendered in lowercase hexadecimal representation.

In all forms, hash is a 256-bit value rendered in lowercase hexadecimal representation (big-endian).

K-Repetition

A grave danger in any system involving ECDSA is the danger of K repetition.

Computing an ECDSA signature starts with selecting a random 256-bit integer, called K. The combination of K and the public key are used to produce the first half of the signature, called R; the values of R, K, the private key, and the message digest are used to compute the other half of the signature, called S.

Because of this process, if the same value of K is chosen for two signatures, both signatures will have the same value for R. If a malicious user can acquire two messages that have different bodies but identical R values, a straightforward computation yields the server's private key.

Assuming that a good PRNG is used, and properly seeded, the probability of a collision is small even across a large number of signatures. However, regular key rotation is still recommended:

  1. Key material may be disclosed due to server compromise, and organizations should be prepared to remediate by performing a key rotation. Regularly exercising a key rotation process is important preparation.
  2. PRNGs are not always perfectly secure or properly seeded.

See Also

Previous documents describing CUP can be found at:

Chromium's implementation of CUP can be found in components/client_update_protocol.