//! Shim lock protocol. #![cfg(any( target_arch = "x86", target_arch = "x86_64", target_arch = "arm", target_arch = "aarch64" ))] use crate::proto::unsafe_protocol; use crate::result::Error; use crate::{Result, Status, StatusExt}; use core::ffi::c_void; use core::mem::MaybeUninit; // The `PE_COFF_LOADER_IMAGE_CONTEXT` type. None of our methods need to inspect // the fields of this struct, we just need to make sure it is the right size. #[repr(C)] struct Context { _image_address: u64, _image_size: u64, _entry_point: u64, _size_of_headers: usize, _image_type: u16, _number_of_sections: u16, _section_alignment: u32, _first_section: *const c_void, _reloc_dir: *const c_void, _sec_dir: *const c_void, _number_of_rva_and_sizes: u64, _pe_hdr: *const c_void, } const SHA1_DIGEST_SIZE: usize = 20; const SHA256_DIGEST_SIZE: usize = 32; /// Authenticode hashes of some UEFI application. #[derive(Debug)] pub struct Hashes { /// SHA256 Authenticode Digest pub sha256: [u8; SHA256_DIGEST_SIZE], /// SHA1 Authenticode Digest pub sha1: [u8; SHA1_DIGEST_SIZE], } // These macros set the correct calling convention for the Shim protocol methods. #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] macro_rules! shim_function { (fn $args:tt -> $return_type:ty) => (extern "sysv64" fn $args -> $return_type) } #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] macro_rules! shim_function { (fn $args:tt -> $return_type:ty) => (extern "C" fn $args -> $return_type) } /// The Shim lock protocol. /// /// This protocol is not part of the UEFI specification, but is /// installed by the [Shim bootloader](https://github.com/rhboot/shim) /// which is commonly used by Linux distributions to support UEFI /// Secure Boot. Shim is built with an embedded certificate that is /// used to validate another EFI application before running it. That /// application may itself be a bootloader that needs to validate /// another EFI application before running it, and the shim lock /// protocol exists to support that. #[derive(Debug)] #[repr(C)] #[unsafe_protocol("605dab50-e046-4300-abb6-3dd810dd8b23")] pub struct ShimLock { verify: shim_function! { fn(buffer: *const u8, size: u32) -> Status }, hash: shim_function! { fn( buffer: *const u8, size: u32, context: *mut Context, sha256: *mut [u8; SHA256_DIGEST_SIZE], sha1: *mut [u8; SHA1_DIGEST_SIZE] ) -> Status }, context: shim_function! { fn(buffer: *const u8, size: u32, context: *mut Context) -> Status }, } impl ShimLock { /// Verify that an EFI application is signed by the certificate /// embedded in shim. /// /// The buffer's size must fit in a `u32`; if that condition is not /// met then a `BAD_BUFFER_SIZE` error will be returned and the shim /// lock protocol will not be called. pub fn verify(&self, buffer: &[u8]) -> Result { let size: u32 = buffer .len() .try_into() .map_err(|_| Error::from(Status::BAD_BUFFER_SIZE))?; (self.verify)(buffer.as_ptr(), size).to_result() } /// Compute the Authenticode Hash of the provided EFI application. /// /// The buffer's size must fit in a `u32`; if that condition is not /// met then a `BAD_BUFFER_SIZE` error will be returned and the shim /// lock protocol will not be called. pub fn hash(&self, buffer: &[u8], hashes: &mut Hashes) -> Result { let ptr: *const u8 = buffer.as_ptr(); let size: u32 = buffer .len() .try_into() .map_err(|_| Error::from(Status::BAD_BUFFER_SIZE))?; let mut context = MaybeUninit::::uninit(); (self.context)(ptr, size, context.as_mut_ptr()).to_result()?; (self.hash)( ptr, size, context.as_mut_ptr(), &mut hashes.sha256, &mut hashes.sha1, ) .to_result() } }