capsules_extra/hmac.rs
1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! HMAC (Hash-based Message Authentication Code).
6//!
7//! Usage
8//! -----
9//!
10//! ```rust,ignore
11//! let hmac = &earlgrey::hmac::HMAC;
12//!
13//! let mux_hmac = static_init!(MuxHmac<'static, lowrisc::hmac::Hmac>, MuxHmac::new(hmac));
14//! digest::Digest::set_client(&earlgrey::hmac::HMAC, mux_hmac);
15//!
16//! let virtual_hmac_user = static_init!(
17//! VirtualMuxHmac<'static, lowrisc::hmac::Hmac>,
18//! VirtualMuxHmac::new(mux_hmac)
19//! );
20//! let hmac = static_init!(
21//! capsules::hmac::HmacDriver<'static, VirtualMuxHmac<'static, lowrisc::hmac::Hmac>>,
22//! capsules::hmac::HmacDriver::new(
23//! virtual_hmac_user,
24//! board_kernel.create_grant(&memory_allocation_cap),
25//! )
26//! );
27//! digest::Digest::set_client(virtual_hmac_user, hmac);
28//! ```
29
30use capsules_core::driver;
31use kernel::errorcode::into_statuscode;
32/// Syscall driver number.
33pub const DRIVER_NUM: usize = driver::NUM::Hmac as usize;
34
35/// Ids for read-only allow buffers
36mod ro_allow {
37 pub const KEY: usize = 0;
38 pub const DATA: usize = 1;
39 pub const COMPARE: usize = 2;
40 /// The number of allow buffers the kernel stores for this grant
41 pub const COUNT: u8 = 3;
42}
43
44/// Ids for read-write allow buffers
45mod rw_allow {
46 pub const DEST: usize = 2;
47 /// The number of allow buffers the kernel stores for this grant
48 pub const COUNT: u8 = 3;
49}
50
51use core::cell::Cell;
52
53use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
54use kernel::hil::digest;
55use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer};
56use kernel::syscall::{CommandReturn, SyscallDriver};
57use kernel::utilities::cells::{OptionalCell, TakeCell};
58use kernel::utilities::leasable_buffer::SubSlice;
59use kernel::utilities::leasable_buffer::SubSliceMut;
60use kernel::{ErrorCode, ProcessId};
61
62enum ShaOperation {
63 Sha256,
64 Sha384,
65 Sha512,
66}
67
68// Temporary buffer to copy the keys from userspace into
69//
70// Needs to be able to accommodate the largest key sizes, e.g. 512
71const TMP_KEY_BUFFER_SIZE: usize = 512 / 8;
72
73pub struct HmacDriver<'a, H: digest::Digest<'a, L>, const L: usize> {
74 hmac: &'a H,
75
76 active: Cell<bool>,
77
78 apps: Grant<
79 App,
80 UpcallCount<1>,
81 AllowRoCount<{ ro_allow::COUNT }>,
82 AllowRwCount<{ rw_allow::COUNT }>,
83 >,
84 processid: OptionalCell<ProcessId>,
85
86 data_buffer: TakeCell<'static, [u8]>,
87 data_copied: Cell<usize>,
88 dest_buffer: TakeCell<'static, [u8; L]>,
89}
90
91impl<
92 'a,
93 H: digest::Digest<'a, L> + digest::HmacSha256 + digest::HmacSha384 + digest::HmacSha512,
94 const L: usize,
95 > HmacDriver<'a, H, L>
96{
97 pub fn new(
98 hmac: &'a H,
99 data_buffer: &'static mut [u8],
100 dest_buffer: &'static mut [u8; L],
101 grant: Grant<
102 App,
103 UpcallCount<1>,
104 AllowRoCount<{ ro_allow::COUNT }>,
105 AllowRwCount<{ rw_allow::COUNT }>,
106 >,
107 ) -> HmacDriver<'a, H, L> {
108 HmacDriver {
109 hmac,
110 active: Cell::new(false),
111 apps: grant,
112 processid: OptionalCell::empty(),
113 data_buffer: TakeCell::new(data_buffer),
114 data_copied: Cell::new(0),
115 dest_buffer: TakeCell::new(dest_buffer),
116 }
117 }
118
119 fn run(&self) -> Result<(), ErrorCode> {
120 self.processid.map_or(Err(ErrorCode::RESERVE), |processid| {
121 self.apps
122 .enter(processid, |app, kernel_data| {
123 kernel_data
124 .get_readonly_processbuffer(ro_allow::KEY)
125 .and_then(|key| {
126 key.enter(|k| {
127 if let Some(op) = &app.sha_operation {
128 let mut tmp_key_buffer: [u8; TMP_KEY_BUFFER_SIZE] =
129 [0; TMP_KEY_BUFFER_SIZE];
130 let key_len = core::cmp::min(k.len(), TMP_KEY_BUFFER_SIZE);
131 k[..key_len].copy_to_slice(&mut tmp_key_buffer[..key_len]);
132
133 match op {
134 ShaOperation::Sha256 => self
135 .hmac
136 .set_mode_hmacsha256(&tmp_key_buffer[..key_len]),
137 ShaOperation::Sha384 => self
138 .hmac
139 .set_mode_hmacsha384(&tmp_key_buffer[..key_len]),
140 ShaOperation::Sha512 => self
141 .hmac
142 .set_mode_hmacsha512(&tmp_key_buffer[..key_len]),
143 }
144 } else {
145 Err(ErrorCode::INVAL)
146 }
147 })
148 })
149 .unwrap_or(Err(ErrorCode::RESERVE))?;
150
151 kernel_data
152 .get_readonly_processbuffer(ro_allow::DATA)
153 .and_then(|data| {
154 data.enter(|data| {
155 let mut static_buffer_len = 0;
156 self.data_buffer.map(|buf| {
157 // Determine the size of the static buffer we have
158 static_buffer_len = buf.len();
159
160 if static_buffer_len > data.len() {
161 static_buffer_len = data.len()
162 }
163
164 self.data_copied.set(static_buffer_len);
165
166 // Copy the data into the static buffer
167 data[..static_buffer_len]
168 .copy_to_slice(&mut buf[..static_buffer_len]);
169 });
170
171 // Add the data from the static buffer to the HMAC
172 let mut lease_buf = SubSliceMut::new(
173 self.data_buffer.take().ok_or(ErrorCode::RESERVE)?,
174 );
175 lease_buf.slice(0..static_buffer_len);
176 if let Err(e) = self.hmac.add_mut_data(lease_buf) {
177 self.data_buffer.replace(e.1.take());
178 return Err(e.0);
179 }
180 Ok(())
181 })
182 })
183 .unwrap_or(Err(ErrorCode::RESERVE))
184 })
185 .unwrap_or_else(|err| Err(err.into()))
186 })
187 }
188
189 fn calculate_digest(&self) -> Result<(), ErrorCode> {
190 self.data_copied.set(0);
191
192 if let Err(e) = self
193 .hmac
194 .run(self.dest_buffer.take().ok_or(ErrorCode::RESERVE)?)
195 {
196 // Error, clear the processid and data
197 self.hmac.clear_data();
198 self.processid.clear();
199 self.dest_buffer.replace(e.1);
200
201 return Err(e.0);
202 }
203
204 Ok(())
205 }
206
207 fn verify_digest(&self) -> Result<(), ErrorCode> {
208 self.data_copied.set(0);
209
210 if let Err(e) = self
211 .hmac
212 .verify(self.dest_buffer.take().ok_or(ErrorCode::RESERVE)?)
213 {
214 // Error, clear the processid and data
215 self.hmac.clear_data();
216 self.processid.clear();
217 self.dest_buffer.replace(e.1);
218
219 return Err(e.0);
220 }
221
222 Ok(())
223 }
224
225 fn check_queue(&self) {
226 for appiter in self.apps.iter() {
227 let started_command = appiter.enter(|app, _| {
228 // If an app is already running let it complete
229 if self.processid.is_some() {
230 return true;
231 }
232
233 // If this app has a pending command let's use it.
234 app.pending_run_app.take().is_some_and(|processid| {
235 // Mark this driver as being in use.
236 self.processid.set(processid);
237 // Actually make the buzz happen.
238 self.run() == Ok(())
239 })
240 });
241 if started_command {
242 break;
243 }
244 }
245 }
246}
247
248impl<
249 'a,
250 H: digest::Digest<'a, L> + digest::HmacSha256 + digest::HmacSha384 + digest::HmacSha512,
251 const L: usize,
252 > digest::ClientData<L> for HmacDriver<'a, H, L>
253{
254 // Because data needs to be copied from a userspace buffer into a kernel (RAM) one,
255 // we always pass mut data; this callback should never be invoked.
256 fn add_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSlice<'static, u8>) {}
257
258 fn add_mut_data_done(&self, _result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>) {
259 self.processid.map(move |id| {
260 self.apps
261 .enter(id, move |app, kernel_data| {
262 let mut data_len = 0;
263 let mut exit = false;
264 let mut static_buffer_len = 0;
265
266 self.data_buffer.replace(data.take());
267
268 self.data_buffer.map(|buf| {
269 let ret = kernel_data
270 .get_readonly_processbuffer(ro_allow::DATA)
271 .and_then(|data| {
272 data.enter(|data| {
273 // Determine the size of the static buffer we have
274 static_buffer_len = buf.len();
275 // Determine how much data we have already copied
276 let copied_data = self.data_copied.get();
277
278 data_len = data.len();
279
280 if data_len > copied_data {
281 let remaining_data = &data[copied_data..];
282 let remaining_len = data_len - copied_data;
283
284 if remaining_len < static_buffer_len {
285 remaining_data.copy_to_slice(&mut buf[..remaining_len]);
286 } else {
287 remaining_data[..static_buffer_len].copy_to_slice(buf);
288 }
289 }
290 Ok(())
291 })
292 })
293 .unwrap_or(Err(ErrorCode::RESERVE));
294
295 if ret == Err(ErrorCode::RESERVE) {
296 // No data buffer, clear the processid and data
297 self.hmac.clear_data();
298 self.processid.clear();
299 exit = true;
300 }
301 });
302
303 if exit {
304 return;
305 }
306
307 if static_buffer_len > 0 {
308 let copied_data = self.data_copied.get();
309
310 if data_len > copied_data {
311 // Update the amount of data copied
312 self.data_copied.set(copied_data + static_buffer_len);
313
314 let mut lease_buf = SubSliceMut::new(self.data_buffer.take().unwrap());
315
316 // Add the data from the static buffer to the HMAC
317 if data_len < (copied_data + static_buffer_len) {
318 lease_buf.slice(..(data_len - copied_data))
319 }
320
321 if self.hmac.add_mut_data(lease_buf).is_err() {
322 // Error, clear the processid and data
323 self.hmac.clear_data();
324 self.processid.clear();
325 return;
326 }
327
328 // Return as we don't want to run the digest yet
329 return;
330 }
331 }
332
333 // If we get here we are ready to run the digest, reset the copied data
334 if app.op.get().unwrap() == UserSpaceOp::Run {
335 if let Err(e) = self.calculate_digest() {
336 kernel_data
337 .schedule_upcall(0, (into_statuscode(e.into()), 0, 0))
338 .ok();
339 }
340 } else if app.op.get().unwrap() == UserSpaceOp::Verify {
341 let _ = kernel_data
342 .get_readonly_processbuffer(ro_allow::COMPARE)
343 .and_then(|compare| {
344 compare.enter(|compare| {
345 let mut static_buffer_len = 0;
346 self.dest_buffer.map(|buf| {
347 // Determine the size of the static buffer we have
348 static_buffer_len = buf.len();
349
350 if static_buffer_len > compare.len() {
351 static_buffer_len = compare.len()
352 }
353
354 self.data_copied.set(static_buffer_len);
355
356 // Copy the data into the static buffer
357 compare[..static_buffer_len]
358 .copy_to_slice(&mut buf[..static_buffer_len]);
359 });
360 })
361 });
362
363 if let Err(e) = self.verify_digest() {
364 kernel_data
365 .schedule_upcall(1, (into_statuscode(e.into()), 0, 0))
366 .ok();
367 }
368 } else {
369 kernel_data.schedule_upcall(0, (0, 0, 0)).ok();
370 }
371 })
372 .map_err(|err| {
373 if err == kernel::process::Error::NoSuchApp
374 || err == kernel::process::Error::InactiveApp
375 {
376 self.processid.clear();
377 }
378 })
379 });
380
381 self.check_queue();
382 }
383}
384
385impl<
386 'a,
387 H: digest::Digest<'a, L> + digest::HmacSha256 + digest::HmacSha384 + digest::HmacSha512,
388 const L: usize,
389 > digest::ClientHash<L> for HmacDriver<'a, H, L>
390{
391 fn hash_done(&self, result: Result<(), ErrorCode>, digest: &'static mut [u8; L]) {
392 self.processid.map(|id| {
393 self.apps
394 .enter(id, |_, kernel_data| {
395 self.hmac.clear_data();
396
397 let pointer = digest[0] as *mut u8;
398
399 let _ = kernel_data
400 .get_readwrite_processbuffer(rw_allow::DEST)
401 .and_then(|dest| {
402 dest.mut_enter(|dest| {
403 let len = dest.len();
404
405 if len < L {
406 dest.copy_from_slice(&digest[0..len]);
407 } else {
408 dest[0..L].copy_from_slice(digest);
409 }
410 })
411 });
412
413 match result {
414 Ok(()) => kernel_data.schedule_upcall(0, (0, pointer as usize, 0)),
415 Err(e) => kernel_data
416 .schedule_upcall(0, (into_statuscode(e.into()), pointer as usize, 0)),
417 }
418 .ok();
419
420 // Clear the current processid as it has finished running
421 self.processid.clear();
422 })
423 .map_err(|err| {
424 if err == kernel::process::Error::NoSuchApp
425 || err == kernel::process::Error::InactiveApp
426 {
427 self.processid.clear();
428 }
429 })
430 });
431
432 self.check_queue();
433 self.dest_buffer.replace(digest);
434 }
435}
436
437impl<
438 'a,
439 H: digest::Digest<'a, L> + digest::HmacSha256 + digest::HmacSha384 + digest::HmacSha512,
440 const L: usize,
441 > digest::ClientVerify<L> for HmacDriver<'a, H, L>
442{
443 fn verification_done(&self, result: Result<bool, ErrorCode>, compare: &'static mut [u8; L]) {
444 self.processid.map(|id| {
445 self.apps
446 .enter(id, |_app, kernel_data| {
447 self.hmac.clear_data();
448
449 match result {
450 Ok(equal) => kernel_data.schedule_upcall(1, (0, equal as usize, 0)),
451 Err(e) => kernel_data.schedule_upcall(1, (into_statuscode(e.into()), 0, 0)),
452 }
453 .ok();
454
455 // Clear the current processid as it has finished running
456 self.processid.clear();
457 })
458 .map_err(|err| {
459 if err == kernel::process::Error::NoSuchApp
460 || err == kernel::process::Error::InactiveApp
461 {
462 self.processid.clear();
463 }
464 })
465 });
466
467 self.check_queue();
468 self.dest_buffer.replace(compare);
469 }
470}
471
472/// Specify memory regions to be used.
473///
474/// ### `allow_num`
475///
476/// - `0`: Allow a buffer for storing the key. The kernel will read from this
477/// when running This should not be changed after running `run` until the HMAC
478/// has completed
479/// - `1`: Allow a buffer for storing the buffer. The kernel will read from this
480/// when running This should not be changed after running `run` until the HMAC
481/// has completed
482/// - `2`: Allow a buffer for storing the digest. The kernel will fill this with
483/// the HMAC digest before calling the `hash_done` callback.
484impl<
485 'a,
486 H: digest::Digest<'a, L> + digest::HmacSha256 + digest::HmacSha384 + digest::HmacSha512,
487 const L: usize,
488 > SyscallDriver for HmacDriver<'a, H, L>
489{
490 // Subscribe to HmacDriver events.
491 //
492 // ### `subscribe_num`
493 //
494 // - `0`: Subscribe to interrupts from HMAC events. The callback signature
495 // is `fn(result: u32)`
496
497 /// Setup and run the HMAC hardware
498 ///
499 /// We expect userspace to setup buffers for the key, data and digest.
500 /// These buffers must be allocated and specified to the kernel from the
501 /// above allow calls.
502 ///
503 /// We expect userspace not to change the value while running. If userspace
504 /// changes the value we have no guarantee of what is passed to the
505 /// hardware. This isn't a security issue, it will just prove the requesting
506 /// app with invalid data.
507 ///
508 /// The driver will take care of clearing data from the underlying implementation
509 /// by calling the `clear_data()` function when the `hash_complete()` callback
510 /// is called or if an error is encountered.
511 ///
512 /// ### `command_num`
513 ///
514 /// - `0`: set_algorithm
515 /// - `1`: run
516 /// - `2`: update
517 /// - `3`: finish
518 fn command(
519 &self,
520 command_num: usize,
521 data1: usize,
522 _data2: usize,
523 processid: ProcessId,
524 ) -> CommandReturn {
525 let match_or_empty_or_nonexistant = self.processid.map_or(true, |owning_app| {
526 // We have recorded that an app has ownership of the HMAC.
527
528 // If the HMAC is still active, then we need to wait for the operation
529 // to finish and the app, whether it exists or not (it may have crashed),
530 // still owns this capsule. If the HMAC is not active, then
531 // we need to verify that that application still exists, and remove
532 // it as owner if not.
533 if self.active.get() {
534 owning_app == processid
535 } else {
536 // Check the app still exists.
537 //
538 // If the `.enter()` succeeds, then the app is still valid, and
539 // we can check if the owning app matches the one that called
540 // the command. If the `.enter()` fails, then the owning app no
541 // longer exists and we return `true` to signify the
542 // "or_nonexistant" case.
543 self.apps
544 .enter(owning_app, |_, _| owning_app == processid)
545 .unwrap_or(true)
546 }
547 });
548
549 let app_match = self.processid.map_or(false, |owning_app| {
550 // We have recorded that an app has ownership of the HMAC.
551
552 // If the HMAC is still active, then we need to wait for the operation
553 // to finish and the app, whether it exists or not (it may have crashed),
554 // still owns this capsule. If the HMAC is not active, then
555 // we need to verify that that application still exists, and remove
556 // it as owner if not.
557 if self.active.get() {
558 owning_app == processid
559 } else {
560 // Check the app still exists.
561 //
562 // If the `.enter()` succeeds, then the app is still valid, and
563 // we can check if the owning app matches the one that called
564 // the command. If the `.enter()` fails, then the owning app no
565 // longer exists and we return `true` to signify the
566 // "or_nonexistant" case.
567 self.apps
568 .enter(owning_app, |_, _| owning_app == processid)
569 .unwrap_or(true)
570 }
571 });
572
573 // Try the commands where we want to start an operation *not* entered in
574 // an app grant first.
575 if match_or_empty_or_nonexistant
576 && (command_num == 1 || command_num == 2 || command_num == 4)
577 {
578 self.processid.set(processid);
579
580 let _ = self.apps.enter(processid, |app, _| {
581 if command_num == 1 {
582 // run
583 // Use key and data to compute hash
584 // This will trigger a callback once the digest is generated
585 app.op.set(Some(UserSpaceOp::Run));
586 } else if command_num == 2 {
587 // update
588 // Input key and data, don't compute final hash yet
589 // This will trigger a callback once the data has been added.
590 app.op.set(Some(UserSpaceOp::Update));
591 } else if command_num == 4 {
592 // verify
593 // Use key and data to compute hash and comapre it against
594 // the digest
595 app.op.set(Some(UserSpaceOp::Verify));
596 }
597 });
598
599 return if let Err(e) = self.run() {
600 self.hmac.clear_data();
601 self.processid.clear();
602 self.check_queue();
603 CommandReturn::failure(e)
604 } else {
605 CommandReturn::success()
606 };
607 }
608
609 self.apps
610 .enter(processid, |app, kernel_data| {
611 match command_num {
612 // set_algorithm
613 0 => {
614 match data1 {
615 // SHA256
616 0 => {
617 app.sha_operation = Some(ShaOperation::Sha256);
618 CommandReturn::success()
619 }
620 // SHA384
621 1 => {
622 app.sha_operation = Some(ShaOperation::Sha384);
623 CommandReturn::success()
624 }
625 // SHA512
626 2 => {
627 app.sha_operation = Some(ShaOperation::Sha512);
628 CommandReturn::success()
629 }
630 _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
631 }
632 }
633
634 // run
635 1 => {
636 // There is an active app, so queue this request (if possible).
637 if app.pending_run_app.is_some() {
638 // No more room in the queue, nowhere to store this
639 // request.
640 CommandReturn::failure(ErrorCode::NOMEM)
641 } else {
642 // We can store this, so lets do it.
643 app.pending_run_app = Some(processid);
644 app.op.set(Some(UserSpaceOp::Run));
645 CommandReturn::success()
646 }
647 }
648
649 // update
650 2 => {
651 // There is an active app, so queue this request (if possible).
652 if app.pending_run_app.is_some() {
653 // No more room in the queue, nowhere to store this
654 // request.
655 CommandReturn::failure(ErrorCode::NOMEM)
656 } else {
657 // We can store this, so lets do it.
658 app.pending_run_app = Some(processid);
659 app.op.set(Some(UserSpaceOp::Update));
660 CommandReturn::success()
661 }
662 }
663
664 // finish
665 // Compute final hash yet, useful after a update command
666 3 => {
667 if app_match {
668 if let Err(e) = self.calculate_digest() {
669 kernel_data
670 .schedule_upcall(
671 0,
672 (kernel::errorcode::into_statuscode(e.into()), 0, 0),
673 )
674 .ok();
675 }
676 CommandReturn::success()
677 } else {
678 // We don't queue this request, the user has to call
679 // `update` first.
680 CommandReturn::failure(ErrorCode::OFF)
681 }
682 }
683
684 // verify
685 4 => {
686 // There is an active app, so queue this request (if possible).
687 if app.pending_run_app.is_some() {
688 // No more room in the queue, nowhere to store this
689 // request.
690 CommandReturn::failure(ErrorCode::NOMEM)
691 } else {
692 // We can store this, so lets do it.
693 app.pending_run_app = Some(processid);
694 app.op.set(Some(UserSpaceOp::Verify));
695 CommandReturn::success()
696 }
697 }
698
699 // verify_finish
700 // Use key and data to compute hash and compare it against
701 // the digest, useful after a update command
702 5 => {
703 if app_match {
704 let _ = kernel_data
705 .get_readonly_processbuffer(ro_allow::COMPARE)
706 .and_then(|compare| {
707 compare.enter(|compare| {
708 let mut static_buffer_len = 0;
709 self.dest_buffer.map(|buf| {
710 // Determine the size of the static buffer we have
711 static_buffer_len = buf.len();
712
713 if static_buffer_len > compare.len() {
714 static_buffer_len = compare.len()
715 }
716
717 self.data_copied.set(static_buffer_len);
718
719 // Copy the data into the static buffer
720 compare[..static_buffer_len]
721 .copy_to_slice(&mut buf[..static_buffer_len]);
722 });
723 })
724 });
725
726 if let Err(e) = self.verify_digest() {
727 kernel_data
728 .schedule_upcall(1, (into_statuscode(e.into()), 0, 0))
729 .ok();
730 }
731 CommandReturn::success()
732 } else {
733 // We don't queue this request, the user has to call
734 // `update` first.
735 CommandReturn::failure(ErrorCode::OFF)
736 }
737 }
738
739 // default
740 _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
741 }
742 })
743 .unwrap_or_else(|err| err.into())
744 }
745
746 fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
747 self.apps.enter(processid, |_, _| {})
748 }
749}
750
751#[derive(Copy, Clone, PartialEq)]
752enum UserSpaceOp {
753 Run,
754 Update,
755 Verify,
756}
757
758#[derive(Default)]
759pub struct App {
760 pending_run_app: Option<ProcessId>,
761 sha_operation: Option<ShaOperation>,
762 op: Cell<Option<UserSpaceOp>>,
763}