atecc608b/command/checkmac.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//! `CheckMac` command.
17//!
18//! Verifies a host-computed MAC against the contents of a slot. The chip
19//! takes the slot value as key, computes `SHA256(key || challenge ||
20//! other_data || padding)`, and compares the result with the host-supplied
21//! `client_resp`. A match yields status byte `0x00`, a mismatch yields
22//! `0x01` ([`crate::error::ChipError::CheckMacOrVerifyFailed`]).
23//!
24//! The flow used in this project is PIN verification. Slot 5 stores
25//! `SHA256(PIN || salt)`. The host computes the same digest from the PIN
26//! the user typed, builds the corresponding MAC, and asks the chip to
27//! cross-check it. If the user has fat-fingered the PIN, the MAC does not
28//! match, the chip reports miscompare, and the relevant counter is bumped.
29//! That counter eventually reaches the `LimitedUse` threshold (5 for PIN,
30//! 10 for PUK) and blocks the slot from any further `CheckMac`.
31//!
32//! Reference: `CryptoAuthLib` `lib/calib/calib_checkmac.c`, constants
33//! `CHECKMAC_MODE_CHALLENGE` (0x00),
34//! `CHECKMAC_CHALLENGE_SIZE` (32),
35//! `CHECKMAC_CLIENT_RESPONSE_SIZE` (32),
36//! `CHECKMAC_OTHER_DATA_SIZE` (13).
37
38use crate::driver::AteccChannel;
39use crate::error::{AteccError, ChipError};
40use crate::hal::AteccHal;
41use crate::opcodes::{EXEC_TIME_CHECKMAC_MS, OP_CHECKMAC};
42use crate::slot::Slot;
43
44/// Size of the challenge block sent to the chip.
45pub const CHECKMAC_CHALLENGE_SIZE: usize = 32;
46
47/// Size of the client response block (the host-computed MAC under test).
48pub const CHECKMAC_CLIENT_RESPONSE_SIZE: usize = 32;
49
50/// Size of the `other_data` block.
51///
52/// `other_data` mirrors the parameters the chip uses internally to compute
53/// the MAC against. It encodes the opcode, mode, key id, and OTP fields
54/// that participate in the hash. Layout per `CryptoAuthLib`:
55///
56/// ```text
57/// [0] : opcode (must match the CheckMac call, 0x28)
58/// [1] : mode (must match param1)
59/// [2..4] : key id LE (must match param2)
60/// [4..7] : OTP bytes 8..10 (zero on a chip without OTP usage)
61/// [7..11]: SN[4..7] (chip serial number, can be left zero)
62/// [11..13]: SN[2..3]
63/// ```
64///
65/// For PIN verification with no OTP coupling, all 13 bytes can be zero.
66pub const CHECKMAC_OTHER_DATA_SIZE: usize = 13;
67
68/// Total size of the data field sent with a `CheckMac` command.
69pub const CHECKMAC_DATA_SIZE: usize =
70 CHECKMAC_CHALLENGE_SIZE + CHECKMAC_CLIENT_RESPONSE_SIZE + CHECKMAC_OTHER_DATA_SIZE;
71
72/// `param1` mode: challenge is taken from the input `data` field, key from
73/// the slot identified by `param2`.
74const CHECKMAC_MODE_CHALLENGE: u8 = 0x00;
75
76impl<H> AteccChannel<'_, H>
77where
78 H: AteccHal,
79{
80 /// Verify a host-computed MAC against the contents of `slot`.
81 ///
82 /// `challenge` is the random nonce used as input. `client_resp` is the
83 /// MAC the host computed. `other_data` lays out the metadata fields
84 /// the chip will hash into its own MAC.
85 ///
86 /// Returns `Ok(true)` on match (chip status `0x00`), `Ok(false)` on
87 /// miscompare (chip status `0x01`). Any other error condition surfaces
88 /// as [`AteccError`].
89 ///
90 /// # Counter side-effect
91 ///
92 /// If the target slot has a `LimitedUse` counter (slots 5 and 6 in this
93 /// project), each `CheckMac` call bumps the counter regardless of the
94 /// outcome. Reaching the threshold permanently blocks the slot.
95 ///
96 /// # Errors
97 /// See [`AteccChannel::execute_command_status`]. A miscompare (`0x01`)
98 /// is returned as `Ok(false)`.
99 pub async fn checkmac
100 (
101 &mut self,
102 slot: Slot,
103 challenge: &[u8; CHECKMAC_CHALLENGE_SIZE],
104 client_resp: &[u8; CHECKMAC_CLIENT_RESPONSE_SIZE],
105 other_data: &[u8; CHECKMAC_OTHER_DATA_SIZE],
106 ) -> Result<bool, AteccError<H::Error>>
107 {
108 let mut data = [0u8; CHECKMAC_DATA_SIZE];
109 data[..CHECKMAC_CHALLENGE_SIZE].copy_from_slice(challenge);
110 data[CHECKMAC_CHALLENGE_SIZE..CHECKMAC_CHALLENGE_SIZE + CHECKMAC_CLIENT_RESPONSE_SIZE]
111 .copy_from_slice(client_resp);
112 data[CHECKMAC_CHALLENGE_SIZE + CHECKMAC_CLIENT_RESPONSE_SIZE..]
113 .copy_from_slice(other_data);
114
115 let result = self
116 .execute_command_status
117 (
118 OP_CHECKMAC,
119 CHECKMAC_MODE_CHALLENGE,
120 u16::from(slot.as_u8()),
121 &data,
122 EXEC_TIME_CHECKMAC_MS,
123 )
124 .await;
125
126 match result
127 {
128 Ok(()) => Ok(true),
129 Err(AteccError::Chip(ChipError::CheckMacOrVerifyFailed)) => Ok(false),
130 Err(other) => Err(other),
131 }
132 }
133}
134
135#[cfg(test)]
136mod tests
137{
138 use super::*;
139
140 #[test]
141 fn data_size_is_77()
142 {
143 assert_eq!(CHECKMAC_DATA_SIZE, 77);
144 }
145
146 #[test]
147 fn challenge_mode_matches_cryptoauthlib_constant()
148 {
149 assert_eq!(CHECKMAC_MODE_CHALLENGE, 0x00);
150 }
151}