Skip to main content

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}