Skip to main content

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}