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::{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}
53
54/// HMAC interface error
55#[derive(Debug, Clone, Copy, PartialEq)]
56#[cfg_attr(feature = "defmt", derive(defmt::Format))]
57pub enum Error {
58    /// Purpose of the selected block does not match the configured key purpose.
59    KeyPurposeMismatch,
60}
61
62/// The peripheral can be configured to deliver its output directly to
63/// software. It can also deliver to other peripherals.
64#[derive(Debug, Clone, Copy, PartialEq)]
65#[cfg_attr(feature = "defmt", derive(defmt::Format))]
66#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")]
67pub enum HmacPurpose {
68    /// HMAC is used to re-enable JTAG after soft-disabling it.
69    ToJtag     = 6,
70    /// HMAC is provided to the digital signature peripheral to decrypt the
71    /// private key.
72    ToDs       = 7,
73    /// Provides a message and reads the result.
74    ToUser     = 8,
75    /// HMAC is used for both the digital signature and JTAG.
76    ToDsOrJtag = 5,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq)]
80#[cfg_attr(feature = "defmt", derive(defmt::Format))]
81/// Represents the key identifiers for the HMAC peripheral.
82pub enum KeyId {
83    /// Key 0.
84    Key0 = 0,
85    /// Key 1.
86    Key1 = 1,
87    /// Key 2.
88    Key2 = 2,
89    /// Key 3.
90    Key3 = 3,
91    /// Key 4.
92    Key4 = 4,
93    /// Key 5.
94    Key5 = 5,
95}
96
97enum NextCommand {
98    None,
99    MessageIng,
100    MessagePad,
101}
102
103impl<'d> Hmac<'d> {
104    /// Creates a new instance of the HMAC peripheral.
105    pub fn new(hmac: HMAC<'d>) -> Self {
106        let guard = GenericPeripheralGuard::new();
107
108        Self {
109            hmac,
110            alignment_helper: AlignmentHelper::default(),
111            byte_written: 64,
112            next_command: NextCommand::None,
113            _guard: guard,
114        }
115    }
116
117    fn regs(&self) -> &pac::hmac::RegisterBlock {
118        self.hmac.register_block()
119    }
120
121    /// Enables the HMAC module.
122    ///
123    /// Before these steps, the peripheral clock bits for
124    /// HMAC and SHA must be set and the corresponding peripheral
125    /// reset bits must be cleared.
126    pub fn init(&mut self) {
127        self.regs().set_start().write(|w| w.set_start().set_bit());
128        self.alignment_helper.reset();
129        self.byte_written = 64;
130        self.next_command = NextCommand::None;
131    }
132
133    /// Configures HMAC keys and key purposes.
134    pub fn configure(&mut self, m: HmacPurpose, key_id: KeyId) -> nb::Result<(), Error> {
135        self.regs()
136            .set_para_purpose()
137            .write(|w| unsafe { w.purpose_set().bits(m as u8) });
138        self.regs()
139            .set_para_key()
140            .write(|w| unsafe { w.key_set().bits(key_id as u8) });
141        self.regs()
142            .set_para_finish()
143            .write(|w| w.set_para_end().set_bit());
144
145        if self.regs().query_error().read().query_check().bit_is_set() {
146            return Err(nb::Error::Other(Error::KeyPurposeMismatch));
147        }
148
149        Ok(())
150    }
151
152    /// Processes the message block by block.
153    ///
154    /// Must be called as many times as necessary while `msg.len() > 0`.
155    pub fn update<'a>(&mut self, msg: &'a [u8]) -> nb::Result<&'a [u8], Infallible> {
156        if self.is_busy() {
157            return Err(nb::Error::WouldBlock);
158        }
159
160        self.next_command();
161
162        let remaining = self.write_data(msg).unwrap();
163
164        Ok(remaining)
165    }
166
167    /// Finalizes the HMAC computation and retrieves the resulting hash output.
168    pub fn finalize(&mut self, output: &mut [u8]) -> nb::Result<(), Infallible> {
169        if self.is_busy() {
170            return Err(nb::Error::WouldBlock);
171        }
172
173        self.next_command();
174
175        let msg_len = self.byte_written as u64;
176
177        nb::block!(self.write_data(&[0x80])).unwrap();
178        nb::block!(self.flush_data()).unwrap();
179        self.next_command();
180        debug_assert!(self.byte_written.is_multiple_of(4));
181
182        self.padding(msg_len);
183
184        // Checking if the message is one block including padding
185        if msg_len < 64 + 56 {
186            self.regs()
187                .one_block()
188                .write(|w| w.set_one_block().set_bit());
189
190            while self.is_busy() {}
191        }
192
193        self.alignment_helper.volatile_read_regset(
194            #[cfg(esp32s2)]
195            self.regs().rd_result_(0).as_ptr(),
196            #[cfg(not(esp32s2))]
197            self.regs().rd_result_mem(0).as_ptr(),
198            output,
199            core::cmp::min(output.len(), 32),
200        );
201
202        self.regs()
203            .set_result_finish()
204            .write(|w| w.set_result_end().set_bit());
205        self.byte_written = 64;
206        self.next_command = NextCommand::None;
207        Ok(())
208    }
209
210    fn is_busy(&mut self) -> bool {
211        self.regs().query_busy().read().busy_state().bit_is_set()
212    }
213
214    fn next_command(&mut self) {
215        match self.next_command {
216            NextCommand::MessageIng => {
217                self.regs()
218                    .set_message_ing()
219                    .write(|w| w.set_text_ing().set_bit());
220            }
221            NextCommand::MessagePad => {
222                self.regs()
223                    .set_message_pad()
224                    .write(|w| w.set_text_pad().set_bit());
225            }
226            NextCommand::None => {}
227        }
228        self.next_command = NextCommand::None;
229    }
230
231    fn write_data<'a>(&mut self, incoming: &'a [u8]) -> nb::Result<&'a [u8], Infallible> {
232        let (remaining, bound_reached) = self.alignment_helper.aligned_volatile_copy(
233            #[cfg(esp32s2)]
234            self.regs().wr_message_(0).as_ptr(),
235            #[cfg(not(esp32s2))]
236            self.regs().wr_message_mem(0).as_ptr(),
237            incoming,
238            64,
239            self.byte_written % 64,
240        );
241
242        self.byte_written = self
243            .byte_written
244            .wrapping_add(incoming.len() - remaining.len());
245
246        if bound_reached {
247            self.regs()
248                .set_message_one()
249                .write(|w| w.set_text_one().set_bit());
250
251            if remaining.len() >= 56 {
252                self.next_command = NextCommand::MessageIng;
253            } else {
254                self.next_command = NextCommand::MessagePad;
255            }
256        }
257
258        Ok(remaining)
259    }
260
261    fn flush_data(&mut self) -> nb::Result<(), Infallible> {
262        if self.is_busy() {
263            return Err(nb::Error::WouldBlock);
264        }
265
266        let flushed = self.alignment_helper.flush_to(
267            #[cfg(esp32s2)]
268            self.regs().wr_message_(0).as_ptr(),
269            #[cfg(not(esp32s2))]
270            self.regs().wr_message_mem(0).as_ptr(),
271            self.byte_written % 64,
272        );
273
274        self.byte_written = self.byte_written.wrapping_add(flushed);
275        if flushed > 0 && self.byte_written.is_multiple_of(64) {
276            self.regs()
277                .set_message_one()
278                .write(|w| w.set_text_one().set_bit());
279            while self.is_busy() {}
280            self.next_command = NextCommand::MessagePad;
281        }
282
283        Ok(())
284    }
285
286    fn padding(&mut self, msg_len: u64) {
287        let mod_cursor = self.byte_written % 64;
288
289        // The padding will be spanned over 2 blocks
290        if mod_cursor > 56 {
291            let pad_len = 64 - mod_cursor;
292            self.alignment_helper.volatile_write(
293                #[cfg(esp32s2)]
294                self.regs().wr_message_(0).as_ptr(),
295                #[cfg(not(esp32s2))]
296                self.regs().wr_message_mem(0).as_ptr(),
297                0_u8,
298                pad_len,
299                mod_cursor,
300            );
301            self.regs()
302                .set_message_one()
303                .write(|w| w.set_text_one().set_bit());
304            self.byte_written = self.byte_written.wrapping_add(pad_len);
305            debug_assert!(self.byte_written.is_multiple_of(64));
306            while self.is_busy() {}
307            self.next_command = NextCommand::MessagePad;
308            self.next_command();
309        }
310
311        let mod_cursor = self.byte_written % 64;
312        let pad_len = 64 - mod_cursor - core::mem::size_of::<u64>();
313
314        self.alignment_helper.volatile_write(
315            #[cfg(esp32s2)]
316            self.regs().wr_message_(0).as_ptr(),
317            #[cfg(not(esp32s2))]
318            self.regs().wr_message_mem(0).as_ptr(),
319            0_u8,
320            pad_len,
321            mod_cursor,
322        );
323
324        self.byte_written = self.byte_written.wrapping_add(pad_len);
325
326        assert_eq!(self.byte_written % 64, 64 - core::mem::size_of::<u64>());
327
328        // Add padded key
329        let len_mem = (msg_len * 8).to_be_bytes();
330
331        self.alignment_helper.aligned_volatile_copy(
332            #[cfg(esp32s2)]
333            self.regs().wr_message_(0).as_ptr(),
334            #[cfg(not(esp32s2))]
335            self.regs().wr_message_mem(0).as_ptr(),
336            &len_mem,
337            64,
338            64 - core::mem::size_of::<u64>(),
339        );
340        self.regs()
341            .set_message_one()
342            .write(|w| w.set_text_one().set_bit());
343
344        while self.is_busy() {}
345    }
346}