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