1//! Shim lock protocol.
2
3#![cfg(any(
4 target_arch = "i386",
5 target_arch = "x86_64",
6 target_arch = "arm",
7 target_arch = "aarch64"
8))]
9
10use crate::proto::unsafe_protocol;
11use crate::result::Error;
12use crate::{Result, Status};
13use core::ffi::c_void;
14use core::mem::MaybeUninit;
15
16// The `PE_COFF_LOADER_IMAGE_CONTEXT` type. None of our methods need to inspect
17// the fields of this struct, we just need to make sure it is the right size.
18#[repr(C)]
19struct Context {
20 _image_address: u64,
21 _image_size: u64,
22 _entry_point: u64,
23 _size_of_headers: usize,
24 _image_type: u16,
25 _number_of_sections: u16,
26 _section_alignment: u32,
27 _first_section: *const c_void,
28 _reloc_dir: *const c_void,
29 _sec_dir: *const c_void,
30 _number_of_rva_and_sizes: u64,
31 _pe_hdr: *const c_void,
32}
33
34const SHA1_DIGEST_SIZE: usize = 20;
35const SHA256_DIGEST_SIZE: usize = 32;
36
37/// Authenticode hashes of some UEFI application
38pub struct Hashes {
39 /// SHA256 Authenticode Digest
40 pub sha256: [u8; SHA256_DIGEST_SIZE],
41 /// SHA1 Authenticode Digest
42 pub sha1: [u8; SHA1_DIGEST_SIZE],
43}
44
45// These macros set the correct calling convention for the Shim protocol methods.
46
47#[cfg(any(target_arch = "i386", target_arch = "x86_64"))]
48macro_rules! shim_function {
49 (fn $args:tt -> $return_type:ty) => (extern "sysv64" fn $args -> $return_type)
50}
51
52#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
53macro_rules! shim_function {
54 (fn $args:tt -> $return_type:ty) => (extern "C" fn $args -> $return_type)
55}
56
57/// The Shim lock protocol.
58///
59/// This protocol is not part of the UEFI specification, but is
60/// installed by the [Shim bootloader](https://github.com/rhboot/shim)
61/// which is commonly used by Linux distributions to support UEFI
62/// Secure Boot. Shim is built with an embedded certificate that is
63/// used to validate another EFI application before running it. That
64/// application may itself be a bootloader that needs to validate
65/// another EFI application before running it, and the shim lock
66/// protocol exists to support that.
67#[repr(C)]
68#[unsafe_protocol("605dab50-e046-4300-abb6-3dd810dd8b23")]
69pub struct ShimLock {
70 verify: shim_function! { fn(buffer: *const u8, size: u32) -> Status },
71 hash: shim_function! {
72 fn(
73 buffer: *const u8,
74 size: u32,
75 context: *mut Context,
76 sha256: *mut [u8; SHA256_DIGEST_SIZE],
77 sha1: *mut [u8; SHA1_DIGEST_SIZE]
78 ) -> Status
79 },
80 context: shim_function! { fn(buffer: *const u8, size: u32, context: *mut Context) -> Status },
81}
82
83impl ShimLock {
84 /// Verify that an EFI application is signed by the certificate
85 /// embedded in shim.
86 ///
87 /// The buffer's size must fit in a `u32`; if that condition is not
88 /// met then a `BAD_BUFFER_SIZE` error will be returned and the shim
89 /// lock protocol will not be called.
90 pub fn verify(&self, buffer: &[u8]) -> Result {
91 let size: u32 = buffer
92 .len()
93 .try_into()
94 .map_err(|_| Error::from(Status::BAD_BUFFER_SIZE))?;
95 (self.verify)(buffer.as_ptr(), size).into()
96 }
97 /// Compute the Authenticode Hash of the provided EFI application.
98 ///
99 /// The buffer's size must fit in a `u32`; if that condition is not
100 /// met then a `BAD_BUFFER_SIZE` error will be returned and the shim
101 /// lock protocol will not be called.
102 pub fn hash(&self, buffer: &[u8], hashes: &mut Hashes) -> Result {
103 let ptr: *const u8 = buffer.as_ptr();
104 let size: u32 = buffer
105 .len()
106 .try_into()
107 .map_err(|_| Error::from(Status::BAD_BUFFER_SIZE))?;
108
109 let mut context = MaybeUninit::<Context>::uninit();
110 Result::from((self.context)(ptr, size, context.as_mut_ptr()))?;
111 (self.hash)(
112 ptr,
113 size,
114 context.as_mut_ptr(),
115 &mut hashes.sha256,
116 &mut hashes.sha1,
117 )
118 .into()
119 }
120}
121