atecc608b/command/lock.rs
1// Copyright (c) 2026 Tuloup Simon
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! /!\ IRREVERSIBLE LOCK OPERATIONS - HANDLE WITH EXTREME CARE.
17//!
18//! The functions in this module mutate the chip's lock state. Once a zone
19//! is locked, it cannot be unlocked. There is no factory reset. A misissued
20//! `Lock` command turns the chip into permanent silicon.
21//!
22//! ## Project rules
23//!
24//! 1. **No automatic flow calls Lock.** Provisioning, initialization, tests,
25//! setup scripts: none of them call any function in this module
26//! implicitly. The user invokes Lock manually through a dedicated USB-HID
27//! command, with a magic word and a CRC of the expected state.
28//!
29//! 2. **Every function takes an explicit confirmation parameter.** For zone
30//! locks the caller supplies the CRC-16 of the zone as it currently is
31//! on the chip. The chip itself recomputes and compares against the value
32//! sent in `param2`. A mismatch is rejected with a chip error. The
33//! firmware combines this with a magic word check at the USB layer.
34//!
35//! ## Workflow expectation
36//!
37//! - Lock config zone: only after `WriteConfigZone` has been replayed,
38//! read back, and bit-compared against the expected blob. The CLI tool
39//! `hsm-host lock-config-DANGEROUS` reads the chip's current
40//! configuration zone, computes the CRC over the full 128 bytes, shows
41//! it in the double-confirmation prompt, and only then sends the Lock
42//! command with that CRC. The chip verifies one last time before
43//! committing.
44//!
45//! - Lock data zone: only after every data slot the project expects has
46//! been provisioned (PIN hash, PUK hash, IO key, and at least one ECC
47//! keypair generated on chip via `GenKey`). No CRC is checked at lock
48//! time: secret-bearing slots are not readable.
49//!
50//! - Lock slot: only after the per-slot content has been verified.
51//!
52//! ## ATECC Lock command encoding
53//!
54//! From the ATECC608B datasheet:
55//!
56//! | mode bits (`param1`) | Effect |
57//! |----------------------|------------------------------------------------|
58//! | `0b0000_0000` | Lock config zone, verify CRC in `param2` |
59//! | `0b1000_0000` | Lock config zone, **no CRC verification** |
60//! | `0b0000_0001` | Lock data zone, verify CRC in `param2` |
61//! | `0b1000_0001` | Lock data zone, no CRC verification |
62//! | `0b0nnn_n010` | Lock individual slot `nnnn` |
63//! | `0b1nnn_n010` | Same, no CRC check |
64//!
65//! Top bit (bit 7) = "summary mode" : when 1, the chip does **not** verify
66//! the CRC in `param2`. We always send with this bit cleared (CRC checked)
67//! for zone locks. For slot lock we set it (the chip does not check a CRC
68//! for individual slots in our usage).
69//!
70//! ## Encoding the CRC for the config-zone lock
71//!
72//! The chip expects `param2` little-endian. Pass the CRC computed
73//! identically to the chip's algorithm (CCITT variant used everywhere in
74//! `CryptoAuthLib`). The CLI helper in `tools/hsm-host` reads the
75//! configuration zone from the chip, computes that CRC over the full
76//! 128 bytes (factory area included), and passes the result to the
77//! firmware. The chip recomputes the same CRC and rejects the command
78//! if the two disagree.
79
80use crate::error::AteccError;
81use crate::opcodes::{EXEC_TIME_LOCK_MS, OP_LOCK};
82use crate::slot::Slot;
83use crate::{AteccChannel, AteccHal};
84
85/// Mode bits for a config-zone lock, with CRC verification.
86const LOCK_MODE_CONFIG_ZONE_VERIFY_CRC: u8 = 0b0000_0000;
87
88/// Mode bits for a data-zone lock, with the chip's CRC verification
89/// disabled. The data zone holds secrets (slots 5, 6, 8 contain hashed
90/// PIN/PUK and the I/O master key) that cannot be read back even with
91/// the data zone unlocked, because every secret-bearing slot has
92/// `IsSecret=1`. There is therefore no way for the host to compute a
93/// meaningful CRC of what is about to be locked, and no value in asking
94/// the chip to verify one. We rely on the magic-word guard at the USB
95/// layer and the interactive double confirmation in the host CLI.
96const LOCK_MODE_DATA_ZONE_NO_CRC: u8 = 0b1000_0001;
97
98/// Mode bits for an individual slot lock, no CRC verification.
99const LOCK_MODE_SLOT_NO_CRC_BASE: u8 = 0b1000_0010;
100
101impl<H: AteccHal> AteccChannel<'_, H>
102{
103 /// Permanently lock the configuration zone.
104 ///
105 /// **Irreversible.** After this call, every byte in the configuration
106 /// zone is read-only forever. Slot policies, key types, the chip's
107 /// I2C address, and counter initial values become immutable.
108 ///
109 /// `expected_crc` is the CRC-16/CCITT of the current configuration
110 /// zone as the host believes it to be. The chip recomputes the CRC
111 /// of its own configuration and compares. If it differs, the chip
112 /// rejects the command with `ATCA_EXECUTION_ERROR` and the zone
113 /// stays unlocked.
114 ///
115 /// The caller must have verified, by reading the chip and computing
116 /// the CRC, that `expected_crc` matches what's actually on the chip,
117 /// and that the configuration is the intended one. The chip's CRC
118 /// check is a backstop, not a substitute.
119 ///
120 /// # Errors
121 /// - [`AteccError::Chip`] with `ChipError::ExecutionError` if the CRC
122 /// does not match (zone stays unlocked).
123 /// - Other [`AteccError`] variants for I2C or wake failures.
124 pub async fn lock_config_zone
125 (
126 &mut self,
127 expected_crc: u16,
128 ) -> Result<(), AteccError<H::Error>>
129 {
130 self.execute_command_status
131 (
132 OP_LOCK,
133 LOCK_MODE_CONFIG_ZONE_VERIFY_CRC,
134 expected_crc,
135 &[],
136 EXEC_TIME_LOCK_MS,
137 )
138 .await
139 }
140
141 /// Permanently lock the data + OTP zones.
142 ///
143 /// **Irreversible.** After this call, slots can no longer be written
144 /// in cleartext. Writes must go through the encrypted-write protocol
145 /// via the I/O Protection Key, and even those are subject to per-slot
146 /// `EncryptWrite` policy.
147 ///
148 /// Unlike [`Self::lock_config_zone`], this call does **not** ask the
149 /// chip to verify a CRC of the data zone before locking. Every
150 /// secret-bearing slot on this project has `IsSecret=1`, so the host
151 /// cannot read the current slot contents back to compute a meaningful
152 /// CRC. The safety guard is the magic-word check in the firmware plus
153 /// the interactive double confirmation in the host CLI.
154 ///
155 /// # Errors
156 /// - [`AteccError::Chip`] if the chip refuses the command (for example
157 /// when the configuration zone is not yet locked).
158 /// - Other [`AteccError`] variants for I2C or wake failures.
159 pub async fn lock_data_zone
160 (
161 &mut self,
162 ) -> Result<(), AteccError<H::Error>>
163 {
164 self.execute_command_status
165 (
166 OP_LOCK,
167 LOCK_MODE_DATA_ZONE_NO_CRC,
168 0x0000,
169 &[],
170 EXEC_TIME_LOCK_MS,
171 )
172 .await
173 }
174
175 /// Permanently lock an individual data slot.
176 ///
177 /// **Irreversible.** After this call, the slot's contents are frozen
178 /// forever. `Write(slot)` and `GenKey(slot)` on that slot return chip
179 /// errors. The slot's policy in the configuration zone must have its
180 /// `Lockable` bit set, or the chip rejects this command.
181 ///
182 /// # Errors
183 /// - [`AteccError::Chip`] with `ChipError::ExecutionError` if the slot
184 /// is not lockable or already locked.
185 /// - Other [`AteccError`] variants for I2C or wake failures.
186 pub async fn lock_slot
187 (
188 &mut self,
189 slot: Slot,
190 ) -> Result<(), AteccError<H::Error>>
191 {
192 // Mode encoding for individual slot lock: top bit set
193 // (no_crc_check = 1) and slot index in bits 2..6.
194 let mode = LOCK_MODE_SLOT_NO_CRC_BASE | ((slot.as_u8() & 0x0F) << 2);
195 self.execute_command_status
196 (
197 OP_LOCK,
198 mode,
199 0x0000,
200 &[],
201 EXEC_TIME_LOCK_MS,
202 )
203 .await
204 }
205}