Skip to main content

esp_hal/
hmac.rs

1//! # Hash-based Message Authentication Code (HMAC) Accelerator
2//!
3//! ## Overview
4//! HMAC is a secure authentication technique that verifies the authenticity and
5//! integrity of a message with a pre-shared key. This module provides hardware
6//! acceleration for SHA256-HMAC generation using a key burned into an eFuse
7//! block.
8//!
9//! Main features:
10//!
11//! - Standard HMAC-SHA-256 algorithm.
12//! - Hash result only accessible by configurable hardware peripheral (in downstream mode).
13//! - Compatible to challenge-response authentication algorithm.
14//! - Generates required keys for the Digital Signature (DS) peripheral (in downstream mode).
15//! - Re-enables soft-disabled JTAG (in downstream mode).
16//!
17//! ## Configuration
18//! The HMAC module can be used in two modes - in ”upstream” mode the HMAC
19//! message is supplied by the user and the calculation result is read back by
20//! the user. In ”downstream” mode the HMAC module is used as a Key Derivation
21//! Function (KDF) for other internal hardwares.
22//!
23//! ### HMAC padding
24//!
25//! The HMAC padding is handled by the driver. In downstream mode, users do not
26//! need to input any message or apply padding. The HMAC module uses a default
27//! 32-byte pattern of 0x00 for re-enabling JTAG and a 32-byte pattern of 0xff
28//! for deriving the AES key for the DS module.
29//!
30//! ## Examples
31//! Visit the [HMAC] example to learn how to use the HMAC accelerator
32//!
33//! [HMAC]: https://github.com/esp-rs/esp-hal/blob/main/examples/peripheral/hmac/src/main.rs
34
35use core::convert::Infallible;
36
37use crate::{
38    pac,
39    peripherals::HMAC,
40    reg_access::{AlignmentHelper, SocDependentEndianess},
41    system::{CryptoClockGuard, GenericPeripheralGuard, Peripheral as PeripheralEnable},
42};
43
44/// Provides an interface for interacting with the HMAC hardware peripheral.
45/// Computes HMACs for cryptographic purposes and ensures data integrity and authenticity.
46pub struct Hmac<'d> {
47    hmac: HMAC<'d>,
48    alignment_helper: AlignmentHelper<SocDependentEndianess>,
49    byte_written: usize,
50    next_command: NextCommand,
51    _guard: GenericPeripheralGuard<{ PeripheralEnable::Hmac as u8 }>,
52    _clock_guard: CryptoClockGuard,
53}
54
55/// HMAC interface error
56#[derive(Debug, Clone, Copy, PartialEq)]
57#[cfg_attr(feature = "defmt", derive(defmt::Format))]
58pub enum Error {
59    /// Purpose of the selected block does not match the configured key purpose.
60    KeyPurposeMismatch,
61}
62
63/// The peripheral can be configured to deliver its output directly to
64/// software. It can also deliver to other peripherals.
65#[derive(Debug, Clone, Copy, PartialEq)]
66#[cfg_attr(feature = "defmt", derive(defmt::Format))]
67#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")]
68pub enum HmacPurpose {
69    /// HMAC is used to re-enable JTAG after soft-disabling it.
70    ToJtag     = 6,
71    /// HMAC is provided to the digital signature peripheral to decrypt the
72    /// private key.
73    ToDs       = 7,
74    /// Provides a message and reads the result.
75    ToUser     = 8,
76    /// HMAC is used for both the digital signature and JTAG.
77    ToDsOrJtag = 5,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq)]
81#[cfg_attr(feature = "defmt", derive(defmt::Format))]
82/// Represents the key identifiers for the HMAC peripheral.
83pub enum KeyId {
84    /// Key 0.
85    Key0 = 0,
86    /// Key 1.
87    Key1 = 1,
88    /// Key 2.
89    Key2 = 2,
90    /// Key 3.
91    Key3 = 3,
92    /// Key 4.
93    Key4 = 4,
94    /// Key 5.
95    Key5 = 5,
96}
97
98enum NextCommand {
99    None,
100    MessageIng,
101    MessagePad,
102}
103
104impl<'d> Hmac<'d> {
105    /// Creates a new instance of the HMAC peripheral.
106    pub fn new(hmac: HMAC<'d>) -> Self {
107        let clock_guard = CryptoClockGuard::new();
108        let guard = GenericPeripheralGuard::new();
109
110        Self {
111            hmac,
112            alignment_helper: AlignmentHelper::default(),
113            byte_written: 64,
114            next_command: NextCommand::None,
115            _guard: guard,
116            _clock_guard: clock_guard,
117        }
118    }
119
120    fn regs(&self) -> &pac::hmac::RegisterBlock {
121        self.hmac.register_block()
122    }
123
124    /// Enables the HMAC module.
125    ///
126    /// Before these steps, the peripheral clock bits for
127    /// HMAC and SHA must be set and the corresponding peripheral
128    /// reset bits must be cleared.
129    pub fn init(&mut self) {
130        self.regs().set_start().write(|w| w.set_start().set_bit());
131        self.alignment_helper.reset();
132        self.byte_written = 64;
133        self.next_command = NextCommand::None;
134    }
135
136    /// Configures HMAC keys and key purposes.
137    pub fn configure(&mut self, m: HmacPurpose, key_id: KeyId) -> nb::Result<(), Error> {
138        self.regs()
139            .set_para_purpose()
140            .write(|w| unsafe { w.purpose_set().bits(m as u8) });
141        self.regs()
142            .set_para_key()
143            .write(|w| unsafe { w.key_set().bits(key_id as u8) });
144        self.regs()
145            .set_para_finish()
146            .write(|w| w.set_para_end().set_bit());
147
148        if self.regs().query_error().read().query_check().bit_is_set() {
149            return Err(nb::Error::Other(Error::KeyPurposeMismatch));
150        }
151
152        Ok(())
153    }
154
155    /// Processes the message block by block.
156    ///
157    /// Must be called as many times as necessary while `msg.len() > 0`.
158    pub fn update<'a>(&mut self, msg: &'a [u8]) -> nb::Result<&'a [u8], Infallible> {
159        if self.is_busy() {
160            return Err(nb::Error::WouldBlock);
161        }
162
163        self.next_command();
164
165        let remaining = self.write_data(msg).unwrap();
166
167        Ok(remaining)
168    }
169
170    /// Finalizes the HMAC computation and retrieves the resulting hash output.
171    pub fn finalize(&mut self, output: &mut [u8]) -> nb::Result<(), Infallible> {
172        if self.is_busy() {
173            return Err(nb::Error::WouldBlock);
174        }
175
176        self.next_command();
177
178        let msg_len = self.byte_written as u64;
179
180        nb::block!(self.write_data(&[0x80])).unwrap();
181        nb::block!(self.flush_data()).unwrap();
182        self.next_command();
183        debug_assert!(self.byte_written.is_multiple_of(4));
184
185        self.padding(msg_len);
186
187        // Checking if the message is one block including padding
188        if msg_len < 64 + 56 {
189            self.regs()
190                .one_block()
191                .write(|w| w.set_one_block().set_bit());
192
193            while self.is_busy() {}
194        }
195
196        self.alignment_helper.volatile_read_regset(
197            #[cfg(esp32s2)]
198            self.regs().rd_result_(0).as_ptr(),
199            #[cfg(not(esp32s2))]
200            self.regs().rd_result_mem(0).as_ptr(),
201            output,
202            core::cmp::min(output.len(), 32),
203        );
204
205        self.regs()
206            .set_result_finish()
207            .write(|w| w.set_result_end().set_bit());
208        self.byte_written = 64;
209        self.next_command = NextCommand::None;
210        Ok(())
211    }
212
213    fn is_busy(&mut self) -> bool {
214        self.regs().query_busy().read().busy_state().bit_is_set()
215    }
216
217    fn next_command(&mut self) {
218        match self.next_command {
219            NextCommand::MessageIng => {
220                self.regs()
221                    .set_message_ing()
222                    .write(|w| w.set_text_ing().set_bit());
223            }
224            NextCommand::MessagePad => {
225                self.regs()
226                    .set_message_pad()
227                    .write(|w| w.set_text_pad().set_bit());
228            }
229            NextCommand::None => {}
230        }
231        self.next_command = NextCommand::None;
232    }
233
234    fn write_data<'a>(&mut self, incoming: &'a [u8]) -> nb::Result<&'a [u8], Infallible> {
235        let (remaining, bound_reached) = self.alignment_helper.aligned_volatile_copy(
236            #[cfg(esp32s2)]
237            self.regs().wr_message_(0).as_ptr(),
238            #[cfg(not(esp32s2))]
239            self.regs().wr_message_mem(0).as_ptr(),
240            incoming,
241            64,
242            self.byte_written % 64,
243        );
244
245        self.byte_written = self
246            .byte_written
247            .wrapping_add(incoming.len() - remaining.len());
248
249        if bound_reached {
250            self.regs()
251                .set_message_one()
252                .write(|w| w.set_text_one().set_bit());
253
254            if remaining.len() >= 56 {
255                self.next_command = NextCommand::MessageIng;
256            } else {
257                self.next_command = NextCommand::MessagePad;
258            }
259        }
260
261        Ok(remaining)
262    }
263
264    fn flush_data(&mut self) -> nb::Result<(), Infallible> {
265        if self.is_busy() {
266            return Err(nb::Error::WouldBlock);
267        }
268
269        let flushed = self.alignment_helper.flush_to(
270            #[cfg(esp32s2)]
271            self.regs().wr_message_(0).as_ptr(),
272            #[cfg(not(esp32s2))]
273            self.regs().wr_message_mem(0).as_ptr(),
274            self.byte_written % 64,
275        );
276
277        self.byte_written = self.byte_written.wrapping_add(flushed);
278        if flushed > 0 && self.byte_written.is_multiple_of(64) {
279            self.regs()
280                .set_message_one()
281                .write(|w| w.set_text_one().set_bit());
282            while self.is_busy() {}
283            self.next_command = NextCommand::MessagePad;
284        }
285
286        Ok(())
287    }
288
289    fn padding(&mut self, msg_len: u64) {
290        let mod_cursor = self.byte_written % 64;
291
292        // The padding will be spanned over 2 blocks
293        if mod_cursor > 56 {
294            let pad_len = 64 - mod_cursor;
295            self.alignment_helper.volatile_write(
296                #[cfg(esp32s2)]
297                self.regs().wr_message_(0).as_ptr(),
298                #[cfg(not(esp32s2))]
299                self.regs().wr_message_mem(0).as_ptr(),
300                0_u8,
301                pad_len,
302                mod_cursor,
303            );
304            self.regs()
305                .set_message_one()
306                .write(|w| w.set_text_one().set_bit());
307            self.byte_written = self.byte_written.wrapping_add(pad_len);
308            debug_assert!(self.byte_written.is_multiple_of(64));
309            while self.is_busy() {}
310            self.next_command = NextCommand::MessagePad;
311            self.next_command();
312        }
313
314        let mod_cursor = self.byte_written % 64;
315        let pad_len = 64 - mod_cursor - core::mem::size_of::<u64>();
316
317        self.alignment_helper.volatile_write(
318            #[cfg(esp32s2)]
319            self.regs().wr_message_(0).as_ptr(),
320            #[cfg(not(esp32s2))]
321            self.regs().wr_message_mem(0).as_ptr(),
322            0_u8,
323            pad_len,
324            mod_cursor,
325        );
326
327        self.byte_written = self.byte_written.wrapping_add(pad_len);
328
329        assert_eq!(self.byte_written % 64, 64 - core::mem::size_of::<u64>());
330
331        // Add padded key
332        let len_mem = (msg_len * 8).to_be_bytes();
333
334        self.alignment_helper.aligned_volatile_copy(
335            #[cfg(esp32s2)]
336            self.regs().wr_message_(0).as_ptr(),
337            #[cfg(not(esp32s2))]
338            self.regs().wr_message_mem(0).as_ptr(),
339            &len_mem,
340            64,
341            64 - core::mem::size_of::<u64>(),
342        );
343        self.regs()
344            .set_message_one()
345            .write(|w| w.set_text_one().set_bit());
346
347        while self.is_busy() {}
348    }
349}