Skip to content

Commit 4d0dc2c

Browse files
committed
Propose an RFC for protocol negotiation
This RFC describes how Cerberus devices would negotiate how to speak Cerberus. Not only does it describe a versioning scheme, but it gives us an easy way to add new negotiable parameters, such as new cryptographic algorithms, or detection of vendor-defined extensions. Signed-off-by: Miguel Young de la Sota <[email protected]>
1 parent d9c9cc3 commit 4d0dc2c

File tree

1 file changed

+364
-0
lines changed

1 file changed

+364
-0
lines changed
Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
* Name: Protocol_Negotiation
2+
* Date: 2021-10-06
3+
* Pull Request: [#24](https://github.com/opencomputeproject/Security/pull/24)
4+
5+
# Objective
6+
7+
This proposal is a merger of the
8+
[Capabilities2](https://github.com/opencomputeproject/Security/pull/21) and
9+
[Version_Detection](https://github.com/opencomputeproject/Security/pull/23)
10+
RFCs, which are found to have sufficient overlap that a single message should be
11+
able to cover both use-cases.
12+
13+
This proposal defines the following:
14+
1. A message for negotiating a collection of key-value pairs between a host and
15+
a device.
16+
2. A versioning definition mechanism for the Cerberus Challenge Protocol, which
17+
is defined in terms of the protocol's Git history. It is negotiated using the
18+
aforementioned message.
19+
20+
These two mechanisms are closely related: we want versioning to determine the
21+
layout of messages, which means a version must be negotiated. Originally, the
22+
two proposals were essentially disjoint negotiations. However, the following
23+
observations imply they are the same idea:
24+
- The version negotiation message cannot itself be versioned via the usual
25+
versioning mechanism (self-evident: this is a bootstrapping problem).
26+
- The format of capabilities negotiation is, by its nature, self-describing: a
27+
host can ask a device any number of questions about what it supports, so it is
28+
desireable to make it easy to ask the device questions it can choose to
29+
ignore.
30+
31+
Thus, this proposal combines them into one.
32+
33+
## Requirements for Negotiation
34+
35+
1. Extensible: the host must be able to ask the device questions it does not
36+
understand, and the device must be able to ignore them.
37+
- As a corollary, hosts must be able to ask vendor-specific questions in a
38+
way that does not encourage collision.
39+
2. Multi-part: because this message occurs very early in communication with a
40+
device, the message must fit within the 64 byte minimum that devices are
41+
required to support; the current `Device Capabilities` message has this
42+
explicit property.
43+
3. Future-compatible: This message is special: it is not versioned with the rest
44+
of the Challenge protocol, since it is used for picking a version. Therefore,
45+
there should be a way to nonetheless change how questions and answers are
46+
encoded.
47+
4. Reusable. Negotiation results must be reusable across many interactions with
48+
a device, given that neither the host nor the device has reset. For example,
49+
any number of challenges, key exchanges, or manifest updates must be possible
50+
without explicit renegotiation. Reset of either device must discard the
51+
negotiation.
52+
53+
## Requirements for Versioning
54+
55+
1. Future-proof: it should be unlikely that we run out of version numbers.
56+
2. Git-compatible: it should make use of the existing change-by-change
57+
versioning of the protocol text itself, to make it absolutely clear what the
58+
text for a specific version is.
59+
3. Deprecation: it must be easy to deprecate messages, and even re-use things
60+
like command bytes. Old versions should restrict new versions as little as
61+
possible.
62+
63+
## Non-goals
64+
65+
Currently, negotiated capabilities in Cerberus are not included in any
66+
authentication signatures. Although this is something we need to address, it is
67+
beyond the scope of this RFC. We will outline a way to achieve this in [Future
68+
Work](#future-work).
69+
70+
# Proposal
71+
72+
This proposal has two parts: the negotiation message, and the versioning scheme.
73+
74+
## The `Protocol Negotiation` Message
75+
76+
Negotiation, fundamentally, is about getting the host and the device to agree on
77+
a set of key-value pairs. We will define a new message, `Protocol Negotiation`,
78+
for this purpose. Using it is as follows:
79+
80+
1. Host (H) sends Device (D) a `Protocol Negotiation` request, which is a map of
81+
keys ("questions") to lists of values (potential "answers"). Each are
82+
represented by arbitrary byte strings.
83+
2. D sends H a `Protocol Negotiation` response, which is a map of a subset of
84+
the questions to one answer for each.
85+
3. Repeat from step (1) with more questions.
86+
87+
The format of keys and values is defined below.
88+
89+
### Encoding
90+
91+
A question/answer pair, or "record", consists of a 32-bit integer followed by a
92+
buffer of at most 64 bytes. Records are encoded thus:
93+
- The first byte, the "header", has two fields: `0bxxyyyyyy`. The six low bits
94+
are the length of the record, not including the first byte. The two high bits
95+
describe the number of bytes in the "question".
96+
- The "question" is encoded as a little-endian integer. The value of the high
97+
bits above defines the number of bytes in the integer: `00` for one, `01` for
98+
2, and `10` for 4. `11` is not used and must be rejected.
99+
- Integers must be minimally-encoded. The integer `42` must be encoded as
100+
one byte; parsers that recognize this and reject them.
101+
- The remaining bytes are the "answers", which are interpreted in a per-question
102+
manner.
103+
104+
Because each record is length-prefixed, they can be skipped by a device that
105+
does not understand them. The 64-byte maximum length is more than enough space,
106+
since negotiation messages should not be longer than 64 bytes long, anyways.
107+
108+
Above, we described requests as containing many possible answers, and responses
109+
containing only one. How this is encoded is defined by each question, but the
110+
host must reject any responses that contain multiple answers for a question.
111+
Hosts must also reject responses that contain answers for questions they did not
112+
ask. Devices must reject requests that ask questions they understand, but where
113+
the answer list fails to parse; this list must be non-empty.
114+
115+
The `Protocol Negotiation` request and response are identical, and is described
116+
as the following pseudo-C struct:
117+
118+
```c
119+
struct Negotiation {
120+
uint8_t reserved;
121+
uint8_t flags;
122+
record records[flags & 0x7f];
123+
}
124+
```
125+
126+
In other words, a negotiation message consists of a list of records, some flags,
127+
and a reserved byte for forward compatibility. This byte must always be set to
128+
zero; future versions may introduce new values for it that update how the
129+
questions are encoded.
130+
131+
The flags byte consists of the length (in records) of the record list, and the
132+
"renegotiate" bit (the high bit), described below. Responses with the
133+
"renegotiate" bit set must be rejected as invalid.
134+
135+
### Negotiation State
136+
137+
Because negotiation defines much of the interactions that are to follow, it must
138+
occur very early in an interaction between a host and a device. In particular,
139+
it must be the first message sent to a device.
140+
141+
Hosts and devices maintain *negotiation state*: once negotiation occurs, the
142+
agreed-upon set of key-value pairs is referenced by messages that follow, in
143+
particular for determining the version of the protocol (and, therefore, the
144+
layout of other messages).
145+
146+
Operations that can be performed on the shared set of key-value pairs are
147+
appending (by way of `Protocol Negotiation` messages) and clearing (by way of
148+
sending a `Protocol Negotiation` request with the "renegotiate" bit set). When
149+
clearing, clearing occurs before the records in the same message are processed.
150+
151+
Appending new key-value pairs via a `Protocol Negotiation` message must not
152+
introduce a repeated key: devices must reject messages that try to do so without
153+
requesting renegotiation first.
154+
155+
If a device receives a `Protocol Negotiation` request from a host, but the
156+
device believes the negotiation state is empty, and the request does not
157+
indicate renegotiation, the device must reject the request. This is to prevent
158+
the state from desyncing if either party resets unexpectedly: the host must
159+
always set the renegotiation bit for the first negotiation request, since the
160+
device will reject the request if it is not set for the first one.
161+
162+
Negotiation state must not be stored across resets.
163+
164+
### Example Negotiation
165+
166+
The following is an example of an exchange between a host H and device D, and
167+
the state of the negotiation state at each step. Questions and answers are
168+
represented in an ad-hoc JSON-like format.
169+
170+
0. Negotiation state is `{}`.
171+
1. H sends D the request `{0: [1, 2, 3], 1: [4, 5]}`; renegotiation is set.
172+
a. D replies with `{0: 2, 1: 4}`.
173+
b. Negotiation state is `{0: 2, 1: 4}`.
174+
2. H sends D the request `{1: [3, 4, 5]}`.
175+
a. D rejects it, since `1` was already negotiated.
176+
b. Negotiation state is unchanged.
177+
3. H sends D the request `{2: [0, 2]}`.
178+
a. D replies with `{}`; it does not understand the question `2`.
179+
b. Negotiation state is unchanged.
180+
4. H sends D the request `{3: [0], 4: [1, 2]}`.
181+
a. D replies with `{4: 1}`; it undersands `3`, but does not like any of the
182+
suggested answers.
183+
b. Negotiation state is `{0: 2, 1: 4, 4: 1}`.
184+
5. H sends D the request `{3: []}`.
185+
a. D rejects it, since no answers for `3` are provided, a mistake on H's
186+
part.
187+
b. Negotiation state is unchanged.
188+
6. H sends D the request `{0: [1, 2, 3], 3: [0, 2]}`; renegotiation is set.
189+
a. D replies with `{0: 2, 3: 2}`.
190+
b. Negotiation state is `{0: 2, 3: 2}`.
191+
192+
2, 3, and 5 represent a misbehaving host, and should not occur in a normal
193+
exchange. Hosts should try to maximize the number of questions sent in each
194+
message.
195+
196+
### Legacy Capabilities Negotiation
197+
198+
Because the old `Device Capabilities` message cannot negotiate a version, it is
199+
completely incompatible with this scheme; we propose removing it altogether and
200+
reallocating its command byte to `Protocol Negotiation`.
201+
202+
## Versioning
203+
204+
We define a Cerberus version to be a sixteen-bit opaque value, which maps to a
205+
commit on this repository. Protocol version N is described by the repository at
206+
that commit.
207+
208+
Versions are recorded as Git tags with the name `protocol-v{#}`, where
209+
`{#}` is replaced with the version number. There will also be a file,
210+
`PROTOCOL_VERSIONS.md`, which contains a list of all versions, their dates,
211+
and a changelog, in the following format:
212+
213+
```
214+
# `protocol-v{#}`
215+
Date: YYYY-MM-DD
216+
<changelog>
217+
```
218+
219+
To create a new version:
220+
1. Create a commit that adds the new version to `PROTOCOL_VERSIONS.md`.
221+
2. The pull request created from that commit acts as an opportunity to discuss
222+
the new version and changelog.
223+
3. Once approved, the author must ensure the date on the new version matches.
224+
4. A maintainer will merge the PR, and then create a lightweight tag with the
225+
correct name pointing to the merged commit. This can be done via
226+
`git tag protocol-v{#} && git push origin --tags`. This must be a push to
227+
the upstream repository.
228+
229+
This proposal does not stipulate guidelines under which creating a new version
230+
is recommended; maintainers should create new versions as they deem necessary.
231+
232+
After this RFC is merged and implemented, a tag for `protocol-v0` will be
233+
created pointing to the implementation commit.
234+
235+
The version to use should be negotiated via the `Protocol Negotiation` message;
236+
see [Cerberus-Defined Questins](#cerberus-defined-questions) below.
237+
The version must be negotiated before any commands that come after; devices
238+
should reject any other requiests until a version is negotiated.
239+
240+
### Updates to `CHALLENGE`
241+
242+
Because only a single protocol version is negotiated, we can replace the version
243+
range in the `CHALLENGE` with the single negotiated version. This doesn't change
244+
the layout of the `CHALLENGE` in a meaningful way, since we are replacing two
245+
eight-bit fields with one sixteen-bit field.
246+
247+
### Evolution and Deprecation Process
248+
249+
This versioning scheme gives us a way to freely evolve the protocol without
250+
fear of subtle incompatibility: because one version is chosen, there is no
251+
ambiguity about different layouts of commands.
252+
253+
We also get deprecation for free: if we remove a message, devices can, given
254+
sufficiently wide range of advertised versions, select a version both are
255+
can work with. We can even re-use command bytes across versions, if that ever
256+
becomes a problem.
257+
258+
There is no particular reason to mark messages as deprecated in the spec itself,
259+
although it may be worthwhile to do so to indicate that they will eventually be
260+
removed. Whether to leave messages deprecated for a version or two, or to remove
261+
them immediately, is up to the maintainers.
262+
263+
This introduces the risk that two devices may refuse to interoperate due to
264+
incompatible versions. It is up to implementers to chose a sufficiently large
265+
range of versions to interoperate with other vendors' devices.
266+
267+
Note that the `Protocol Negotiation` message is exempt: due to the bootstrapping
268+
problem mentioned in [Objectives](#objectives), it cannot be versioned along
269+
with the rest of the protocol. Instead, the `reserved` byte in its structure can
270+
be used for this, instead.
271+
272+
## Cerberus-Defined Questions
273+
274+
Cerberus defines a number of questions, some coming from either the old `Device
275+
Capabilities` message. They are as follows:
276+
277+
| Question | Name | Description |
278+
|----------|-------------------|------------------------------------------------------|
279+
| `0x00` | `version` | Negotiates the overall protocol version to use. |
280+
| `0x01` | `has_sessions` | Subcomponent: secure sessions. |
281+
| `0x02` | `has_logging` | Subcomponent: log-extraction commands. |
282+
| `0x03` | `has_pfms` | Subcomponent: PFM operations. |
283+
| `0x03` | `has_manifests` | Subcomponent: CFM/PCD operations. |
284+
| `0x06` | `has_fw_updates` | Subcomponent: firmware updates. |
285+
| `0x07` | `has_fw_recovery` | Subcomponent: firmware recovery. |
286+
| `0x08` | `has_pmrs` | Subcomponent: PMR operations. |
287+
| `0x09` | `has_unsealing` | Subcomponent: unsealing. |
288+
| `0x10` | `rsa` | Negotiates which RSA key sizes are supported. |
289+
| `0x11` | `ecdsa` | Negotiates which ECDSA key sizes are supported. |
290+
| `0x12` | `aes` | Negotiates which AES key sizes are supported. |
291+
292+
`version` negotiates the sixteen-bit version. The answer list is encoded as a
293+
sequence of sixteen bit little-endian integers. The first two are an inclusive
294+
version range of supported versions, followed by versions explicitly not
295+
supported. For example, the sequence `[5, 10, 7, 8]` indicates that the answer
296+
set is `5, 6, 9, 10`. This encoding is chosen because most devices will support
297+
a contiguous range of versions with very few explicitly forbidden versions.
298+
299+
`has_*` messages are "subcomponent" messages, which specify whether or not
300+
a device supports an optional portion of Cerberus. They have an empty body,
301+
since it's a yes/no question.
302+
303+
`rsa`, `ecdsa`, and `aes` are used to ask which key sizes are supported for
304+
different algorithms. They are each encoded as single-byte bit flags, with
305+
values according to those in the "RSA", "ECC", and "AES" fields of `Device
306+
Capabilities`. Not answering these questions indicates that that particular
307+
algorithm is unsupported.
308+
309+
## Vendor-Defined Questions
310+
311+
Vendors may also want to define their own questions to negotiate; these are
312+
identified by very large question IDs (greater than 65535). Vendors *must*
313+
choose question IDs at random.
314+
315+
This is more than enough entropy: given the birthday paradox rule of thumb that
316+
the expected number of choices before a collision is `sqrt(pi/2 * N)`, where N
317+
is the ID space, we get a value of `~80000` for `~2^32`; it is extremely
318+
unlikely we will see anywhere close to that number of extensions.
319+
320+
Vendors are free to define the encoding of their answers as they see fit.
321+
322+
# Specification Changelist
323+
324+
- A section describing Challenge Protocol versioning early in the spec, probably
325+
after the Summary.
326+
- A section describing the negotiation mechanism. This should go right before
327+
the Certificates section.
328+
- New text describing the new `Protocol Negotiation` command encoding in the
329+
Command Format section.
330+
- A table after the table of command bytes, which maps commands onto
331+
subcomponents (as described in [Cerberus-Defined
332+
Questions](#cerberus-defined-questions)).
333+
- The min/max fields in the `CHALLENGE` should be replaced with the negotiated
334+
version.
335+
- The `Device Capabilities` message should be deleted.
336+
- `PROTOCOL_VERSIONS.md` must be created, and the first version `protocol-v0`
337+
minted.
338+
339+
# Implementation Guidance
340+
341+
The main implementation concern is keeping track of negotiation state. In
342+
general, not all state needs to be kept around; only which keys were already
343+
negotiated, and answers that one or the other side intends to use.
344+
Implementations must also record whether or not negotiation state with a host
345+
exists at all, for enforcing when renegotiation is required. This cost is
346+
bounded by the number of questions a device knows how to answer.
347+
348+
# Alternatives Considered
349+
350+
See:
351+
- [Capabilities2](https://github.com/opencomputeproject/Security/pull/21)
352+
- [Version_Detection](https://github.com/opencomputeproject/Security/pull/23)
353+
354+
# Future Work
355+
356+
Negotiation becomes somewhat more load-bearing by containing a version, and
357+
including it in the challenge signature will be necessary for protection against
358+
downgrade attacks. A followup RFC should describe how to incorporate the
359+
negotiation process (not just the negotiated key-value pairs) into the
360+
signature.
361+
362+
This RFC does not describe norms and practices around when to mint versions nor
363+
when to make the actual decision of deprecation; these are left up to the
364+
maintainers or a potential future RFC.

0 commit comments

Comments
 (0)