Skip to main content

firmware_uefi_custom_vars/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Types and methods for defining and layering sets of UEFI nvram variables.
5
6#![expect(missing_docs)]
7#![forbid(unsafe_code)]
8
9use guid::Guid;
10use mesh_protobuf::Protobuf;
11use thiserror::Error;
12use uefi_specs::uefi::nvram::vars::EFI_GLOBAL_VARIABLE;
13
14pub mod delta;
15
16/// A complete base template deferred as raw JSON.
17#[derive(Debug, Clone, Protobuf)]
18#[mesh(transparent)]
19pub struct BaseTemplateJson(Vec<u8>);
20
21impl BaseTemplateJson {
22    /// Return the JSON bytes.
23    pub fn as_bytes(&self) -> &[u8] {
24        &self.0
25    }
26}
27
28impl From<Vec<u8>> for BaseTemplateJson {
29    fn from(json: Vec<u8>) -> Self {
30        Self(json)
31    }
32}
33
34/// A complete base template deferred as raw JSON.
35///
36/// This envelope leaves room for adding reliably sourced template metadata
37/// without changing the UEFI configuration interface.
38#[derive(Debug, Clone, Protobuf)]
39pub struct BaseTemplate {
40    pub json: BaseTemplateJson,
41}
42
43/// A customer-provided UEFI variable delta deferred as raw JSON.
44#[derive(Debug, Clone, Protobuf)]
45#[mesh(transparent)]
46pub struct UefiVarsDeltaJson(Vec<u8>);
47
48impl UefiVarsDeltaJson {
49    /// Return the JSON bytes.
50    pub fn as_bytes(&self) -> &[u8] {
51        &self.0
52    }
53}
54
55impl From<Vec<u8>> for UefiVarsDeltaJson {
56    fn from(json: Vec<u8>) -> Self {
57        Self(json)
58    }
59}
60
61/// Collection of UEFI nvram variables that will be injected on first boot.
62#[derive(Debug, Default, Clone, Protobuf)]
63pub struct UefiVars {
64    /// Secure Boot signature vars
65    pub signatures: Option<Signatures>,
66    /// UEFI vars that are not Secure Boot signature vars
67    pub non_signature_vars: Vec<(String, UefiVar)>,
68}
69
70/// A complete set of variables supplied by a built-in template.
71#[derive(Debug, Default, Clone, Protobuf)]
72#[mesh(transparent)]
73pub struct BaseTemplateVars(UefiVars);
74
75/// A complete set of variables ready for NVRAM injection.
76#[derive(Debug)]
77pub struct FinalVars(UefiVars);
78
79impl From<UefiVars> for BaseTemplateVars {
80    fn from(vars: UefiVars) -> Self {
81        Self(vars)
82    }
83}
84
85impl BaseTemplateVars {
86    /// Return the Secure Boot signatures supplied by this template.
87    pub fn signatures(&self) -> Option<&Signatures> {
88        self.0.signatures.as_ref()
89    }
90}
91
92impl FinalVars {
93    /// Resolve an optional base template and custom delta into final variables.
94    pub fn resolve(
95        base_template: Option<BaseTemplateVars>,
96        custom_template_delta: Option<delta::UefiVarsDelta>,
97    ) -> Result<Self, ApplyDeltaError> {
98        let base_vars = base_template.map_or_else(UefiVars::default, |base| base.0);
99        let final_vars = match custom_template_delta {
100            Some(delta) => base_vars.apply_delta(delta)?,
101            None => base_vars,
102        };
103        Ok(Self(final_vars))
104    }
105
106    /// Return the finalized UEFI variables.
107    pub fn into_uefi_vars(self) -> UefiVars {
108        self.0
109    }
110}
111
112#[derive(Debug, Clone, Protobuf)]
113pub struct Signatures {
114    pub pk: Signature,
115    pub kek: Vec<Signature>,
116    pub db: Vec<Signature>,
117    pub dbx: Vec<Signature>,
118    pub moklist: Vec<Signature>,
119    pub moklistx: Vec<Signature>,
120}
121
122#[derive(Debug, Clone, Protobuf)]
123pub enum Signature {
124    X509(Vec<X509Cert>),
125    Sha256(Vec<Sha256Digest>),
126}
127
128#[derive(Debug, Clone, Protobuf)]
129pub struct UefiVar {
130    pub guid: Guid,
131    pub attr: u32,
132    pub value: Vec<u8>,
133}
134
135#[derive(Clone, Protobuf)]
136pub struct X509Cert(pub Vec<u8>);
137
138impl std::fmt::Debug for X509Cert {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        f.debug_tuple("X509Cert").field(&"[..]").finish()
141    }
142}
143
144#[derive(Clone, Protobuf)]
145pub struct Sha256Digest(pub [u8; 32]);
146
147impl std::fmt::Debug for Sha256Digest {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        f.debug_tuple("Sha256Digest")
150            .field(&self.0.map(|b| format!("{:02x?}", b)).join(""))
151            .finish()
152    }
153}
154
155#[derive(Debug, Error)]
156pub enum ApplyDeltaError {
157    #[error("cannot Append if no base signatures are provided")]
158    AppendWithoutBase,
159    #[error("cannot use \"Default\" variable type if no base signatures are provided")]
160    DefaultWithoutBase,
161    #[error("cannot set restricted variable: {name}:{guid}")]
162    RestrictedUefiVar { name: String, guid: Guid },
163}
164
165impl UefiVars {
166    /// Create a new, blank set of UEFI variables.
167    pub fn new() -> UefiVars {
168        UefiVars::default()
169    }
170
171    /// Apply a delta on top of an existing set of UEFI variables.
172    pub fn apply_delta(self, delta: delta::UefiVarsDelta) -> Result<UefiVars, ApplyDeltaError> {
173        use delta::SignatureDelta;
174        use delta::SignatureDeltaVec;
175        use delta::SignaturesAppend;
176        use delta::SignaturesDelta;
177        use delta::SignaturesReplace;
178
179        let signatures = match (self.signatures, delta.signatures) {
180            (None, SignaturesDelta::Append(..)) => {
181                return Err(ApplyDeltaError::AppendWithoutBase);
182            }
183            (
184                None,
185                SignaturesDelta::Replace(SignaturesReplace {
186                    pk,
187                    kek,
188                    db,
189                    dbx,
190                    moklist,
191                    moklistx,
192                }),
193            ) => {
194                fn deny_default(sig_delta: SignatureDelta) -> Result<Signature, ApplyDeltaError> {
195                    match sig_delta {
196                        SignatureDelta::Sig(sig) => Ok(sig),
197                        SignatureDelta::Default => Err(ApplyDeltaError::DefaultWithoutBase),
198                    }
199                }
200
201                fn deny_default_vec(
202                    sig_delta_vec: SignatureDeltaVec,
203                ) -> Result<Vec<Signature>, ApplyDeltaError> {
204                    match sig_delta_vec {
205                        SignatureDeltaVec::Sigs(sig) => Ok(sig),
206                        SignatureDeltaVec::Default => Err(ApplyDeltaError::DefaultWithoutBase),
207                    }
208                }
209
210                Signatures {
211                    pk: deny_default(pk)?,
212                    kek: deny_default_vec(kek)?,
213                    db: deny_default_vec(db)?,
214                    dbx: deny_default_vec(dbx)?,
215                    moklist: moklist
216                        .map(deny_default_vec)
217                        .transpose()?
218                        .unwrap_or_default(),
219                    moklistx: moklistx
220                        .map(deny_default_vec)
221                        .transpose()?
222                        .unwrap_or_default(),
223                }
224            }
225            (
226                Some(Signatures {
227                    pk,
228                    mut kek,
229                    mut db,
230                    mut dbx,
231                    mut moklist,
232                    mut moklistx,
233                }),
234                sig_delta,
235            ) => match sig_delta {
236                SignaturesDelta::Append(SignaturesAppend {
237                    kek: append_kek,
238                    db: append_db,
239                    dbx: append_dbx,
240                    moklist: append_moklist,
241                    moklistx: append_moklistx,
242                }) => {
243                    if let Some(append_kek) = append_kek {
244                        kek.extend(append_kek);
245                    }
246
247                    if let Some(append_db) = append_db {
248                        db.extend(append_db);
249                    }
250
251                    if let Some(append_dbx) = append_dbx {
252                        dbx.extend(append_dbx);
253                    }
254
255                    if let Some(append_moklist) = append_moklist {
256                        moklist.extend(append_moklist)
257                    }
258
259                    if let Some(append_moklistx) = append_moklistx {
260                        moklistx.extend(append_moklistx)
261                    }
262
263                    Signatures {
264                        pk,
265                        kek,
266                        db,
267                        dbx,
268                        moklist,
269                        moklistx,
270                    }
271                }
272                SignaturesDelta::Replace(SignaturesReplace {
273                    pk: replace_pk,
274                    kek: replace_kek,
275                    db: replace_db,
276                    dbx: replace_dbx,
277                    moklist: replace_moklist,
278                    moklistx: replace_moklistx,
279                }) => {
280                    fn replace_default(sig_delta: SignatureDelta, base: Signature) -> Signature {
281                        match sig_delta {
282                            SignatureDelta::Sig(sig) => sig,
283                            SignatureDelta::Default => base,
284                        }
285                    }
286
287                    fn replace_default_vec(
288                        sig_delta_vec: SignatureDeltaVec,
289                        base: Vec<Signature>,
290                    ) -> Vec<Signature> {
291                        match sig_delta_vec {
292                            SignatureDeltaVec::Sigs(sigs) => sigs,
293                            SignatureDeltaVec::Default => base,
294                        }
295                    }
296
297                    fn replace_default_option_vec(
298                        sig_delta_vec: Option<SignatureDeltaVec>,
299                        base: Vec<Signature>,
300                    ) -> Vec<Signature> {
301                        match sig_delta_vec {
302                            Some(SignatureDeltaVec::Sigs(sigs)) => sigs,
303                            Some(SignatureDeltaVec::Default) | None => base,
304                        }
305                    }
306
307                    Signatures {
308                        pk: replace_default(replace_pk, pk),
309                        kek: replace_default_vec(replace_kek, kek),
310                        db: replace_default_vec(replace_db, db),
311                        dbx: replace_default_vec(replace_dbx, dbx),
312                        moklist: replace_default_option_vec(replace_moklist, moklist),
313                        moklistx: replace_default_option_vec(replace_moklistx, moklistx),
314                    }
315                }
316            },
317        };
318
319        let mut non_signature_vars = self.non_signature_vars;
320
321        // Replace overwritten vars, append new vars
322        'outer: for (new_key, new_val) in delta.non_signature_vars {
323            if new_key == "dbDefault" && new_val.guid == EFI_GLOBAL_VARIABLE {
324                return Err(ApplyDeltaError::RestrictedUefiVar {
325                    name: new_key,
326                    guid: new_val.guid,
327                });
328            }
329
330            for (old_key, old_val) in &mut non_signature_vars {
331                if *old_key == new_key {
332                    *old_val = new_val;
333                    continue 'outer;
334                }
335            }
336            non_signature_vars.push((new_key, new_val));
337        }
338
339        Ok(UefiVars {
340            signatures: Some(signatures),
341            non_signature_vars,
342        })
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use crate::delta::SignatureDelta;
350    use crate::delta::SignatureDeltaVec;
351    use crate::delta::SignaturesAppend;
352    use crate::delta::SignaturesDelta;
353    use crate::delta::SignaturesReplace;
354    use crate::delta::UefiVarsDelta;
355
356    #[test]
357    fn no_templates_resolve_to_empty_final_vars() {
358        let vars = FinalVars::resolve(None, None).unwrap().into_uefi_vars();
359
360        assert!(vars.signatures.is_none());
361        assert!(vars.non_signature_vars.is_empty());
362    }
363
364    #[test]
365    fn append_without_base_fails() {
366        let delta = UefiVarsDelta {
367            signatures: SignaturesDelta::Append(SignaturesAppend {
368                kek: None,
369                db: None,
370                dbx: None,
371                moklist: None,
372                moklistx: None,
373            }),
374            non_signature_vars: Vec::new(),
375        };
376
377        assert!(matches!(
378            FinalVars::resolve(None, Some(delta)),
379            Err(ApplyDeltaError::AppendWithoutBase)
380        ));
381    }
382
383    #[test]
384    fn default_without_base_fails() {
385        let delta = UefiVarsDelta {
386            signatures: SignaturesDelta::Replace(SignaturesReplace {
387                pk: SignatureDelta::Default,
388                kek: SignatureDeltaVec::Default,
389                db: SignatureDeltaVec::Default,
390                dbx: SignatureDeltaVec::Default,
391                moklist: None,
392                moklistx: None,
393            }),
394            non_signature_vars: Vec::new(),
395        };
396
397        assert!(matches!(
398            FinalVars::resolve(None, Some(delta)),
399            Err(ApplyDeltaError::DefaultWithoutBase)
400        ));
401    }
402
403    #[test]
404    fn restricted_uefi_var_fails() {
405        let base_template = UefiVars {
406            signatures: Some(Signatures {
407                pk: Signature::X509(Vec::new()),
408                kek: Vec::new(),
409                db: Vec::new(),
410                dbx: Vec::new(),
411                moklist: Vec::new(),
412                moklistx: Vec::new(),
413            }),
414            non_signature_vars: Vec::new(),
415        }
416        .into();
417        let delta = UefiVarsDelta {
418            signatures: SignaturesDelta::Append(SignaturesAppend {
419                kek: None,
420                db: None,
421                dbx: None,
422                moklist: None,
423                moklistx: None,
424            }),
425            non_signature_vars: vec![(
426                "dbDefault".into(),
427                UefiVar {
428                    guid: EFI_GLOBAL_VARIABLE,
429                    attr: 0,
430                    value: Vec::new(),
431                },
432            )],
433        };
434
435        assert!(matches!(
436            FinalVars::resolve(Some(base_template), Some(delta)),
437            Err(ApplyDeltaError::RestrictedUefiVar { name, guid })
438                if name == "dbDefault" && guid == EFI_GLOBAL_VARIABLE
439        ));
440    }
441}