@@ -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.
9827type 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.
265187func 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.
313344func 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