Joshua Pawlicki | 32ec7d8 | 2020-07-29 21:42:06 | [diff] [blame] | 1 | # Client Update Protocol (CUP) |
| 2 | |
| 3 | CUP is an HTTP protocol extension used to secure communication with CUP-capable |
| 4 | servers when TLS is untrustworthy or impractical for other reasons. |
| 5 | |
| 6 | CUP provides: |
| 7 | * Integrity of the client's request body. |
| 8 | * Integrity of the server's response body. |
| 9 | * Freshness of the server's response body. |
| 10 | |
| 11 | CUP does not provide: |
| 12 | * Integrity of the client's request headers or request line. |
| 13 | * Integrity of the server's response headers. |
| 14 | * Confidentiality of the client's request. |
| 15 | * Confidentiality of the server's response. |
| 16 | * Freshness of the client's request. |
| 17 | |
| 18 | CUP requires: |
| 19 | * Control of both the client and server. |
| 20 | |
| 21 | Current CUP communications are secured by an ECDSA key pair. The client has the |
| 22 | public key hardcoded into the application binary. The server signs |
| 23 | request-response pairs with the private key, and the client verifies using the |
| 24 | public key. |
| 25 | |
| 26 | In Chromium, CUP keys are rotated annually. Each key pair has a corresponding |
| 27 | version number. To interoperate with old clients, the server must keep all |
| 28 | historical versions of the key, and be prepared to sign with any of them. The |
| 29 | client should only keep a single version (whichever was most recent at compile |
| 30 | time). |
| 31 | |
| 32 | ## Details |
| 33 | |
| 34 | ### Description |
| 35 | The server publishes an elliptic curve field/equation and a public key curve |
| 36 | point to be used by the client. In practice, these values are hardcoded into |
| 37 | each client at compile time. |
| 38 | |
| 39 | For each request, the client assembles three components: |
| 40 | 1. The message body (the request body to be sent to the server). |
Joshua Pawlicki | 20800e7 | 2021-09-23 19:13:55 | [diff] [blame] | 41 | 2. A random value to be used as a nonce for freshness (at least 256 bits). |
Joshua Pawlicki | 32ec7d8 | 2020-07-29 21:42:06 | [diff] [blame] | 42 | 3. A code to identify the public key the client will use to verify this |
| 43 | request. The client converts the public key id and nonce to a string: the |
Joshua Pawlicki | 20800e7 | 2021-09-23 19:13:55 | [diff] [blame] | 44 | public key is converted to decimal, and the nonce to any url-safe encoding. |
Joshua Pawlicki | 32ec7d8 | 2020-07-29 21:42:06 | [diff] [blame] | 45 | |
| 46 | The client stores the request body in a buffer, in UTF-8 format; it |
| 47 | appends the keyid/nonce string to this buffer. It calculates a SHA-256 hash of |
| 48 | this combined buffer, which it stores for validation later. It sends the |
| 49 | request and the keyid/nonce string to the server. |
| 50 | |
| 51 | The server receives the request body, public key id, and nonce; it performs |
| 52 | the same appending operation, and computes the SHA-256 hash of the received data |
| 53 | buffer. |
| 54 | |
| 55 | The server attempts to find a matching ECDSA private key for the specified |
| 56 | public key id, returning an HTTP error if no such private key exists. Finally, |
| 57 | it assembles the response body. |
| 58 | |
| 59 | Before sending, the server stores the response body (also in UTF-8) in a |
| 60 | buffer. It appends the computed SHA-256 hash of the request body+keyid+nonce to |
| 61 | the buffer. It then calculates an ECDSA signature over that combined buffer, |
| 62 | using the server’s private key. It sends the ECDSA signature and the response |
| 63 | body + client hash back to the user. |
| 64 | |
| 65 | The client receives the response body, observed client hash, and ECDSA signature. |
| 66 | The client compares the observed client hash to its stored request hash. If |
| 67 | there is a mismatch, then either the request or response have been tampered with |
| 68 | and the response is rejected. |
| 69 | |
| 70 | The client concatenates the request hash to the response body, and verifies the |
| 71 | signature using its public key. If verification fails, then either the request |
| 72 | or response have been tampered with and the response is rejected. |
| 73 | |
| 74 | ### HTTP Implementation |
| 75 | The request body is a POST body. The key ID and nonce, are transmitted in a |
| 76 | query parameter appended to the requested URL, using the format `&cup2key=%d:%u` |
| 77 | where the first parameter is the key ID, the second is the freshness nonce. |
| 78 | |
| 79 | For debugging purposes, the request hash is sent to the server using a query |
| 80 | parameter appended to the requested URL, using the format `&cup2hreq=%s` where |
| 81 | %s is the lowercase hexadecimal value of the hash (in big-endian format). |
| 82 | |
| 83 | The server returns the ECDSA signature and the request hash it computed in at |
| 84 | least one of three forms: |
| 85 | 1. The `X-Cup-Server-Proof` HTTP header, with the value in the format |
| 86 | `signature:hash`. |
| 87 | 2. The `ETag` HTTP header, with the value in the format of |
| 88 | `W/"signature:hash"`. |
| 89 | 3. The `ETag` HTTP header, with the value in the format of `signature:hash`. |
| 90 | |
| 91 | If multiple forms are present in the response, clients should prefer form 1, |
| 92 | falling back to form 2 only if form 1 is not present, and falling back to form 3 |
| 93 | only if form 1 and 2 are not present. |
| 94 | |
| 95 | In practice, multiple forms allow the communication to navigate different types |
| 96 | of proxies that mutate request headers. |
| 97 | |
| 98 | In all forms, `signature` is a DER-encoded ASN.1 sequence of "R" and "S", |
| 99 | rendered in lowercase hexadecimal representation. |
| 100 | |
| 101 | In all forms, `hash` is a 256-bit value rendered in lowercase hexadecimal |
| 102 | representation (big-endian). |
| 103 | |
| 104 | ### K-Repetition |
| 105 | A grave danger in any system involving ECDSA is the danger of K repetition. |
| 106 | |
| 107 | Computing an ECDSA signature starts with selecting a random 256-bit integer, |
| 108 | called K. The combination of K and the public key are used to produce the first |
| 109 | half of the signature, called R; the values of R, K, the private key, and the |
| 110 | message digest are used to compute the other half of the signature, called S. |
| 111 | |
| 112 | Because of this process, if the same value of K is chosen for two signatures, |
| 113 | both signatures will have the same value for R. If a malicious user can acquire |
| 114 | two messages that have different bodies but identical R values, a |
| 115 | straightforward computation yields the server's private key. |
| 116 | |
| 117 | Assuming that a good PRNG is used, and properly seeded, the probability of a |
| 118 | collision is small even across a large number of signatures. However, regular |
| 119 | key rotation is still recommended: |
| 120 | 1. Key material may be disclosed due to server compromise, and organizations |
| 121 | should be prepared to remediate by performing a key rotation. Regularly |
| 122 | exercising a key rotation process is important preparation. |
| 123 | 2. PRNGs are not always perfectly secure or properly seeded. |
| 124 | |
| 125 | ## See Also |
| 126 | Previous documents describing CUP can be found at: |
| 127 | * [Original Public Design](https://github.com/google/omaha/blob/master/doc/ClientUpdateProtocol.md) |
| 128 | * [ECDSA Extension](https://github.com/google/omaha/blob/master/doc/ClientUpdateProtocolEcdsa.md) |
| 129 | |
| 130 | Chromium's implementation of CUP can be found in |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 131 | [components/client\_update\_protocol](https://source.chromium.org/chromium/chromium/src/+/main:components/client_update_protocol/). |
Joshua Pawlicki | 32ec7d8 | 2020-07-29 21:42:06 | [diff] [blame] | 132 | |