Skip to content

Commit 26b1b05

Browse files
committed
refactor: marshal/unmarshal command/response
1 parent 9b4e0b3 commit 26b1b05

File tree

3 files changed

+206
-318
lines changed

3 files changed

+206
-318
lines changed

tpm2/marshalling.go

Lines changed: 181 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -23,77 +23,6 @@ type marshallableWithHint interface {
2323
get(hint int64) (reflect.Value, error)
2424
}
2525

26-
// CommandPreimage represents a structured preimage of cpHash for a TPM command.
27-
// This structure is marshaled to bytes using [Marshal] for storage/transmission
28-
// and can be converted to the raw cpHash preimage format for hashing.
29-
//
30-
// See definition in Part 1: Architecture, section 16.7.
31-
type CommandPreimage struct {
32-
// CommandCode is the TPM command code
33-
CommandCode TPMCC
34-
// Names are the names of the handles referenced by the command
35-
Names []TPM2BName
36-
// Parameters are the marshaled command parameters
37-
Parameters TPM2BData
38-
}
39-
40-
// Marshal converts the CommandPreimage to its byte representation.
41-
//
42-
// Format:
43-
// - CommandCode: 4 bytes (TPMCC)
44-
// - NameCount: 4 bytes (uint32)
45-
// - For each Name:
46-
// - NameSize: 4 bytes (uint32)
47-
// - Parameters: remaining bytes
48-
func (cp *CommandPreimage) Marshal() ([]byte, error) {
49-
buf := new(bytes.Buffer)
50-
if err := binary.Write(buf, binary.BigEndian, cp.CommandCode); err != nil {
51-
return nil, err
52-
}
53-
if err := binary.Write(buf, binary.BigEndian, uint32(len(cp.Names))); err != nil {
54-
return nil, err
55-
}
56-
for _, name := range cp.Names {
57-
if err := binary.Write(buf, binary.BigEndian, uint32(len(name.Buffer))); err != nil {
58-
return nil, err
59-
}
60-
buf.Write(name.Buffer)
61-
}
62-
buf.Write(cp.Parameters.Buffer)
63-
return buf.Bytes(), nil
64-
}
65-
66-
// Unmarshal populates the [CommandPreimage] from its byte representation.
67-
func (cp *CommandPreimage) Unmarshal(b []byte) error {
68-
buf := bytes.NewBuffer(b)
69-
70-
if err := binary.Read(buf, binary.BigEndian, &cp.CommandCode); err != nil {
71-
return fmt.Errorf("unmarshalling CommandCode: %w", err)
72-
}
73-
74-
var nameCount uint32
75-
if err := binary.Read(buf, binary.BigEndian, &nameCount); err != nil {
76-
return fmt.Errorf("unmarshalling Names count: %w", err)
77-
}
78-
79-
cp.Names = make([]TPM2BName, nameCount)
80-
for i := uint32(0); i < nameCount; i++ {
81-
var name TPM2BName
82-
83-
var nameSize uint32
84-
if err := binary.Read(buf, binary.BigEndian, &nameSize); err != nil {
85-
return fmt.Errorf("unmarshalling Name size %d: %w", i, err)
86-
}
87-
name.Buffer = make([]byte, nameSize)
88-
if _, err := buf.Read(name.Buffer); err != nil {
89-
return fmt.Errorf("unmarshalling Name %d: %w", i, err)
90-
}
91-
cp.Names[i] = name
92-
}
93-
cp.Parameters.Buffer = buf.Bytes()
94-
return nil
95-
}
96-
9726
// Unmarshallable represents any TPM type that can be marshalled or unmarshalled.
9827
type Unmarshallable interface {
9928
Marshallable
@@ -199,8 +128,17 @@ func (b *boxed[T]) unmarshal(buf *bytes.Buffer) error {
199128
return unmarshal(buf, reflect.ValueOf(b.Contents))
200129
}
201130

202-
// toCommandPreimage convert a Command to a CommandPreimage structure from a command.
203-
func toCommandPreimage[C Command[R, *R], R any](cmd C) (*CommandPreimage, error) {
131+
// MarshalCommand marshals a TPM command into its raw cpHash preimage format.
132+
// The returned bytes can be directly hashed to compute cpHash.
133+
//
134+
// Example:
135+
//
136+
// cmdData, _ := MarshalCommand(myCmd)
137+
// cpHash := sha256.Sum256(cmdData)
138+
//
139+
// Note: Encrypted command parameters (via sessions) are not currently supported.
140+
// The marshaled parameters are in their unencrypted form.
141+
func MarshalCommand[C Command[R, *R], R any](cmd C) ([]byte, error) {
204142
cc := cmd.Command()
205143

206144
names, err := cmdNames(cmd)
@@ -213,109 +151,226 @@ func toCommandPreimage[C Command[R, *R], R any](cmd C) (*CommandPreimage, error)
213151
return nil, err
214152
}
215153

216-
return &CommandPreimage{
217-
CommandCode: cc,
218-
Names: names,
219-
Parameters: TPM2BData{
220-
Buffer: params,
221-
},
222-
}, nil
223-
}
154+
// Build raw cpHash preimage: CommandCode {∥ Name1 {∥ Name2 {∥ Name3 }}} {∥ Parameters }
155+
// See section 16.7 of TPM 2.0 specification, part 1.
156+
buf := new(bytes.Buffer)
224157

225-
// MarshalCommand marshals a TPM command into a serialized CommandPreimage.
226-
// The returned bytes contain a marshaled CommandPreimage structure that includes:
227-
// - CommandCode (4 bytes)
228-
// - Names (sized list)
229-
// - Parameters (sized buffer)
230-
//
231-
// This can be stored, transmitted, or later unmarshaled [UnmarshalCommand].
232-
//
233-
// Note: Encrypted command parameters (via sessions) are not currently supported.
234-
// The marshaled parameters are in their unencrypted form.
235-
func MarshalCommand[C Command[R, *R], R any](cmd C) ([]byte, error) {
236-
preimage, err := toCommandPreimage(cmd)
237-
if err != nil {
238-
return nil, err
158+
if err := binary.Write(buf, binary.BigEndian, cc); err != nil {
159+
return nil, fmt.Errorf("marshalling command code: %w", err)
239160
}
240-
return preimage.Marshal()
241-
}
242161

243-
// unmarshalCommandPreimage unmarshals serialized data into CommandPreimage components.
244-
// Returns the command code, names, and parameters.
245-
func unmarshalCommandPreimage(data []byte) (TPMCC, []TPM2BName, []byte, error) {
246-
if data == nil {
247-
return 0, nil, nil, fmt.Errorf("data cannot be nil")
162+
for i, name := range names {
163+
if _, err := buf.Write(name.Buffer); err != nil {
164+
return nil, fmt.Errorf("marshalling name %d: %w", i, err)
165+
}
248166
}
249167

250-
var preimage CommandPreimage
251-
if err := preimage.Unmarshal(data); err != nil {
252-
return 0, nil, nil, fmt.Errorf("unmarshalling CommandPreimage: %w", err)
168+
if _, err := buf.Write(params); err != nil {
169+
return nil, fmt.Errorf("marshalling parameters: %w", err)
253170
}
254171

255-
return preimage.CommandCode, preimage.Names, preimage.Parameters.Buffer, nil
172+
return buf.Bytes(), nil
256173
}
257174

258-
// UnmarshalCommand unmarshals a serialized [CommandPreimage] back into a TPM command.
175+
// UnmarshalCommand unmarshals a raw cpHash preimage back into a TPM command.
259176
// The data should be the output from [MarshalCommand].
260177
//
178+
// Example:
179+
//
180+
// cmdData, _ := MarshalCommand(myCmd)
181+
// cmd, _ := UnmarshalCommand[MyCommandType](cmdData)
182+
//
261183
// Notes:
262184
// - command produced from this function is not meant to be executed directly on a TPM,
263185
// instead it is expected to be used for purposes such as auditing or inspection.
264186
// - encrypted command parameters (via sessions) are not currently supported.
265187
func UnmarshalCommand[C Command[R, *R], R any](data []byte) (C, error) {
266188
var cmd C
267189

268-
cc, names, params, err := unmarshalCommandPreimage(data)
269-
if err != nil {
270-
return cmd, err
190+
if data == nil {
191+
return cmd, fmt.Errorf("data cannot be nil")
192+
}
193+
194+
buf := bytes.NewBuffer(data)
195+
196+
var cc TPMCC
197+
if err := binary.Read(buf, binary.BigEndian, &cc); err != nil {
198+
return cmd, fmt.Errorf("unmarshalling command code: %w", err)
271199
}
272200

273201
if cc != cmd.Command() {
274202
return cmd, fmt.Errorf("command code mismatch: expected %v, got %v", cmd.Command(), cc)
275203
}
276204

277-
{
278-
n, err := cmdNames(cmd)
205+
expectedNames, err := cmdNames(cmd)
206+
if err != nil {
207+
return cmd, fmt.Errorf("getting expected names count: %w", err)
208+
}
209+
numNames := len(expectedNames)
210+
211+
names := make([]TPM2BName, numNames)
212+
for i := range numNames {
213+
remaining := buf.Bytes()
214+
if len(remaining) == 0 {
215+
return cmd, fmt.Errorf("unexpected end of data while parsing name %d", i)
216+
}
217+
218+
nameSize, err := parseNameSize(remaining)
279219
if err != nil {
280-
return cmd, err
220+
return cmd, fmt.Errorf("parsing name %d size: %w", i, err)
281221
}
282222

283-
expectedNames := len(names)
284-
if len(n) != expectedNames {
285-
return cmd, fmt.Errorf("name count mismatch: command expects %d names, got %d", expectedNames, len(n))
223+
if len(remaining) < nameSize {
224+
return cmd, fmt.Errorf("insufficient data for name %d: need %d bytes, have %d", i, nameSize, len(remaining))
286225
}
226+
227+
nameBytes := make([]byte, nameSize)
228+
if _, err := buf.Read(nameBytes); err != nil {
229+
return cmd, fmt.Errorf("reading name %d: %w", i, err)
230+
}
231+
232+
names[i] = TPM2BName{Buffer: nameBytes}
287233
}
288234

289235
// Populate the command's handle fields from the names
290236
if err := populateHandlesFromNames(&cmd, names); err != nil {
291-
return cmd, fmt.Errorf("populating handles: %w", err)
237+
return cmd, err
292238
}
293239

294-
// Now unmarshal the parameters using the helper
295-
buf := bytes.NewBuffer(params)
296-
if err := unmarshalCmdParameters(buf, &cmd, nil); err != nil {
240+
params := buf.Bytes()
241+
242+
paramsBuf := bytes.NewBuffer(params)
243+
if err := unmarshalCmdParameters(paramsBuf, &cmd, nil); err != nil {
297244
return cmd, err
298245
}
299246
return cmd, nil
300247
}
301248

302-
// MarshalResponse marshals a TPM response.
303-
func MarshalResponse[R any](rsp *R) ([]byte, error) {
304-
return marshalRspParameters(rsp, nil)
249+
// parseNameSize determines the size of a TPM2BName by inspecting its first bytes.
250+
// Returns the total size in bytes for the name.
251+
//
252+
// Case 1: Handle-based names (4 bytes)
253+
// - 0x0000... → PCR
254+
// - 0x02... → HMAC Session
255+
// - 0x03... → Policy Session
256+
// - 0x40... → Permanent Values
257+
//
258+
// Case 2: Hash-based names (2 + hash_size bytes) - for all other entities
259+
// - Format: nameAlg (2 bytes) || H_nameAlg (hash digest)
260+
//
261+
// See section 14 of TPM 2.0 specification, part 1.
262+
func parseNameSize(buf []byte) (int, error) {
263+
if len(buf) < 2 {
264+
return 0, fmt.Errorf("buffer too short to parse name")
265+
}
266+
267+
firstByte := TPMHT(buf[0])
268+
firstTwoBytes := binary.BigEndian.Uint16(buf[0:2])
269+
270+
// Case 1: Handle-based names (4 bytes)
271+
switch {
272+
case firstTwoBytes == 0x0000:
273+
// PCR handles (pattern: 0x0000XXXX)
274+
// Must check both bytes to distinguish from hash algorithms
275+
// that also start with 0x00 (e.g., TPMAlgSHA256 = 0x000B)
276+
return 4, nil
277+
case firstByte == TPMHTHMACSession: // 0x02
278+
return 4, nil
279+
case firstByte == TPMHTPolicySession: // 0x03
280+
return 4, nil
281+
case firstByte == TPMHTPermanent: // 0x40
282+
return 4, nil
283+
}
284+
285+
// Case 2: Hash-based names (nameAlg || hash)
286+
// firstTwoBytes is the algorithm ID (0x0001 to 0x00B3)
287+
algID := TPMIAlgHash(firstTwoBytes)
288+
hashAlg, err := algID.Hash()
289+
if err != nil {
290+
return 0, fmt.Errorf("unsupported hash algorithm 0x%x in name: %w", firstTwoBytes, err)
291+
}
292+
293+
// 2 bytes for algID + hash size
294+
return 2 + hashAlg.Size(), nil
305295
}
306296

307-
// UnmarshalResponse unmarshals a TPM response.
297+
// MarshalResponse marshals a TPM response into its raw rpHash preimage format.
298+
// The returned bytes can be directly hashed to compute rpHash.
299+
//
300+
// Example:
301+
//
302+
// rspData, _ := MarshalResponse(commandCode, myRsp)
303+
// rpHash := sha256.Sum256(rspData)
304+
//
305+
// Note: Encrypted response parameters (via sessions) are not currently supported.
306+
func MarshalResponse[C Command[R, *R], R any](c C, rsp *R) ([]byte, error) {
307+
cc := c.Command()
308+
309+
params, err := marshalRspParameters(rsp, nil)
310+
if err != nil {
311+
return nil, err
312+
}
313+
314+
// Build raw rpHash preimage: responseCode || commandCode || parameters
315+
buf := new(bytes.Buffer)
316+
317+
// Write responseCode (4 bytes, always 0 for successful responses)
318+
if err := binary.Write(buf, binary.BigEndian, uint32(0)); err != nil {
319+
return nil, fmt.Errorf("marshalling response code: %w", err)
320+
}
321+
322+
if err := binary.Write(buf, binary.BigEndian, cc); err != nil {
323+
return nil, fmt.Errorf("marshalling command code: %w", err)
324+
}
325+
326+
if _, err := buf.Write(params); err != nil {
327+
return nil, fmt.Errorf("marshalling parameters: %w", err)
328+
}
329+
330+
return buf.Bytes(), nil
331+
}
332+
333+
// UnmarshalResponse unmarshals a raw rpHash preimage back into a TPM response.
334+
// The data should be the output from [MarshalResponse].
335+
//
336+
// Example:
337+
//
338+
// rspData, _ := MarshalResponse(commandCode, myRsp)
339+
// rsp, _ := UnmarshalResponse[MyResponseType](rspData)
308340
//
309341
// Notes:
310342
// - the result from this function is expected to be used for purposes such as auditing or inspection.
311343
// - encrypted response parameters (via sessions) are not currently supported.
312-
// The marshaled parameters are always in their unencrypted form.
313344
func UnmarshalResponse[R any](data []byte) (*R, error) {
314345
var rsp R
346+
315347
if data == nil {
316348
return nil, fmt.Errorf("data cannot be nil")
317349
}
318-
if err := rspParameters(data, nil, &rsp); err != nil {
350+
351+
if len(data) < 8 {
352+
return nil, fmt.Errorf("data too short: need at least 8 bytes (responseCode + commandCode), got %d", len(data))
353+
}
354+
355+
buf := bytes.NewBuffer(data)
356+
357+
var responseCode uint32
358+
if err := binary.Read(buf, binary.BigEndian, &responseCode); err != nil {
359+
return nil, fmt.Errorf("unmarshalling response code: %w", err)
360+
}
361+
362+
if responseCode != 0 {
363+
return nil, fmt.Errorf("invalid response code: expected 0, got 0x%x", responseCode)
364+
}
365+
366+
var cc TPMCC
367+
if err := binary.Read(buf, binary.BigEndian, &cc); err != nil {
368+
return nil, fmt.Errorf("unmarshalling command code: %w", err)
369+
}
370+
371+
params := buf.Bytes()
372+
373+
if err := rspParameters(params, nil, &rsp); err != nil {
319374
return nil, err
320375
}
321376
return &rsp, nil

0 commit comments

Comments
 (0)