opentitanlib/chip/
boot_svc.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::Result;
6use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
7use serde_annotate::Annotate;
8use sha2::{Digest, Sha256};
9use sphincsplus::SpxSecretKey;
10use std::convert::TryFrom;
11use std::io::{Read, Write};
12
13use super::ChipDataError;
14use crate::chip::boolean::HardenedBool;
15use crate::chip::rom_error::RomError;
16use crate::crypto::ecdsa::{EcdsaPrivateKey, EcdsaPublicKey, EcdsaRawPublicKey, EcdsaRawSignature};
17use crate::ownership::{DetachedSignature, OwnershipKeyAlg};
18use crate::with_unknown;
19
20with_unknown! {
21    pub enum BootSlot: u32 [default = Self::Unknown] {
22        Unknown = 0,
23        SlotA = u32::from_le_bytes(*b"AA__"),
24        SlotB = u32::from_le_bytes(*b"__BB"),
25        Unspecified = u32::from_le_bytes(*b"UUUU"),
26    }
27
28    /// The unlock mode for the OwnershipUnlock command.
29    pub enum UnlockMode: u32 [default = Self::Unknown] {
30        Unknown = 0,
31        /// Unlock the chip to accept any next owner.
32        Any = 0x00594e41,
33        /// Unlock the chip to accept only the endorsed next owner.
34        Endorsed = 0x4f444e45,
35        /// Unlock the chip to update the current owner configuration.
36        Update = 0x00445055,
37        /// Abort the unlock operation.
38        Abort = 0x54524241,
39    }
40
41    pub enum BootSvcKind: u32 [default = Self::Unknown] {
42        Unknown = 0,
43        EmptyRequest = u32::from_le_bytes(*b"EMPT"),
44        EmptyResponse = u32::from_le_bytes(*b"TPME"),
45        MinBl0SecVerRequest = u32::from_le_bytes(*b"MSEC"),
46        MinBl0SecVerResponse = u32::from_le_bytes(*b"CESM"),
47        NextBl0SlotRequest = u32::from_le_bytes(*b"NEXT"),
48        NextBl0SlotResponse = u32::from_le_bytes(*b"TXEN"),
49        OwnershipUnlockRequest = u32::from_le_bytes(*b"UNLK"),
50        OwnershipUnlockResponse = u32::from_le_bytes(*b"KLNU"),
51        OwnershipActivateRequest = u32::from_le_bytes(*b"ACTV"),
52        OwnershipActivateResponse = u32::from_le_bytes(*b"VTCA"),
53    }
54}
55
56impl BootSlot {
57    pub fn opposite(self) -> Result<Self> {
58        match self {
59            BootSlot::SlotA => Ok(BootSlot::SlotB),
60            BootSlot::SlotB => Ok(BootSlot::SlotA),
61            _ => Err(ChipDataError::BadSlot(self).into()),
62        }
63    }
64}
65
66/// The Boot Services header common to all boot services commands and responses.
67#[derive(Debug, Default, Annotate)]
68pub struct Header {
69    /// A SHA256 digest over the rest of the boot services message.
70    #[annotate(format=hex)]
71    pub digest: [u32; 8],
72    /// A tag that identifies this struct as a boot services message ('BSVC').
73    #[annotate(format=hex)]
74    pub identifier: u32,
75    /// The type of boot services message that follows this header.
76    pub kind: BootSvcKind,
77    /// The length of the boot services message in bytes (including the header).
78    pub length: u32,
79}
80
81/// An empty boot services message.
82#[derive(Debug, Default, Annotate)]
83pub struct Empty {
84    #[annotate(format=hex)]
85    pub payload: Vec<u32>,
86}
87
88/// Request to set the minimum owner stage firmware version.
89#[derive(Debug, Default, Annotate)]
90pub struct MinBl0SecVerRequest {
91    /// The desired minimum BL0 version.
92    pub ver: u32,
93}
94
95/// Response to the minimum version request.
96#[derive(Debug, Default, Annotate)]
97pub struct MinBl0SecVerResponse {
98    /// The current minimum BL0 version.
99    pub ver: u32,
100    /// The status response to the request.
101    #[annotate(format = hex)]
102    pub status: RomError,
103}
104
105/// Request to set the next (one-time) owner stage boot slot.
106#[derive(Debug, Default, Annotate)]
107pub struct NextBl0SlotRequest {
108    /// The slot to boot.
109    pub next_bl0_slot: BootSlot,
110    /// The slot to configure as primary.
111    pub primary_bl0_slot: BootSlot,
112}
113
114/// Response to the set next boot slot request.
115#[derive(Debug, Default, Annotate)]
116pub struct NextBl0SlotResponse {
117    /// The status response to the request.
118    #[annotate(format = hex)]
119    pub status: RomError,
120    /// The current primary slot.
121    pub primary_bl0_slot: BootSlot,
122}
123
124/// Request to unlock ownership of the chip.
125#[derive(Debug, Default, Annotate)]
126pub struct OwnershipUnlockRequest {
127    /// The desired unlock mode.
128    pub unlock_mode: UnlockMode,
129    /// The Device Identification Number of the chip.
130    pub din: u64,
131    /// Reserved for future use.
132    #[serde(with = "serde_bytes", skip_serializing_if = "Vec::is_empty")]
133    #[annotate(format=hexstr)]
134    pub reserved: Vec<u8>,
135    /// The ROM_EXT nonce.
136    #[annotate(format=hex)]
137    pub nonce: u64,
138    /// The next owner's key (for unlock Endorsed mode).
139    pub next_owner_key: EcdsaRawPublicKey,
140    // TODO(cfrantz): Hybrid key material
141    #[serde(with = "serde_bytes", skip_serializing_if = "Vec::is_empty")]
142    #[annotate(format=hexstr)]
143    pub hybrid_padding: Vec<u8>,
144    /// A signature over [unlock_mode..next_owner_key] with the current owner unlock key.
145    pub signature: EcdsaRawSignature,
146}
147
148/// Response to the ownership unlock command.
149#[derive(Debug, Default, Annotate)]
150pub struct OwnershipUnlockResponse {
151    /// The status response to the request.
152    #[annotate(format = hex)]
153    pub status: RomError,
154}
155
156/// Request to activate ownership of the chip.
157#[derive(Debug, Default, Annotate)]
158pub struct OwnershipActivateRequest {
159    /// The new primary boot slot after activating ownership.
160    pub primary_bl0_slot: BootSlot,
161    /// The Device Identification Number of the chip.
162    pub din: u64,
163    /// Whether to erase the previous owner's data during activation.
164    pub erase_previous: HardenedBool,
165    /// Reserved for future use.
166    #[serde(with = "serde_bytes", skip_serializing_if = "Vec::is_empty")]
167    #[annotate(format=hexstr)]
168    pub reserved: Vec<u8>,
169    /// The ROM_EXT nonce.
170    #[annotate(format=hex)]
171    pub nonce: u64,
172    /// A signature over [primary_bl0_slot..nonce] with the next owner's activate key.
173    pub signature: EcdsaRawSignature,
174}
175
176/// Response to the ownership activate command.
177#[derive(Debug, Default, Annotate)]
178pub struct OwnershipActivateResponse {
179    /// The status response to the request.
180    #[annotate(format = hex)]
181    pub status: RomError,
182}
183
184#[derive(Debug, Annotate)]
185pub enum Message {
186    Raw(
187        #[serde(with = "serde_bytes")]
188        #[annotate(format=hexdump)]
189        Vec<u8>,
190    ),
191    Empty(Empty),
192    MinBl0SecVerRequest(MinBl0SecVerRequest),
193    NextBl0SlotRequest(NextBl0SlotRequest),
194    OwnershipUnlockRequest(OwnershipUnlockRequest),
195    OwnershipActivateRequest(OwnershipActivateRequest),
196    MinBl0SecVerResponse(MinBl0SecVerResponse),
197    NextBl0SlotResponse(NextBl0SlotResponse),
198    OwnershipUnlockResponse(OwnershipUnlockResponse),
199    OwnershipActivateResponse(OwnershipActivateResponse),
200}
201
202#[derive(Debug, Annotate)]
203pub struct BootSvc {
204    pub header: Header,
205    pub message: Message,
206}
207
208impl TryFrom<&[u8]> for BootSvc {
209    type Error = ChipDataError;
210    fn try_from(buf: &[u8]) -> std::result::Result<Self, Self::Error> {
211        let header = Header::try_from(buf)?;
212        let len = header.length as usize;
213        if buf.len() - Header::SIZE < len {
214            return Err(ChipDataError::BadSize(len, buf.len()));
215        }
216        let mut digest = Sha256::digest(&buf[Header::HASH_LEN..Header::SIZE]);
217        digest.reverse();
218        if digest[..] == buf[..Header::HASH_LEN] {
219            return Err(ChipDataError::InvalidDigest);
220        }
221        let buf = &buf[Header::SIZE..];
222        let message = match header.kind {
223            BootSvcKind::EmptyRequest => Message::Empty(TryFrom::try_from(buf)?),
224            BootSvcKind::EmptyResponse => Message::Empty(TryFrom::try_from(buf)?),
225            BootSvcKind::MinBl0SecVerRequest => {
226                Message::MinBl0SecVerRequest(TryFrom::try_from(buf)?)
227            }
228            BootSvcKind::MinBl0SecVerResponse => {
229                Message::MinBl0SecVerResponse(TryFrom::try_from(buf)?)
230            }
231            BootSvcKind::NextBl0SlotRequest => Message::NextBl0SlotRequest(TryFrom::try_from(buf)?),
232            BootSvcKind::NextBl0SlotResponse => {
233                Message::NextBl0SlotResponse(TryFrom::try_from(buf)?)
234            }
235            BootSvcKind::OwnershipUnlockRequest => {
236                Message::OwnershipUnlockRequest(TryFrom::try_from(buf)?)
237            }
238            BootSvcKind::OwnershipUnlockResponse => {
239                Message::OwnershipUnlockResponse(TryFrom::try_from(buf)?)
240            }
241            BootSvcKind::OwnershipActivateRequest => {
242                Message::OwnershipActivateRequest(TryFrom::try_from(buf)?)
243            }
244            BootSvcKind::OwnershipActivateResponse => {
245                Message::OwnershipActivateResponse(TryFrom::try_from(buf)?)
246            }
247            _ => Message::Raw(buf.to_vec()),
248        };
249
250        Ok(BootSvc { header, message })
251    }
252}
253
254impl BootSvc {
255    pub fn to_bytes(&self) -> Result<Vec<u8>> {
256        let mut data = Vec::new();
257        self.header.write(&mut data)?;
258        match &self.message {
259            Message::Empty(m) => m.write(&mut data)?,
260            Message::MinBl0SecVerRequest(m) => m.write(&mut data)?,
261            Message::MinBl0SecVerResponse(m) => m.write(&mut data)?,
262            Message::NextBl0SlotRequest(m) => m.write(&mut data)?,
263            Message::NextBl0SlotResponse(m) => m.write(&mut data)?,
264            Message::OwnershipUnlockRequest(m) => m.write(&mut data)?,
265            Message::OwnershipUnlockResponse(m) => m.write(&mut data)?,
266            Message::OwnershipActivateRequest(m) => m.write(&mut data)?,
267            Message::OwnershipActivateResponse(m) => m.write(&mut data)?,
268            Message::Raw(m) => data.extend_from_slice(m.as_slice()),
269        };
270        let mut digest = Sha256::digest(&data[Header::HASH_LEN..]);
271        digest.reverse();
272        data[..Header::HASH_LEN].copy_from_slice(&digest);
273        Ok(data)
274    }
275
276    pub fn min_bl0_sec_ver(ver: u32) -> Self {
277        BootSvc {
278            header: Header {
279                digest: [0u32; 8],
280                identifier: Header::IDENTIFIER,
281                kind: BootSvcKind::MinBl0SecVerRequest,
282                length: (Header::SIZE + MinBl0SecVerRequest::SIZE) as u32,
283            },
284            message: Message::MinBl0SecVerRequest(MinBl0SecVerRequest { ver }),
285        }
286    }
287
288    pub fn next_boot_bl0_slot(primary: BootSlot, next: BootSlot) -> Self {
289        BootSvc {
290            header: Header {
291                digest: [0u32; 8],
292                identifier: Header::IDENTIFIER,
293                kind: BootSvcKind::NextBl0SlotRequest,
294                length: (Header::SIZE + NextBl0SlotRequest::SIZE) as u32,
295            },
296            message: Message::NextBl0SlotRequest(NextBl0SlotRequest {
297                next_bl0_slot: next,
298                primary_bl0_slot: primary,
299            }),
300        }
301    }
302
303    pub fn ownership_unlock(unlock: OwnershipUnlockRequest) -> Self {
304        BootSvc {
305            header: Header {
306                digest: [0u32; 8],
307                identifier: Header::IDENTIFIER,
308                kind: BootSvcKind::OwnershipUnlockRequest,
309                length: (Header::SIZE + OwnershipUnlockRequest::SIZE) as u32,
310            },
311            message: Message::OwnershipUnlockRequest(unlock),
312        }
313    }
314
315    pub fn ownership_activate(activate: OwnershipActivateRequest) -> Self {
316        BootSvc {
317            header: Header {
318                digest: [0u32; 8],
319                identifier: Header::IDENTIFIER,
320                kind: BootSvcKind::OwnershipActivateRequest,
321                length: (Header::SIZE + OwnershipActivateRequest::SIZE) as u32,
322            },
323            message: Message::OwnershipActivateRequest(activate),
324        }
325    }
326}
327
328impl TryFrom<&[u8]> for Header {
329    type Error = ChipDataError;
330    fn try_from(buf: &[u8]) -> std::result::Result<Self, Self::Error> {
331        let mut reader = std::io::Cursor::new(buf);
332        let mut val = Header::default();
333        reader.read_u32_into::<LittleEndian>(&mut val.digest)?;
334        val.identifier = reader.read_u32::<LittleEndian>()?;
335        val.kind = BootSvcKind(reader.read_u32::<LittleEndian>()?);
336        val.length = reader.read_u32::<LittleEndian>()?;
337        Ok(val)
338    }
339}
340impl Header {
341    pub const SIZE: usize = 44;
342    pub const IDENTIFIER: u32 = 0x43565342;
343    const HASH_LEN: usize = 32;
344
345    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
346        for d in self.digest.iter() {
347            dest.write_u32::<LittleEndian>(*d)?;
348        }
349        dest.write_u32::<LittleEndian>(self.identifier)?;
350        dest.write_u32::<LittleEndian>(u32::from(self.kind))?;
351        dest.write_u32::<LittleEndian>(self.length)?;
352        Ok(())
353    }
354}
355
356impl TryFrom<&[u8]> for Empty {
357    type Error = ChipDataError;
358    fn try_from(buf: &[u8]) -> std::result::Result<Self, Self::Error> {
359        let mut reader = std::io::Cursor::new(buf);
360        let mut val = Empty::default();
361        val.payload.resize(64, 0);
362        reader.read_u32_into::<LittleEndian>(&mut val.payload)?;
363        Ok(val)
364    }
365}
366impl Empty {
367    pub const SIZE: usize = 256;
368    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
369        for i in 0..64 {
370            let p = self.payload.get(i).unwrap_or(&0);
371            dest.write_u32::<LittleEndian>(*p)?;
372        }
373        Ok(())
374    }
375}
376
377impl TryFrom<&[u8]> for MinBl0SecVerRequest {
378    type Error = ChipDataError;
379    fn try_from(buf: &[u8]) -> std::result::Result<Self, Self::Error> {
380        let mut reader = std::io::Cursor::new(buf);
381        Ok(MinBl0SecVerRequest {
382            ver: reader.read_u32::<LittleEndian>()?,
383        })
384    }
385}
386impl MinBl0SecVerRequest {
387    pub const SIZE: usize = 4;
388    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
389        dest.write_u32::<LittleEndian>(self.ver)?;
390        Ok(())
391    }
392}
393
394impl TryFrom<&[u8]> for MinBl0SecVerResponse {
395    type Error = ChipDataError;
396    fn try_from(buf: &[u8]) -> std::result::Result<Self, Self::Error> {
397        let mut reader = std::io::Cursor::new(buf);
398        Ok(MinBl0SecVerResponse {
399            ver: reader.read_u32::<LittleEndian>()?,
400            status: RomError(reader.read_u32::<LittleEndian>()?),
401        })
402    }
403}
404impl MinBl0SecVerResponse {
405    pub const SIZE: usize = 8;
406    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
407        dest.write_u32::<LittleEndian>(self.ver)?;
408        dest.write_u32::<LittleEndian>(u32::from(self.status))?;
409        Ok(())
410    }
411}
412
413impl TryFrom<&[u8]> for NextBl0SlotRequest {
414    type Error = ChipDataError;
415    fn try_from(buf: &[u8]) -> std::result::Result<Self, Self::Error> {
416        let mut reader = std::io::Cursor::new(buf);
417        Ok(NextBl0SlotRequest {
418            next_bl0_slot: BootSlot(reader.read_u32::<LittleEndian>()?),
419            primary_bl0_slot: BootSlot(reader.read_u32::<LittleEndian>()?),
420        })
421    }
422}
423impl NextBl0SlotRequest {
424    pub const SIZE: usize = 8;
425    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
426        dest.write_u32::<LittleEndian>(u32::from(self.next_bl0_slot))?;
427        dest.write_u32::<LittleEndian>(u32::from(self.primary_bl0_slot))?;
428        Ok(())
429    }
430}
431
432impl TryFrom<&[u8]> for NextBl0SlotResponse {
433    type Error = ChipDataError;
434    fn try_from(buf: &[u8]) -> std::result::Result<Self, Self::Error> {
435        let mut reader = std::io::Cursor::new(buf);
436        Ok(NextBl0SlotResponse {
437            status: RomError(reader.read_u32::<LittleEndian>()?),
438            primary_bl0_slot: BootSlot(reader.read_u32::<LittleEndian>()?),
439        })
440    }
441}
442impl NextBl0SlotResponse {
443    pub const SIZE: usize = 4;
444    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
445        dest.write_u32::<LittleEndian>(u32::from(self.status))?;
446        Ok(())
447    }
448}
449
450impl TryFrom<&[u8]> for OwnershipUnlockRequest {
451    type Error = ChipDataError;
452    fn try_from(buf: &[u8]) -> std::result::Result<Self, Self::Error> {
453        let mut reader = std::io::Cursor::new(buf);
454        let mut val = Self::default();
455        val.unlock_mode = UnlockMode(reader.read_u32::<LittleEndian>()?);
456        val.din = reader.read_u64::<LittleEndian>()?;
457        val.reserved.resize(Self::RESERVED_SIZE, 0);
458        reader.read_exact(&mut val.reserved)?;
459        val.nonce = reader.read_u64::<LittleEndian>()?;
460        val.next_owner_key = EcdsaRawPublicKey::read(&mut reader).map_err(ChipDataError::Anyhow)?;
461
462        // SPX+ public key size is 32 bytes.
463        val.hybrid_padding.resize(32, 0);
464        reader.read_exact(&mut val.hybrid_padding)?;
465
466        val.signature = EcdsaRawSignature::read(&mut reader).map_err(ChipDataError::Anyhow)?;
467        Ok(val)
468    }
469}
470impl OwnershipUnlockRequest {
471    pub const SIZE: usize = 212;
472    const RESERVED_SIZE: usize = 8 * std::mem::size_of::<u32>();
473    const SIGNATURE_OFFSET: usize = 148;
474    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
475        dest.write_u32::<LittleEndian>(u32::from(self.unlock_mode))?;
476        dest.write_u64::<LittleEndian>(self.din)?;
477        for i in 0..Self::RESERVED_SIZE {
478            let p = self.reserved.get(i).unwrap_or(&0x00);
479            dest.write_all(std::slice::from_ref(p))?;
480        }
481        dest.write_u64::<LittleEndian>(self.nonce)?;
482        self.next_owner_key.write(dest)?;
483
484        // SPX+ public key size is 32 bytes.
485        for i in 0..32 {
486            let p = self.hybrid_padding.get(i).unwrap_or(&0x00);
487            dest.write_all(std::slice::from_ref(p))?;
488        }
489
490        self.signature.write(dest)?;
491        Ok(())
492    }
493
494    pub fn set_next_owner_key(&mut self, key: &EcdsaPublicKey) -> Result<()> {
495        self.next_owner_key = EcdsaRawPublicKey::try_from(key)?;
496        Ok(())
497    }
498
499    pub fn sign(&mut self, key: &EcdsaPrivateKey) -> Result<()> {
500        let mut data = Vec::new();
501        self.write(&mut data)?;
502        self.signature = key.digest_and_sign(&data[..Self::SIGNATURE_OFFSET])?;
503        Ok(())
504    }
505
506    pub fn detached_sign(
507        &mut self,
508        algorithm: OwnershipKeyAlg,
509        ecdsa_key: Option<&EcdsaPrivateKey>,
510        spx_key: Option<&SpxSecretKey>,
511    ) -> Result<DetachedSignature> {
512        self.signature = Default::default();
513        let mut data = Vec::new();
514        self.write(&mut data)?;
515        DetachedSignature::new(
516            &data[..Self::SIGNATURE_OFFSET],
517            BootSvcKind::OwnershipUnlockRequest.into(),
518            algorithm,
519            self.nonce,
520            ecdsa_key,
521            spx_key,
522        )
523    }
524}
525
526impl TryFrom<&[u8]> for OwnershipUnlockResponse {
527    type Error = ChipDataError;
528    fn try_from(buf: &[u8]) -> std::result::Result<Self, Self::Error> {
529        let mut reader = std::io::Cursor::new(buf);
530        Ok(OwnershipUnlockResponse {
531            status: RomError(reader.read_u32::<LittleEndian>()?),
532        })
533    }
534}
535impl OwnershipUnlockResponse {
536    pub const SIZE: usize = 4;
537    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
538        dest.write_u32::<LittleEndian>(u32::from(self.status))?;
539        Ok(())
540    }
541}
542
543impl TryFrom<&[u8]> for OwnershipActivateRequest {
544    type Error = ChipDataError;
545    fn try_from(buf: &[u8]) -> std::result::Result<Self, Self::Error> {
546        let mut reader = std::io::Cursor::new(buf);
547        let mut val = Self::default();
548        val.primary_bl0_slot = BootSlot(reader.read_u32::<LittleEndian>()?);
549        val.din = reader.read_u64::<LittleEndian>()?;
550        val.erase_previous = HardenedBool(reader.read_u32::<LittleEndian>()?);
551        val.reserved.resize(Self::RESERVED_SIZE, 0);
552        reader.read_exact(&mut val.reserved)?;
553        val.nonce = reader.read_u64::<LittleEndian>()?;
554        val.signature = EcdsaRawSignature::read(&mut reader).map_err(ChipDataError::Anyhow)?;
555        Ok(val)
556    }
557}
558impl OwnershipActivateRequest {
559    pub const SIZE: usize = 212;
560    const RESERVED_SIZE: usize = 31 * std::mem::size_of::<u32>();
561    const SIGNATURE_OFFSET: usize = 148;
562    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
563        dest.write_u32::<LittleEndian>(u32::from(self.primary_bl0_slot))?;
564        dest.write_u64::<LittleEndian>(self.din)?;
565        dest.write_u32::<LittleEndian>(u32::from(self.erase_previous))?;
566        for i in 0..Self::RESERVED_SIZE {
567            let p = self.reserved.get(i).unwrap_or(&0x00);
568            dest.write_all(std::slice::from_ref(p))?;
569        }
570        dest.write_u64::<LittleEndian>(self.nonce)?;
571        self.signature.write(dest)?;
572        Ok(())
573    }
574
575    pub fn sign(&mut self, key: &EcdsaPrivateKey) -> Result<()> {
576        let mut data = Vec::new();
577        self.write(&mut data)?;
578        self.signature = key.digest_and_sign(&data[..Self::SIGNATURE_OFFSET])?;
579        Ok(())
580    }
581
582    pub fn detached_sign(
583        &mut self,
584        algorithm: OwnershipKeyAlg,
585        ecdsa_key: Option<&EcdsaPrivateKey>,
586        spx_key: Option<&SpxSecretKey>,
587    ) -> Result<DetachedSignature> {
588        self.signature = Default::default();
589        let mut data = Vec::new();
590        self.write(&mut data)?;
591        DetachedSignature::new(
592            &data[..Self::SIGNATURE_OFFSET],
593            BootSvcKind::OwnershipActivateRequest.into(),
594            algorithm,
595            self.nonce,
596            ecdsa_key,
597            spx_key,
598        )
599    }
600}
601
602impl TryFrom<&[u8]> for OwnershipActivateResponse {
603    type Error = ChipDataError;
604    fn try_from(buf: &[u8]) -> std::result::Result<Self, Self::Error> {
605        let mut reader = std::io::Cursor::new(buf);
606        Ok(OwnershipActivateResponse {
607            status: RomError(reader.read_u32::<LittleEndian>()?),
608        })
609    }
610}
611impl OwnershipActivateResponse {
612    pub const SIZE: usize = 4;
613    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
614        dest.write_u32::<LittleEndian>(u32::from(self.status))?;
615        Ok(())
616    }
617}