uefi_nvram_specvars/
signature_list.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Code to parse, manipulate, and emit [`EFI_SIGNATURE_LIST`] structures.
5//!
6//! [`ParseSignatureLists`] is the entrypoint to zero-copy iterate over a list
7//! of serialized `EFI_SIGNATURE_LIST` objects.
8
9use guid::Guid;
10use std::borrow::Cow;
11use std::collections::BTreeSet;
12use thiserror::Error;
13use uefi_specs::uefi::nvram::signature_list::EFI_CERT_SHA256_GUID;
14use uefi_specs::uefi::nvram::signature_list::EFI_CERT_X509_GUID;
15use uefi_specs::uefi::nvram::signature_list::EFI_SIGNATURE_DATA;
16use uefi_specs::uefi::nvram::signature_list::EFI_SIGNATURE_LIST;
17use zerocopy::FromBytes;
18use zerocopy::IntoBytes;
19
20#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
21pub struct X509Data<'a>(pub Cow<'a, [u8]>);
22
23#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
24pub struct Sha256Data<'a>(pub Cow<'a, [u8; 32]>);
25
26// used as part of the `ParseSignatureLists::collect_signature_lists`
27pub enum SignatureDataPayload<'a> {
28    X509(&'a [u8]),
29    Sha256(&'a [u8; 32]),
30}
31
32/// Rust-y representation of a [`EFI_SIGNATURE_DATA`] struct.
33#[derive(Debug, PartialEq, Eq)]
34pub struct SignatureData<T> {
35    pub header: EFI_SIGNATURE_DATA,
36    pub data: T,
37}
38
39impl SignatureData<Sha256Data<'_>> {
40    /// Instantiate a new `SignatureData` with a sha256 digest payload.
41    pub fn new_sha256(owner: Guid, data: Cow<'_, [u8; 32]>) -> SignatureData<Sha256Data<'_>> {
42        SignatureData {
43            header: EFI_SIGNATURE_DATA {
44                signature_owner: owner,
45            },
46            data: Sha256Data(data),
47        }
48    }
49}
50
51impl SignatureData<X509Data<'_>> {
52    /// Instantiate a new `SignatureData` with a x509 cert payload.
53    pub fn new_x509(owner: Guid, data: Cow<'_, [u8]>) -> SignatureData<X509Data<'_>> {
54        SignatureData {
55            header: EFI_SIGNATURE_DATA {
56                signature_owner: owner,
57            },
58            data: X509Data(data),
59        }
60    }
61}
62
63impl SignatureData<Sha256Data<'_>> {
64    fn extend_as_spec_signature_data(&self, v: &mut Vec<u8>) {
65        v.extend(self.header.as_bytes());
66        v.extend(self.data.0.as_bytes());
67    }
68}
69
70impl SignatureData<X509Data<'_>> {
71    fn extend_as_spec_signature_data(&self, v: &mut Vec<u8>) {
72        v.extend(self.header.as_bytes());
73        v.extend(self.data.0.as_bytes());
74    }
75}
76
77/// Rust-y representation of a [`EFI_SIGNATURE_LIST`] struct.
78#[derive(Debug, PartialEq, Eq)]
79pub enum SignatureList<'a> {
80    Sha256(Vec<SignatureData<Sha256Data<'a>>>),
81    // assume that each signature list only contains a single cert
82    //
83    // While the spec _technically_ allows stuffing multiple certs into a single
84    // signature list, the only way that could occur is if the certs have
85    // exactly the same length, which never actually happens in practice.
86    X509(SignatureData<X509Data<'a>>),
87}
88
89impl SignatureList<'_> {
90    /// Serialize the signature list as a `EFI_SIGNATURE_LIST` into a vec
91    pub fn extend_as_spec_signature_list(&self, res: &mut Vec<u8>) {
92        let (signature_type, sig_data_size, multiplier) = match &self {
93            SignatureList::Sha256(sigs) => (EFI_CERT_SHA256_GUID, 32, sigs.len()),
94            SignatureList::X509(sig) => (EFI_CERT_X509_GUID, sig.data.0.len(), 1),
95        };
96
97        let signature_size = size_of::<EFI_SIGNATURE_DATA>() + sig_data_size;
98
99        let header = EFI_SIGNATURE_LIST {
100            signature_type,
101            signature_list_size: (size_of::<EFI_SIGNATURE_LIST>() + (signature_size * multiplier))
102                as u32,
103            signature_header_size: 0, // always zero
104            signature_size: signature_size as u32,
105        };
106
107        res.extend(header.as_bytes());
108        match self {
109            SignatureList::Sha256(sigs) => {
110                for sig in sigs {
111                    sig.extend_as_spec_signature_data(res)
112                }
113            }
114            SignatureList::X509(sig) => sig.extend_as_spec_signature_data(res),
115        }
116    }
117}
118
119/// Errors which may occur during `EFI_SIGNATURE_LIST` parsing.
120#[derive(Debug, Error)]
121pub enum ParseError {
122    #[error("could not read signature list header")]
123    InvalidHeader,
124    #[error("unsupported signature type: {0}")]
125    UnsupportedSignatureType(Guid),
126    #[error("buffer contains less data than specified in EFI_SIGNATURE_LIST header")]
127    TruncatedData,
128
129    #[error("invalid signature_size specified for sha256 (expected 32 + 16, got {0})")]
130    Sha256InvalidSigSize(u32),
131    #[error("unexpected end of buffer while reading sha256 EFI_SIGNATURE_DATA header")]
132    Sha256InvalidHeader,
133    #[error("unexpected end of buffer while reading sha256 EFI_SIGNATURE_DATA data")]
134    Sha256TruncatedData,
135
136    #[error("invalid signature_size specified for x509 (expected {0}, got {1})")]
137    X509InvalidSigSize(u32, u32),
138    #[error("unexpected end of buffer while reading x509 EFI_SIGNATURE_DATA header")]
139    X509InvalidHeader,
140}
141
142/// Iterator over a series of `EFI_SIGNATURE_LIST` structs in a single buffer.
143pub struct ParseSignatureLists<'a> {
144    buf: &'a [u8],
145}
146
147impl<'a> ParseSignatureLists<'a> {
148    /// Instantiate a `ParseSignatureLists` with the given `buf`
149    pub fn new(buf: &'a [u8]) -> ParseSignatureLists<'a> {
150        ParseSignatureLists { buf }
151    }
152
153    /// Parse a list of `EFI_SIGNATURE_LIST`s into a
154    /// [`Vec<SignatureList>`](SignatureList).
155    ///
156    /// `filter` can be used to discard certain signatures (e.g: checking for
157    /// duplicate signatures alongside [`Self::collect_signature_set`])
158    pub fn collect_signature_lists<F>(
159        self,
160        mut filter: F,
161    ) -> Result<Vec<SignatureList<'a>>, ParseError>
162    where
163        F: FnMut(EFI_SIGNATURE_DATA, SignatureDataPayload<'_>) -> bool,
164    {
165        let mut lists = Vec::new();
166
167        for list in self {
168            let list = list?;
169            let list = match list {
170                ParseSignatureList::X509(mut certs) => {
171                    let cert = certs.next().unwrap()?;
172                    assert!(certs.next().is_none());
173
174                    if !filter(cert.header, SignatureDataPayload::X509(&cert.data.0)) {
175                        continue;
176                    }
177
178                    SignatureList::X509(cert)
179                }
180                ParseSignatureList::Sha256(sigs) => {
181                    let mut list = Vec::new();
182                    for sig in sigs {
183                        let sig = sig?;
184
185                        if !filter(sig.header, SignatureDataPayload::Sha256(&sig.data.0)) {
186                            continue;
187                        }
188
189                        list.push(sig);
190                    }
191
192                    // if all the signatures were filtered out, don't include an
193                    // empty signature list
194                    if list.is_empty() {
195                        continue;
196                    }
197
198                    SignatureList::Sha256(list)
199                }
200            };
201            lists.push(list)
202        }
203
204        Ok(lists)
205    }
206
207    /// Parse a list of `EFI_SIGNATURE_LIST`s into a [`BTreeSet`] of signatures.
208    pub fn collect_signature_set(
209        self,
210    ) -> Result<BTreeSet<(EFI_SIGNATURE_DATA, Cow<'a, [u8]>)>, ParseError> {
211        let mut sig_set = BTreeSet::new();
212
213        for list in self {
214            let list = list?;
215            match list {
216                ParseSignatureList::X509(mut certs) => {
217                    let cert = certs.next().unwrap()?;
218                    assert!(certs.next().is_none());
219                    sig_set.insert((cert.header, cert.data.0));
220                }
221                ParseSignatureList::Sha256(sigs) => {
222                    for sig in sigs {
223                        let sig = sig?;
224                        sig_set.insert((
225                            sig.header,
226                            match sig.data.0 {
227                                Cow::Borrowed(a) => Cow::Borrowed(a),
228                                Cow::Owned(a) => Cow::Owned(a.to_vec()),
229                            },
230                        ));
231                    }
232                }
233            };
234        }
235
236        Ok(sig_set)
237    }
238
239    fn next_inner(&mut self) -> Result<Option<ParseSignatureList<'a>>, ParseError> {
240        if self.buf.is_empty() {
241            return Ok(None);
242        }
243
244        let (
245            EFI_SIGNATURE_LIST {
246                signature_type,
247                signature_list_size,
248                signature_header_size: _,
249                signature_size,
250            },
251            buf,
252        ) = EFI_SIGNATURE_LIST::read_from_prefix(self.buf)
253            .map_err(|_| ParseError::InvalidHeader)?; // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
254
255        let expected_data_len = signature_list_size as usize - size_of::<EFI_SIGNATURE_LIST>();
256        if buf.len() < expected_data_len {
257            return Err(ParseError::TruncatedData);
258        }
259        let (data, buf) = buf.split_at(expected_data_len);
260
261        let res = match signature_type {
262            EFI_CERT_SHA256_GUID => {
263                ParseSignatureList::Sha256(ParseSignatureSha256::new(data, signature_size)?)
264            }
265            EFI_CERT_X509_GUID => {
266                ParseSignatureList::X509(ParseSignatureX509::new(data, signature_size)?)
267            }
268            sig => return Err(ParseError::UnsupportedSignatureType(sig)),
269        };
270
271        self.buf = buf;
272
273        Ok(Some(res))
274    }
275}
276
277impl<'a> Iterator for ParseSignatureLists<'a> {
278    type Item = Result<ParseSignatureList<'a>, ParseError>;
279
280    fn next(&mut self) -> Option<Result<ParseSignatureList<'a>, ParseError>> {
281        self.next_inner().transpose()
282    }
283}
284
285/// Parsers for various kinds of `EFI_SIGNATURE_DATA` lists.
286pub enum ParseSignatureList<'a> {
287    X509(ParseSignatureX509<'a>),
288    Sha256(ParseSignatureSha256<'a>),
289}
290
291/// Iterator over `EFI_SIGNATURE_DATA` objects containing x509 certs.
292pub struct ParseSignatureX509<'a> {
293    buf: &'a [u8],
294}
295
296impl<'a> ParseSignatureX509<'a> {
297    fn new(buf: &'a [u8], signature_size: u32) -> Result<ParseSignatureX509<'a>, ParseError> {
298        if buf.len() != signature_size as usize {
299            return Err(ParseError::X509InvalidSigSize(
300                signature_size,
301                buf.len() as u32,
302            ));
303        }
304
305        Ok(ParseSignatureX509 { buf })
306    }
307
308    // assume there is only single cert per signature list
309    //
310    // see comment associated with `SignatureList::X509` for rationale
311    fn next_inner(&mut self) -> Result<Option<SignatureData<X509Data<'a>>>, ParseError> {
312        if self.buf.is_empty() {
313            return Ok(None);
314        }
315
316        let (header, buf) = EFI_SIGNATURE_DATA::read_from_prefix(self.buf)
317            .map_err(|_| ParseError::X509InvalidHeader)?; // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
318
319        let val: Cow<'a, [u8]> = buf.into();
320        let res = SignatureData::new_x509(header.signature_owner, val);
321
322        self.buf = &[];
323        Ok(Some(res))
324    }
325}
326
327impl<'a> Iterator for ParseSignatureX509<'a> {
328    type Item = Result<SignatureData<X509Data<'a>>, ParseError>;
329
330    fn next(&mut self) -> Option<Result<SignatureData<X509Data<'a>>, ParseError>> {
331        self.next_inner().transpose()
332    }
333}
334
335/// Iterator over `EFI_SIGNATURE_DATA` objects containing sha256 digests.
336pub struct ParseSignatureSha256<'a> {
337    buf: &'a [u8],
338}
339
340impl<'a> ParseSignatureSha256<'a> {
341    fn new(buf: &'a [u8], signature_size: u32) -> Result<ParseSignatureSha256<'a>, ParseError> {
342        let expected_signature_size = 32 + size_of::<EFI_SIGNATURE_DATA>();
343
344        if signature_size != expected_signature_size as u32 {
345            return Err(ParseError::Sha256InvalidSigSize(signature_size));
346        }
347
348        // sha256 has consistent signature sizes, so we can perform some early
349        // validation as an optimization
350        if buf.len() % expected_signature_size != 0 {
351            return Err(ParseError::Sha256TruncatedData);
352        }
353
354        Ok(ParseSignatureSha256 { buf })
355    }
356
357    fn next_inner(&mut self) -> Result<Option<SignatureData<Sha256Data<'a>>>, ParseError> {
358        if self.buf.is_empty() {
359            return Ok(None);
360        }
361
362        let (header, buf) =
363            EFI_SIGNATURE_DATA::read_from_prefix(self.buf).expect("buf size validated in `new`"); // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
364
365        let expected_data_len = 32;
366        assert!(buf.len() >= expected_data_len, "validated in new()");
367        let (signature, buf) = buf.split_at(expected_data_len);
368
369        let val: &'a [u8; 32] = signature.try_into().unwrap();
370        let res = SignatureData::new_sha256(header.signature_owner, Cow::Borrowed(val));
371
372        self.buf = buf;
373        Ok(Some(res))
374    }
375}
376
377impl<'a> Iterator for ParseSignatureSha256<'a> {
378    type Item = Result<SignatureData<Sha256Data<'a>>, ParseError>;
379
380    fn next(&mut self) -> Option<Result<SignatureData<Sha256Data<'a>>, ParseError>> {
381        self.next_inner().transpose()
382    }
383}
384
385#[cfg(test)]
386mod test {
387    use super::*;
388
389    const OWNER_1: Guid = Guid {
390        data1: 1,
391        data2: 0,
392        data3: 0,
393        data4: [0, 0, 0, 0, 0, 0, 0, 0],
394    };
395
396    const OWNER_2: Guid = Guid {
397        data1: 2,
398        data2: 0,
399        data3: 0,
400        data4: [0, 0, 0, 0, 0, 0, 0, 0],
401    };
402
403    fn test_data() -> Vec<SignatureList<'static>> {
404        vec![
405            SignatureList::Sha256(vec![
406                SignatureData::new_sha256(OWNER_1, Cow::Owned([0; 32])),
407                SignatureData::new_sha256(OWNER_2, Cow::Owned([1; 32])),
408                SignatureData::new_sha256(OWNER_1, Cow::Owned([2; 32])),
409            ]),
410            SignatureList::X509(SignatureData::new_x509(
411                OWNER_2,
412                b"some cert data"[..].into(),
413            )),
414            SignatureList::Sha256(vec![
415                SignatureData::new_sha256(OWNER_1, Cow::Owned([0; 32])),
416                SignatureData::new_sha256(OWNER_2, Cow::Owned([1; 32])),
417            ]),
418            SignatureList::X509(SignatureData::new_x509(
419                OWNER_1,
420                b"more cert data"[..].into(),
421            )),
422        ]
423    }
424
425    fn test_data_no_owner_1() -> Vec<SignatureList<'static>> {
426        vec![
427            SignatureList::Sha256(vec![SignatureData::new_sha256(
428                OWNER_2,
429                Cow::Owned([1; 32]),
430            )]),
431            SignatureList::X509(SignatureData::new_x509(
432                OWNER_2,
433                b"some cert data"[..].into(),
434            )),
435            SignatureList::Sha256(vec![SignatureData::new_sha256(
436                OWNER_2,
437                Cow::Owned([1; 32]),
438            )]),
439        ]
440    }
441
442    fn dump_to_vec(lists: Vec<SignatureList<'_>>) -> Vec<u8> {
443        let mut buf = Vec::new();
444        for l in &lists {
445            l.extend_as_spec_signature_list(&mut buf)
446        }
447        buf
448    }
449
450    #[test]
451    fn roundtrip() {
452        let lists = test_data();
453
454        // dump the list of signature lists into a buffer
455        let mut buf = Vec::new();
456        for l in &lists {
457            l.extend_as_spec_signature_list(&mut buf)
458        }
459
460        // reconstruct list of signature lists using the parser framework
461        let new_lists = ParseSignatureLists::new(&buf)
462            .collect_signature_lists(|_, _| true)
463            .unwrap();
464
465        assert_eq!(lists, new_lists);
466    }
467
468    #[test]
469    fn filter() {
470        let lists = test_data();
471        let lists_no_owner_1 = test_data_no_owner_1();
472
473        let lists_buf = dump_to_vec(lists);
474        let new_lists_no_owner_1 = ParseSignatureLists::new(&lists_buf)
475            .collect_signature_lists(|header, _| header.signature_owner != OWNER_1)
476            .unwrap();
477
478        assert_eq!(lists_no_owner_1, new_lists_no_owner_1);
479    }
480}