Skip to main content

bao1x_api/
signatures.rs

1use bytemuck::{Pod, Zeroable};
2
3use crate::*;
4
5/// Total reserved space for the signature block
6pub const SIGBLOCK_LEN: usize = 768;
7/// The jump instruction and the signature itself are not protected
8pub const UNSIGNED_LEN: usize = SignatureInFlash::sealed_data_offset();
9
10// These are vendored in so we don't have a circular dependency on ed25519 crate
11pub const SIGNATURE_LENGTH: usize = 64; // length of an ed25519 signature.
12pub const PUBLIC_KEY_LENGTH: usize = 32; // length of an ed25519 public key.
13pub const AAD_LENGTH: usize = 60; // at least 37 bytes, rounded up to the nearest 32-byte boundary
14
15/// These are notional and subject to change
16#[repr(u32)]
17#[derive(num_enum::TryFromPrimitive, PartialEq, Eq, Clone, Copy)]
18pub enum FunctionCode {
19    /// Should never be used
20    Invalid = 0,
21    /// Code for a valid boot0 region
22    Boot0 = 1,
23    /// Code for a valid boot1 region
24    Boot1 = 2,
25    /// Expected code when being passed a boot1 region update. Self-signing/sealing implementations
26    /// can change this code to Boot1; externally signed implementations would accept UpdatedBoot1
27    /// as interchangeable with Boot1
28    UpdatedBoot1 = 3,
29    /// Loader
30    Loader = 4,
31    UpdatedLoader = 5,
32    /// Baremetal options
33    Baremetal = 6,
34    UpdatedBaremetal = 7,
35    /// Kernel region
36    Kernel = 0x1_00,
37    UpdatedKernel = 0x1_01,
38    /// Swap region
39    Swap = 0x80_00,
40    UpdatedSwap = 0x80_01,
41    /// Application region
42    App = 0x10_0000,
43    UpdatedApp = 0x10_0001,
44}
45
46impl FunctionCode {
47    pub fn to_anti_rollback_counter(&self) -> Option<usize> {
48        match self {
49            Self::Boot0 => Some(BOOT0_ANTI_ROLLBACK),
50            Self::Boot1 | Self::UpdatedBoot1 => Some(BOOT1_ANTI_ROLLBACK),
51            Self::Loader | Self::UpdatedLoader => Some(LOADER_ANTI_ROLLBACK),
52            Self::Kernel | Self::UpdatedKernel => Some(KERNEL_ANTI_ROLLBACK),
53            Self::Swap | Self::UpdatedSwap => Some(SWAP_ANTI_ROLLBACK),
54            Self::App | Self::UpdatedApp => Some(APP_ANTI_ROLLBACK),
55            Self::Baremetal | Self::UpdatedBaremetal => Some(BAREMETAL_ANTI_ROLLBACK),
56            _ => None,
57        }
58    }
59}
60
61pub const BAOCHIP_SIG_VERSION: u32 = 0x1_00;
62pub const PADDING_LEN: usize = SIGBLOCK_LEN - size_of::<SignatureInFlash>();
63pub const MAGIC_NUMBER: [u32; 2] = [u32::from_be_bytes(*b"yumy"), u32::from_be_bytes(*b"Bao3")];
64/// Representation of the signature block in memory.
65#[repr(C)]
66#[derive(Copy, Clone)]
67pub struct SignatureInFlash {
68    /// This is a field that contains a jump instruction such that the CPU boot passes over
69    /// this structure on the way to the boot code.
70    pub _jal_instruction: u32,
71    /// The actual signature.
72    pub signature: [u8; SIGNATURE_LENGTH],
73    /// `aad_len` also specifies the signing protocol. If it is `0`, then a pure `ed25519ph`
74    /// signature is assumed. If it is greater than `0`, then it's assumed to be a
75    /// FIDO2/WebAuthn signature format using ed25519, where the signature is computed as:
76    /// `signature = Ed25519.sign(authenticatorData || SHA-256(clientData))`
77    /// `authenticatorData` is a field that is at least 37 bytes in size. The size is influenced
78    /// by the type of signing and operation, but for our implementation I think it stays
79    /// fixed at 37 bytes.
80    pub aad_len: u32,
81    pub aad: [u8; AAD_LENGTH],
82    /// All data from this point onward are included in the signature computation.
83    pub sealed_data: SealedFields,
84}
85unsafe impl Zeroable for SignatureInFlash {}
86unsafe impl Pod for SignatureInFlash {}
87impl AsRef<[u8]> for SignatureInFlash {
88    fn as_ref(&self) -> &[u8] { bytemuck::bytes_of(self) }
89}
90impl AsMut<[u8]> for SignatureInFlash {
91    fn as_mut(&mut self) -> &mut [u8] { bytemuck::bytes_of_mut(self) }
92}
93impl Default for SignatureInFlash {
94    fn default() -> Self {
95        Self {
96            _jal_instruction: 0,
97            signature: [0u8; SIGNATURE_LENGTH],
98            aad_len: 0,
99            aad: [0u8; AAD_LENGTH],
100            sealed_data: SealedFields::default(),
101        }
102    }
103}
104impl SignatureInFlash {
105    // The offset is after the jal instruction + signature.
106    pub const fn sealed_data_offset() -> usize {
107        size_of::<u32>() + SIGNATURE_LENGTH + size_of::<u32>() + AAD_LENGTH
108    }
109}
110
111#[repr(C)]
112#[derive(Copy, Clone)]
113pub struct Pubkey {
114    pub pk: [u8; PUBLIC_KEY_LENGTH],
115    pub tag: [u8; 4],
116}
117unsafe impl Zeroable for Pubkey {}
118unsafe impl Pod for Pubkey {}
119impl Default for Pubkey {
120    fn default() -> Self { Self { pk: [0u8; PUBLIC_KEY_LENGTH], tag: [0u8; 4] } }
121}
122impl Pubkey {
123    pub fn populate_from(&mut self, record: &Pubkey) {
124        self.pk.copy_from_slice(&record.pk);
125        self.tag.copy_from_slice(&record.tag);
126    }
127}
128
129#[repr(C)]
130#[derive(Copy, Clone, Pod, Zeroable)]
131pub struct SealedFields {
132    /// Version number of this signature record
133    pub version: u32,
134    /// Magic number, to quickly differentiate uninitialized memory from a valid signature record
135    pub magic: [u32; 2],
136    /// Length of the signed code.
137    pub signed_len: u32,
138    /// Function code of the signed block. Used to provide metadata about what is inside the signed
139    /// block. See `FunctionCode`.
140    pub function_code: u32,
141    /// Anti-rollback version number. This is matched against a one-way counter stored in the
142    /// one-way counter memory bank.
143    pub anti_rollback: u32,
144    /// Minimum version of target code that can process this update. This allows us to break
145    /// compatibility with extremely old code bases, should we need to.
146    pub min_semver: [u8; 16],
147    /// Version of the Xous tree in Semver format that was used to build the signed artifact
148    pub semver: [u8; 16],
149    /// The public keys used to check the signature. In the case of boot0, this is committed to a write-only
150    /// partition, so it is immutable. It is also used to check the boot1 block.
151    ///
152    /// An array of keys are provided. An all-0 key is to be disregarded. Furthermore, one-way counters
153    /// in slots 124-127 correspond to keys 0-3 here. A key is to be considered revoked/disregarded
154    /// if the one-way counter does not have a 0 value.
155    ///
156    /// Valid keys are checked in ascending slot order. Any valid signature is considered to be allowed for
157    /// boot.
158    ///
159    /// Key slot 3 is reserved for the developer public key. It is the last key checked, and if it is the
160    /// only valid key and it is not revoked with the one-way counter mechanism, the device root secret
161    /// is erased and boot is allowed to proceed.
162    ///
163    /// If no valid keys are found, the device is effectively bricked and goes into a "die" state.
164    pub pubkeys: [Pubkey; 4],
165}
166
167impl AsRef<[u8]> for SealedFields {
168    fn as_ref(&self) -> &[u8] { bytemuck::bytes_of(self) }
169}
170impl Default for SealedFields {
171    fn default() -> Self {
172        Self {
173            version: BAOCHIP_SIG_VERSION,
174            magic: MAGIC_NUMBER,
175            signed_len: 0,
176            function_code: 0,
177            anti_rollback: 0,
178            min_semver: [0u8; 16],
179            semver: [0u8; 16],
180            pubkeys: [Pubkey::default(); 4],
181        }
182    }
183}
184
185#[derive(Debug, Clone, Copy, Pod, Zeroable)]
186#[repr(C)]
187pub struct SwapSourceHeader {
188    pub version: u32,
189    pub partial_nonce: [u8; 8],
190    pub mac_offset: u32,
191    pub aad_len: u32,
192    // aad is limited to 64 bytes!
193    pub aad: [u8; 64],
194}
195impl AsRef<[u8]> for SwapSourceHeader {
196    fn as_ref(&self) -> &[u8] { bytemuck::bytes_of(self) }
197}
198impl Default for SwapSourceHeader {
199    fn default() -> Self {
200        Self { version: 0, partial_nonce: [0; 8], mac_offset: 0, aad_len: 0, aad: [0; 64] }
201    }
202}
203
204#[repr(C)]
205#[derive(Debug, Clone, Copy, Pod, Zeroable, Default)]
206pub struct SwapDescriptor {
207    pub ram_offset: u32,
208    pub ram_size: u32,
209    pub name: u32,
210    pub key: [u8; 32],
211    pub flash_offset: u32,
212}
213impl AsRef<[u8]> for SwapDescriptor {
214    fn as_ref(&self) -> &[u8] { bytemuck::bytes_of(self) }
215}