1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! Cryptographic operations to validate authenticated variables
#![cfg(feature = "auth-var-verify-crypto")]
use super::ParsedAuthVar;
use thiserror::Error;
use uefi_nvram_specvars::signature_list;
use zerocopy::AsBytes;
/// Errors that occur due to various formatting issues in the crypto objects.
#[derive(Debug, Error)]
pub enum FormatError {
#[error("parsing signature list from auth_var_data")]
SignatureList(#[from] signature_list::ParseError),
#[error("decoding x509 cert from signature list")]
SignatureListX509(#[source] openssl::error::ErrorStack),
#[error("parsing auth var's pkcs7_data as pkcs#7 DER")]
AuthVarPkcs7Der(#[source] openssl::error::ErrorStack),
#[error("could not reconstruct signedData header for auth var's pkcs#7 data: {0}")]
AuthVarPkcs7DerHeader(der::Error),
}
impl FormatError {
/// Whether the error is due to malformed data in the signature lists
pub fn key_var_error(&self) -> bool {
match self {
FormatError::SignatureList(_) | FormatError::SignatureListX509(_) => true,
FormatError::AuthVarPkcs7Der(_) | FormatError::AuthVarPkcs7DerHeader(_) => false,
}
}
}
/// Authenticate the variable against the certs in the provided signature_lists,
/// returning `true` if the auth was successful.
pub fn authenticate_variable(
signature_lists: &[u8],
var: ParsedAuthVar<'_>,
) -> Result<bool, FormatError> {
let ParsedAuthVar {
name,
vendor,
attr,
timestamp,
pkcs7_data,
var_data,
} = var;
// stage 1 - parse the pkcs7_data into an openssl Pkcs7 object
let var_pkcs7 = match openssl::pkcs7::Pkcs7::from_der(pkcs7_data) {
Ok(pkcs7) => pkcs7,
Err(_) => {
// From UEFI spec 8.2.2 Using the EFI_VARIABLE_AUTHENTICATION_2 descriptor
//
// > Construct a DER-encoded SignedData structure per PKCS#7 version 1.5
// > (RFC 2315), which shall be supported **both with and without**
// > a DER-encoded ContentInfo structure per PKCS#7 version 1.5 [..]
//
// (emphasis mine)
//
// Yes, you read that right.
//
// The UEFI spec explicitly allows _malformed_ PKCS#7 payloads that
// are missing a ContentInfo header. _sigh_
// stage 1.5 - if parsing fails the first time, construct an appropriate
// ContentInfo header and retry parsing the payload as a PKCS#7 DER
let buf = pkcs7_details::encapsulate_in_content_info(pkcs7_data)
.map_err(FormatError::AuthVarPkcs7DerHeader)?;
match openssl::pkcs7::Pkcs7::from_der(&buf) {
Ok(pkcs7) => pkcs7,
// ...but if that also fails, there's nothing else we can do
Err(e) => return Err(FormatError::AuthVarPkcs7Der(e)),
}
}
};
// stage 2 - extract and parse all the x509 certs from the signature list(s)
// into openssl x509 objects
let certs = {
let mut parsed_certs = Vec::new();
let lists = signature_list::ParseSignatureLists::new(signature_lists);
for list in lists {
let list = list?;
// we only care about x509 certs in the signature lists
if let signature_list::ParseSignatureList::X509(certs) = list {
for cert in certs {
let cert = cert?;
let cert = openssl::x509::X509::from_der(&cert.data.0)
.map_err(FormatError::SignatureListX509)?;
parsed_certs.push(cert);
}
}
}
parsed_certs
};
// stage 3 - construct the "data to verify" buffer
//
// See bullet point 2. in UEFI spec 8.2.2
let mut verify_buf = Vec::new();
verify_buf.extend(name.as_bytes_without_nul());
verify_buf.extend(vendor.as_bytes());
verify_buf.extend(attr.as_bytes());
verify_buf.extend(timestamp.as_bytes());
verify_buf.extend(var_data);
// stage 4 - package those raw certs into an openssl X509Store object
let store = {
let mut store = openssl::x509::store::X509StoreBuilder::new().unwrap();
// unlike the HCL / worker process implementations, which manually
// compare certs to perform the verification, we leverage openssl's
// built-in functionality to do this for us.
//
// first, we throw all our trusted certs into a X509Store:
for cert in certs {
store.add_cert(cert).unwrap();
}
// then, we set some extra flags to work around the particular
// idiosyncrasies of how these certs are constructed...
// PARTIAL_CHAIN rationale: the certs in the EFI_SIGNATURE_LIST are not
// root certs, and we don't have a full cert chain available. Instead,
// we want to terminate the chain verification at whatever certs are
// present from the EFI_SIGNATURE_LISTs.
//
// NO_CHECK_TIME rationale: when testing this feature, we noticed that
// the UEFI signing key expired a long time ago. The existing
// implementations didn't care about this, and allowed the verification
// to succeed regardless.
let store_flags = openssl::x509::verify::X509VerifyFlags::PARTIAL_CHAIN
| openssl::x509::verify::X509VerifyFlags::NO_CHECK_TIME;
store.set_flags(store_flags).unwrap();
// X509Purpose::Any rationale: openssl expects the trusted certs to have
// certain capabilities that ours do not. Omitting this call will result
// in the verify operation failing with "Verify error:unsupported
// certificate purpose"
store
.set_purpose(openssl::x509::X509PurposeId::ANY)
.unwrap();
store.build()
};
// stage 5 - actually perform the verification
match var_pkcs7.verify(
// `certs` should be nullable (i.e: represented using an optional).
// This is an oversight in the openssl-rs API, so instead, we use an
// empty stack...
&openssl::stack::Stack::new().unwrap(),
&store,
Some(&verify_buf),
None,
openssl::pkcs7::Pkcs7Flags::empty(),
) {
Ok(()) => Ok(true),
Err(e) => {
tracing::trace!(
error = &e as &dyn std::error::Error,
"could not verify auth var"
);
Ok(false)
}
}
}
mod pkcs7_details {
use der::asn1::AnyRef;
use der::asn1::ContextSpecific;
use der::asn1::ObjectIdentifier;
use der::Encode;
use der::Sequence;
use der::TagMode;
use der::TagNumber;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence)]
struct ContentInfo<'a> {
pub content_type: ObjectIdentifier,
pub content: ContextSpecific<AnyRef<'a>>,
}
/// Construct a ASN.1 `ContentInfo` header with `ContentType = signedData`
/// as specified by the PKCS#7 RFC2315.
///
/// See https://datatracker.ietf.org/doc/html/rfc2315#section-7
///
/// ```text
/// ContentInfo ::= SEQUENCE {
/// contentType ContentType,
/// content
/// [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
/// ```
pub fn encapsulate_in_content_info(content: &[u8]) -> der::Result<Vec<u8>> {
// constant pulled from https://datatracker.ietf.org/doc/html/rfc2315#section-14
const PKCS_7_SIGNED_DATA_OID: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.2.840.113549.1.7.2");
let content_info = ContentInfo {
content_type: PKCS_7_SIGNED_DATA_OID,
content: ContextSpecific {
tag_number: TagNumber::new(0),
value: AnyRef::try_from(content)?,
tag_mode: TagMode::Explicit,
},
};
Encode::to_der(&content_info)
}
}