blob: c58ead1363d4c97e068f8a6a4081428d75e86ab5 [file] [log] [blame]
Alan Stokes4db76eb2023-04-26 14:28:15 +01001// Copyright 2023, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Code to inspect/manipulate the BCC (DICE Chain) we receive from our loader (the hypervisor).
16
17// TODO(b/279910232): Unify this, somehow, with the similar but different code in hwtrust.
18
19use alloc::vec::Vec;
20use ciborium::value::Value;
21use core::fmt;
22use diced_open_dice::DiceMode;
23use log::trace;
24
25type Result<T> = core::result::Result<T, BccError>;
26
27pub enum BccError {
28 CborDecodeError(ciborium::de::Error<ciborium_io::EndOfFile>),
29 ExtraneousBytes,
30 MalformedBcc(&'static str),
31 MissingBcc,
32}
33
34impl fmt::Display for BccError {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self {
37 Self::CborDecodeError(e) => write!(f, "Error parsing BCC CBOR: {e:?}"),
38 Self::ExtraneousBytes => write!(f, "Unexpected trailing data in BCC"),
39 Self::MalformedBcc(s) => {
40 write!(f, "BCC does not have the expected CBOR structure: {s}")
41 }
42 Self::MissingBcc => write!(f, "Missing BCC"),
43 }
44 }
45}
46
47/// Represents a (partially) decoded BCC DICE chain.
48pub struct Bcc {
49 is_debug_mode: bool,
50}
51
52impl Bcc {
53 /// Returns whether any node in the received DICE chain is marked as debug (and hence is not
54 /// secure).
55 pub fn new(received_bcc: Option<&[u8]>) -> Result<Bcc> {
56 let received_bcc = received_bcc.unwrap_or(&[]);
57 if received_bcc.is_empty() {
58 return Err(BccError::MissingBcc);
59 }
60
61 // We don't attempt to fully validate the BCC (e.g. we don't check the signatures) - we
62 // have to trust our loader. But if it's invalid CBOR or otherwise clearly ill-formed,
63 // something is very wrong, so we fail.
64 let bcc_cbor = value_from_bytes(received_bcc)?;
65
66 // Bcc = [
67 // PubKeyEd25519 / PubKeyECDSA256, // DK_pub
68 // + BccEntry, // Root -> leaf (KM_pub)
69 // ]
70 let bcc = match bcc_cbor {
71 Value::Array(v) if v.len() >= 2 => v,
72 _ => return Err(BccError::MalformedBcc("Invalid top level value")),
73 };
74 // Decode all the entries to make sure they are well-formed.
75 let entries: Vec<_> = bcc.into_iter().skip(1).map(BccEntry::new).collect();
76
77 let is_debug_mode = is_any_entry_debug_mode(entries.as_slice())?;
78 Ok(Self { is_debug_mode })
79 }
80
81 pub fn is_debug_mode(&self) -> bool {
82 self.is_debug_mode
83 }
84}
85
86fn is_any_entry_debug_mode(entries: &[BccEntry]) -> Result<bool> {
87 // Check if any entry in the chain is marked as Debug mode, which means the device is not
88 // secure. (Normal means it is a secure boot, for that stage at least; we ignore recovery
89 // & not configured /invalid values, since it's not clear what they would mean in this
90 // context.)
91 for entry in entries {
92 if entry.payload()?.is_debug_mode()? {
93 return Ok(true);
94 }
95 }
96 Ok(false)
97}
98
99#[repr(transparent)]
100struct BccEntry(Value);
101
102#[repr(transparent)]
103struct BccPayload(Value);
104
105impl BccEntry {
106 pub fn new(entry: Value) -> Self {
107 Self(entry)
108 }
109
110 pub fn payload(&self) -> Result<BccPayload> {
111 // BccEntry = [ // COSE_Sign1 (untagged)
112 // protected : bstr .cbor {
113 // 1 : AlgorithmEdDSA / AlgorithmES256, // Algorithm
114 // },
115 // unprotected: {},
116 // payload: bstr .cbor BccPayload,
117 // signature: bstr // PureEd25519(SigningKey, bstr .cbor BccEntryInput) /
118 // // ECDSA(SigningKey, bstr .cbor BccEntryInput)
119 // // See RFC 8032 for details of how to encode the signature value for Ed25519.
120 // ]
121 let payload =
122 self.payload_bytes().ok_or(BccError::MalformedBcc("Invalid payload in BccEntry"))?;
123 let payload = value_from_bytes(payload)?;
124 trace!("Bcc payload: {payload:?}");
125 Ok(BccPayload(payload))
126 }
127
128 fn payload_bytes(&self) -> Option<&Vec<u8>> {
129 let entry = self.0.as_array()?;
130 if entry.len() != 4 {
131 return None;
132 };
133 entry[2].as_bytes()
134 }
135}
136
137const KEY_MODE: i32 = -4670551;
138const MODE_DEBUG: u8 = DiceMode::kDiceModeDebug as u8;
139
140impl BccPayload {
141 pub fn is_debug_mode(&self) -> Result<bool> {
142 // BccPayload = { // CWT
143 // ...
144 // ? -4670551 : bstr, // Mode
145 // ...
146 // }
147
148 let Some(value) = self.value_from_key(KEY_MODE) else { return Ok(false) };
149
150 // Mode is supposed to be encoded as a 1-byte bstr, but some implementations instead
151 // encode it as an integer. Accept either. See b/273552826.
152 // If Mode is omitted, it should be treated as if it was Unknown, according to the Open
153 // Profile for DICE spec.
154 let mode = if let Some(bytes) = value.as_bytes() {
155 if bytes.len() != 1 {
156 return Err(BccError::MalformedBcc("Invalid mode bstr"));
157 }
158 bytes[0].into()
159 } else {
160 value.as_integer().ok_or(BccError::MalformedBcc("Invalid type for mode"))?
161 };
162 Ok(mode == MODE_DEBUG.into())
163 }
164
165 fn value_from_key(&self, key: i32) -> Option<&Value> {
166 // BccPayload is just a map; we only use integral keys, but in general it's legitimate
167 // for other things to be present, or for the key we care about not to be present.
168 // Ciborium represents the map as a Vec, preserving order (and allowing duplicate keys,
169 // which we ignore) but preventing fast lookup.
170 let payload = self.0.as_map()?;
171 for (k, v) in payload {
172 if k.as_integer() == Some(key.into()) {
173 return Some(v);
174 }
175 }
176 None
177 }
178}
179
180/// Decodes the provided binary CBOR-encoded value and returns a
181/// ciborium::Value struct wrapped in Result.
182fn value_from_bytes(mut bytes: &[u8]) -> Result<Value> {
183 let value = ciborium::de::from_reader(&mut bytes).map_err(BccError::CborDecodeError)?;
184 // Ciborium tries to read one Value, but doesn't care if there is trailing data after it. We do.
185 if !bytes.is_empty() {
186 return Err(BccError::ExtraneousBytes);
187 }
188 Ok(value)
189}