Skip to main content

virtfw_libefi/efivar/
auth.rs

1//! parse authenticated variable updates
2extern crate alloc;
3use alloc::vec;
4use alloc::vec::Vec;
5
6use core::mem::size_of;
7use uguid::Guid;
8use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
9
10use crate::efivar::types::EfiVar;
11use crate::guids::{EfiCertPkcs7, RawGuid};
12use crate::types::EfiTime;
13
14pub const WIN_CERT_TYPE_EFI_GUID: u16 = 0x0EF1;
15
16/// EFI_VARIABLE_AUTHENTICATION_2 in edk2
17#[repr(C)]
18#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)]
19pub struct EfiVarAuth2Header {
20    pub time: EfiTime,
21    pub length: u32,
22    pub revision: u16,
23    pub certtype: u16,
24    pub guid: RawGuid,
25}
26
27pub struct EfiVarAuth2<'a> {
28    header: EfiVarAuth2Header,
29    pkcs7: &'a [u8],
30    data: &'a [u8],
31}
32
33impl<'a> EfiVarAuth2<'a> {
34    pub fn new_from_bytes(auth: &'a [u8]) -> Option<Self> {
35        let (header, _) = EfiVarAuth2Header::read_from_prefix(auth).ok()?;
36        if header.revision != 0x0200 || header.certtype != WIN_CERT_TYPE_EFI_GUID {
37            return None;
38        }
39
40        let guid: Guid = header.guid.into();
41        if guid != EfiCertPkcs7 {
42            return None;
43        };
44
45        let pkcs7_offset = size_of::<EfiVarAuth2Header>();
46        let data_offset = 16 + header.length as usize;
47        let pkcs7 = auth.get(pkcs7_offset..data_offset)?;
48        let data = auth.get(data_offset..)?;
49
50        Some(Self {
51            header,
52            pkcs7,
53            data,
54        })
55    }
56
57    pub fn new_from_data(time: &EfiTime, data: &'a [u8]) -> Self {
58        let length = size_of::<EfiVarAuth2Header>() as u32 - 16;
59        let header = EfiVarAuth2Header {
60            time: *time,
61            length,
62            revision: 0x0200,
63            certtype: WIN_CERT_TYPE_EFI_GUID,
64            guid: EfiCertPkcs7.into(),
65        };
66
67        Self {
68            header,
69            pkcs7: &[],
70            data,
71        }
72    }
73
74    /// Return pkcs7 signature.  Add envelope if needed.
75    pub fn get_pkcs7(&self) -> Vec<u8> {
76        let wrap: [u8; 13] = [
77            0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, 0xa0, 0x82,
78        ];
79
80        if self.pkcs7.is_empty() {
81            return self.pkcs7.to_vec();
82        }
83        if self.pkcs7.get(4..17) == Some(&wrap) {
84            return self.pkcs7.to_vec();
85        }
86
87        let data_len_inner = self.pkcs7.len() as u16;
88        let data_len_outer = data_len_inner + 15;
89
90        // UEFI spec allows pkcs7 signatures being used without the
91        // envelope which identifies them as pkcs7 signatures.  Most
92        // crypto libs will not parse them without the envelope though.
93        // So add it if needed.
94        let mut pkcs7 = vec![0x30, 0x82];
95        pkcs7.extend_from_slice(&data_len_outer.to_be_bytes());
96        pkcs7.extend_from_slice(&wrap);
97        pkcs7.extend_from_slice(&data_len_inner.to_be_bytes());
98        pkcs7.extend_from_slice(self.pkcs7);
99        pkcs7
100    }
101
102    /// Create signed data for pkcs7 signature verification,
103    pub fn get_signed_data(&self, variable: &EfiVar) -> Vec<u8> {
104        let mut sdata: Vec<u8> = Vec::new();
105
106        // Variable Name (without terminating \0)
107        let name_bytes: Vec<u8> = variable
108            .name()
109            .encode_utf16()
110            .flat_map(|u| u.to_le_bytes())
111            .collect();
112        sdata.extend_from_slice(&name_bytes);
113
114        // Variable Namespace Guid
115        sdata.extend_from_slice(&variable.guid().to_bytes());
116
117        // Attributes
118        let attr: u32 = variable.attr().into();
119        sdata.extend_from_slice(&attr.to_le_bytes());
120
121        // TimeStamp
122        sdata.extend_from_slice(self.header.time.as_bytes());
123
124        // Variable Content
125        sdata.extend_from_slice(self.data);
126
127        sdata
128    }
129
130    pub fn header(&self) -> &EfiVarAuth2Header {
131        &self.header
132    }
133
134    pub fn data(&self) -> &[u8] {
135        self.data
136    }
137}
138
139impl From<&EfiVarAuth2<'_>> for Vec<u8> {
140    fn from(authvar: &EfiVarAuth2) -> Vec<u8> {
141        let mut auth = Vec::new();
142        auth.extend_from_slice(authvar.header.as_bytes());
143        auth.extend_from_slice(authvar.pkcs7);
144        auth.extend_from_slice(authvar.data);
145        auth
146    }
147}
148
149/// Unpack efi signature database from authenticated variable update.
150pub fn auth_to_esl(auth: &[u8]) -> Option<(EfiTime, &[u8])> {
151    let authvar = EfiVarAuth2::new_from_bytes(auth)?;
152    Some((authvar.header.time, authvar.data))
153}
154
155/// generate update with timestamp (but without pkcs7 signature)
156pub fn esl_to_auth(time: &EfiTime, esl: &[u8]) -> Vec<u8> {
157    let authvar = &EfiVarAuth2::new_from_data(time, esl);
158    authvar.into()
159}