Skip to main content

firmware_uefi/service/nvram/spec_services/
auth_var_crypto.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Cryptographic operations to validate authenticated variables
5
6use super::ParsedAuthVar;
7use thiserror::Error;
8use uefi_nvram_specvars::signature_list;
9use zerocopy::IntoBytes;
10
11/// Errors that occur due to various formatting issues in the crypto objects.
12#[derive(Debug, Error)]
13pub enum FormatError {
14    #[error("parsing signature list from auth_var_data")]
15    SignatureList(#[source] signature_list::ParseError),
16    #[error("parsing x509 cert from signature list")]
17    SignatureListX509(#[source] crypto::x509::X509Error),
18    #[error("parsing auth var's pkcs7_data as pkcs#7 DER")]
19    AuthVarPkcs7Der(#[source] crypto::pkcs7::Pkcs7Error),
20    #[error("could not reconstruct signedData header for auth var's pkcs#7 data: {0:?}")]
21    AuthVarPkcs7DerHeader(der::Error),
22    #[error("PKCS#7 verification")]
23    AuthVarPkcs7Verify(#[source] crypto::pkcs7::Pkcs7VerifyError),
24}
25
26impl FormatError {
27    /// Whether the error is due to malformed data in the signature lists
28    pub fn key_var_error(&self) -> bool {
29        match self {
30            FormatError::SignatureList(_) | FormatError::SignatureListX509(_) => true,
31            FormatError::AuthVarPkcs7Der(_)
32            | FormatError::AuthVarPkcs7DerHeader(_)
33            | FormatError::AuthVarPkcs7Verify(_) => false,
34        }
35    }
36}
37
38/// Authenticate the variable against the certs in the provided signature_lists,
39/// returning `true` if the auth was successful.
40pub fn authenticate_variable(
41    signature_lists: &[u8],
42    var: ParsedAuthVar<'_>,
43) -> Result<bool, FormatError> {
44    let ParsedAuthVar {
45        name,
46        vendor,
47        attr,
48        timestamp,
49        pkcs7_data,
50        var_data,
51    } = var;
52
53    // stage 1 - parse the pkcs7_data into a PKCS#7 object
54    let var_pkcs7 = match crypto::pkcs7::Pkcs7SignedData::from_der(pkcs7_data) {
55        Ok(pkcs7) => pkcs7,
56        Err(_) => {
57            // From UEFI spec 8.2.2 Using the EFI_VARIABLE_AUTHENTICATION_2 descriptor
58            //
59            // > Construct a DER-encoded SignedData structure per PKCS#7 version 1.5
60            // > (RFC 2315), which shall be supported **both with and without**
61            // > a DER-encoded ContentInfo structure per PKCS#7 version 1.5 [..]
62            //
63            // (emphasis mine)
64            //
65            // Yes, you read that right.
66            //
67            // The UEFI spec explicitly allows _malformed_ PKCS#7 payloads that
68            // are missing a ContentInfo header. _sigh_
69
70            // stage 1.5 - if parsing fails the first time, construct an appropriate
71            // ContentInfo header and retry parsing the payload as a PKCS#7 DER
72            let buf = pkcs7_details::encapsulate_in_content_info(pkcs7_data)
73                .map_err(FormatError::AuthVarPkcs7DerHeader)?;
74            match crypto::pkcs7::Pkcs7SignedData::from_der(&buf) {
75                Ok(pkcs7) => pkcs7,
76                // ...but if that also fails, there's nothing else we can do
77                Err(e) => return Err(FormatError::AuthVarPkcs7Der(e)),
78            }
79        }
80    };
81
82    // stage 2 - extract all the x509 certs from the signature list(s)
83    let mut trusted_certs = Vec::new();
84
85    let lists = signature_list::ParseSignatureLists::new(signature_lists);
86    for list in lists {
87        let list = list.map_err(FormatError::SignatureList)?;
88        // we only care about x509 certs in the signature lists
89        if let signature_list::ParseSignatureList::X509(certs) = list {
90            for cert in certs {
91                let cert = cert.map_err(FormatError::SignatureList)?;
92                trusted_certs.push(
93                    crypto::x509::X509Certificate::from_der(&cert.data.0)
94                        .map_err(FormatError::SignatureListX509)?,
95                );
96            }
97        }
98    }
99
100    // stage 3 - construct the "data to verify" buffer
101    //
102    // See bullet point 2. in UEFI spec 8.2.2
103    let mut verify_buf = Vec::new();
104    verify_buf.extend(name.as_bytes_without_nul());
105    verify_buf.extend(vendor.as_bytes());
106    verify_buf.extend(attr.as_bytes());
107    verify_buf.extend(timestamp.as_bytes());
108    verify_buf.extend(var_data);
109
110    // stage 4 - verify the signed data using trusted certs from EFI signature lists
111    var_pkcs7
112        .verify_uefi(&trusted_certs, &verify_buf)
113        .map_err(FormatError::AuthVarPkcs7Verify)
114}
115
116mod pkcs7_details {
117    use der::Encode;
118    use der::Sequence;
119    use der::TagMode;
120    use der::TagNumber;
121    use der::asn1::AnyRef;
122    use der::asn1::ContextSpecific;
123    use der::asn1::ObjectIdentifier;
124
125    #[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence)]
126    struct ContentInfo<'a> {
127        pub content_type: ObjectIdentifier,
128        pub content: ContextSpecific<AnyRef<'a>>,
129    }
130
131    /// Construct a ASN.1 `ContentInfo` header with `ContentType = signedData`
132    /// as specified by the PKCS#7 RFC2315.
133    ///
134    /// See https://datatracker.ietf.org/doc/html/rfc2315#section-7
135    ///
136    /// ```text
137    /// ContentInfo ::= SEQUENCE {
138    ///   contentType ContentType,
139    ///   content
140    ///     [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
141    /// ```
142    pub fn encapsulate_in_content_info(content: &[u8]) -> der::Result<Vec<u8>> {
143        // constant pulled from https://datatracker.ietf.org/doc/html/rfc2315#section-14
144        const PKCS_7_SIGNED_DATA_OID: ObjectIdentifier =
145            ObjectIdentifier::new_unwrap("1.2.840.113549.1.7.2");
146
147        let content_info = ContentInfo {
148            content_type: PKCS_7_SIGNED_DATA_OID,
149            content: ContextSpecific {
150                tag_number: TagNumber(0),
151                value: AnyRef::try_from(content)?,
152                tag_mode: TagMode::Explicit,
153            },
154        };
155
156        Encode::to_der(&content_info)
157    }
158}