blob: 158590bb8edaebcd21a853c9bd669de45a21eb49 [file] [log] [blame]
//! 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::<Context>::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()
}
}