1 // 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
19 use alloc::vec;
20 use alloc::vec::Vec;
21 use ciborium::value::Value;
22 use core::fmt;
23 use core::mem::size_of;
24 use coset::{iana, Algorithm, CborSerializable, CoseKey};
25 use diced_open_dice::{BccHandover, Cdi, DiceArtifacts, DiceMode};
26 use log::trace;
27
28 type Result<T> = core::result::Result<T, BccError>;
29
30 pub enum BccError {
31 CborDecodeError,
32 CborEncodeError,
33 CosetError(coset::CoseError),
34 DiceError(diced_open_dice::DiceError),
35 MalformedBcc(&'static str),
36 MissingBcc,
37 }
38
39 impl From<coset::CoseError> for BccError {
from(e: coset::CoseError) -> Self40 fn from(e: coset::CoseError) -> Self {
41 Self::CosetError(e)
42 }
43 }
44
45 impl fmt::Display for BccError {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
48 Self::CborDecodeError => write!(f, "Error parsing BCC CBOR"),
49 Self::CborEncodeError => write!(f, "Error encoding BCC CBOR"),
50 Self::CosetError(e) => write!(f, "Encountered an error with coset: {e}"),
51 Self::DiceError(e) => write!(f, "Dice error: {e:?}"),
52 Self::MalformedBcc(s) => {
53 write!(f, "BCC does not have the expected CBOR structure: {s}")
54 }
55 Self::MissingBcc => write!(f, "Missing BCC"),
56 }
57 }
58 }
59
60 /// Return a new CBOR encoded BccHandover that is based on the incoming CDIs but does not chain
61 /// from the received BCC.
truncate(bcc_handover: BccHandover) -> Result<Vec<u8>>62 pub fn truncate(bcc_handover: BccHandover) -> Result<Vec<u8>> {
63 // Note: The strings here are deliberately different from those used in a normal DICE handover
64 // because we want this to not be equivalent to any valid DICE derivation.
65 let cdi_seal = taint_cdi(bcc_handover.cdi_seal(), "TaintCdiSeal")?;
66 let cdi_attest = taint_cdi(bcc_handover.cdi_attest(), "TaintCdiAttest")?;
67
68 // BccHandover = {
69 // 1 : bstr .size 32, ; CDI_Attest
70 // 2 : bstr .size 32, ; CDI_Seal
71 // ? 3 : Bcc, ; Certificate chain
72 // }
73 let bcc_handover: Vec<(Value, Value)> =
74 vec![(1.into(), cdi_attest.as_slice().into()), (2.into(), cdi_seal.as_slice().into())];
75 cbor_util::serialize(&bcc_handover).map_err(|_| BccError::CborEncodeError)
76 }
77
taint_cdi(cdi: &Cdi, info: &str) -> Result<Cdi>78 fn taint_cdi(cdi: &Cdi, info: &str) -> Result<Cdi> {
79 // An arbitrary value generated randomly.
80 const SALT: [u8; 64] = [
81 0xdc, 0x0d, 0xe7, 0x40, 0x47, 0x9d, 0x71, 0xb8, 0x69, 0xd0, 0x71, 0x85, 0x27, 0x47, 0xf5,
82 0x65, 0x7f, 0x16, 0xfa, 0x59, 0x23, 0x19, 0x6a, 0x6b, 0x77, 0x41, 0x01, 0x45, 0x90, 0x3b,
83 0xfa, 0x68, 0xad, 0xe5, 0x26, 0x31, 0x5b, 0x40, 0x85, 0x71, 0x97, 0x12, 0xbd, 0x0b, 0x38,
84 0x5c, 0x98, 0xf3, 0x0e, 0xe1, 0x7c, 0x82, 0x23, 0xa4, 0x38, 0x38, 0x85, 0x84, 0x85, 0x0d,
85 0x02, 0x90, 0x60, 0xd3,
86 ];
87 let mut result = [0u8; size_of::<Cdi>()];
88 diced_open_dice::kdf(cdi.as_slice(), &SALT, info.as_bytes(), result.as_mut_slice())
89 .map_err(BccError::DiceError)?;
90 Ok(result)
91 }
92
93 /// Represents a (partially) decoded BCC DICE chain.
94 pub struct Bcc {
95 is_debug_mode: bool,
96 leaf_subject_pubkey: PublicKey,
97 }
98
99 impl Bcc {
100 /// Returns whether any node in the received DICE chain is marked as debug (and hence is not
101 /// secure).
new(received_bcc: Option<&[u8]>) -> Result<Bcc>102 pub fn new(received_bcc: Option<&[u8]>) -> Result<Bcc> {
103 let received_bcc = received_bcc.unwrap_or(&[]);
104 if received_bcc.is_empty() {
105 return Err(BccError::MissingBcc);
106 }
107
108 // We don't attempt to fully validate the BCC (e.g. we don't check the signatures) - we
109 // have to trust our loader. But if it's invalid CBOR or otherwise clearly ill-formed,
110 // something is very wrong, so we fail.
111 let bcc_cbor =
112 cbor_util::deserialize(received_bcc).map_err(|_| BccError::CborDecodeError)?;
113
114 // Bcc = [
115 // PubKeyEd25519 / PubKeyECDSA256, // DK_pub
116 // + BccEntry, // Root -> leaf (KM_pub)
117 // ]
118 let bcc = match bcc_cbor {
119 Value::Array(v) if v.len() >= 2 => v,
120 _ => return Err(BccError::MalformedBcc("Invalid top level value")),
121 };
122 // Decode all the DICE payloads to make sure they are well-formed.
123 let payloads = bcc
124 .into_iter()
125 .skip(1)
126 .map(|v| BccEntry::new(v).payload())
127 .collect::<Result<Vec<_>>>()?;
128
129 let is_debug_mode = is_any_payload_debug_mode(&payloads)?;
130 // Safe to unwrap because we checked the length above.
131 let leaf_subject_pubkey = payloads.last().unwrap().subject_public_key()?;
132 Ok(Self { is_debug_mode, leaf_subject_pubkey })
133 }
134
is_debug_mode(&self) -> bool135 pub fn is_debug_mode(&self) -> bool {
136 self.is_debug_mode
137 }
138
leaf_subject_pubkey(&self) -> &PublicKey139 pub fn leaf_subject_pubkey(&self) -> &PublicKey {
140 &self.leaf_subject_pubkey
141 }
142 }
143
is_any_payload_debug_mode(payloads: &[BccPayload]) -> Result<bool>144 fn is_any_payload_debug_mode(payloads: &[BccPayload]) -> Result<bool> {
145 // Check if any payload in the chain is marked as Debug mode, which means the device is not
146 // secure. (Normal means it is a secure boot, for that stage at least; we ignore recovery
147 // & not configured /invalid values, since it's not clear what they would mean in this
148 // context.)
149 for payload in payloads {
150 if payload.is_debug_mode()? {
151 return Ok(true);
152 }
153 }
154 Ok(false)
155 }
156
157 #[repr(transparent)]
158 struct BccEntry(Value);
159
160 #[repr(transparent)]
161 struct BccPayload(Value);
162
163 #[derive(Debug, Clone)]
164 pub struct PublicKey {
165 /// The COSE key algorithm for the public key, representing the value of the `alg`
166 /// field in the COSE key format of the public key. See RFC 8152, section 7 for details.
167 pub cose_alg: iana::Algorithm,
168 }
169
170 impl BccEntry {
new(entry: Value) -> Self171 pub fn new(entry: Value) -> Self {
172 Self(entry)
173 }
174
payload(&self) -> Result<BccPayload>175 pub fn payload(&self) -> Result<BccPayload> {
176 // BccEntry = [ // COSE_Sign1 (untagged)
177 // protected : bstr .cbor {
178 // 1 : AlgorithmEdDSA / AlgorithmES256, // Algorithm
179 // },
180 // unprotected: {},
181 // payload: bstr .cbor BccPayload,
182 // signature: bstr // PureEd25519(SigningKey, bstr .cbor BccEntryInput) /
183 // // ECDSA(SigningKey, bstr .cbor BccEntryInput)
184 // // See RFC 8032 for details of how to encode the signature value for Ed25519.
185 // ]
186 let payload =
187 self.payload_bytes().ok_or(BccError::MalformedBcc("Invalid payload in BccEntry"))?;
188 let payload = cbor_util::deserialize(payload).map_err(|_| BccError::CborDecodeError)?;
189 trace!("Bcc payload: {payload:?}");
190 Ok(BccPayload(payload))
191 }
192
payload_bytes(&self) -> Option<&Vec<u8>>193 fn payload_bytes(&self) -> Option<&Vec<u8>> {
194 let entry = self.0.as_array()?;
195 if entry.len() != 4 {
196 return None;
197 };
198 entry[2].as_bytes()
199 }
200 }
201
202 const KEY_MODE: i32 = -4670551;
203 const MODE_DEBUG: u8 = DiceMode::kDiceModeDebug as u8;
204 const SUBJECT_PUBLIC_KEY: i32 = -4670552;
205
206 impl BccPayload {
is_debug_mode(&self) -> Result<bool>207 pub fn is_debug_mode(&self) -> Result<bool> {
208 // BccPayload = { // CWT
209 // ...
210 // ? -4670551 : bstr, // Mode
211 // ...
212 // }
213
214 let Some(value) = self.value_from_key(KEY_MODE) else { return Ok(false) };
215
216 // Mode is supposed to be encoded as a 1-byte bstr, but some implementations instead
217 // encode it as an integer. Accept either. See b/273552826.
218 // If Mode is omitted, it should be treated as if it was Unknown, according to the Open
219 // Profile for DICE spec.
220 let mode = if let Some(bytes) = value.as_bytes() {
221 if bytes.len() != 1 {
222 return Err(BccError::MalformedBcc("Invalid mode bstr"));
223 }
224 bytes[0].into()
225 } else {
226 value.as_integer().ok_or(BccError::MalformedBcc("Invalid type for mode"))?
227 };
228 Ok(mode == MODE_DEBUG.into())
229 }
230
subject_public_key(&self) -> Result<PublicKey>231 fn subject_public_key(&self) -> Result<PublicKey> {
232 // BccPayload = { ; CWT [RFC8392]
233 // ...
234 // -4670552 : bstr .cbor PubKeyEd25519 /
235 // bstr .cbor PubKeyECDSA256 /
236 // bstr .cbor PubKeyECDSA384, ; Subject Public Key
237 // ...
238 // }
239 self.value_from_key(SUBJECT_PUBLIC_KEY)
240 .ok_or(BccError::MalformedBcc("Subject public key missing"))?
241 .as_bytes()
242 .ok_or(BccError::MalformedBcc("Subject public key is not a byte string"))
243 .and_then(|v| PublicKey::from_slice(v))
244 }
245
value_from_key(&self, key: i32) -> Option<&Value>246 fn value_from_key(&self, key: i32) -> Option<&Value> {
247 // BccPayload is just a map; we only use integral keys, but in general it's legitimate
248 // for other things to be present, or for the key we care about not to be present.
249 // Ciborium represents the map as a Vec, preserving order (and allowing duplicate keys,
250 // which we ignore) but preventing fast lookup.
251 let payload = self.0.as_map()?;
252 for (k, v) in payload {
253 if k.as_integer() == Some(key.into()) {
254 return Some(v);
255 }
256 }
257 None
258 }
259 }
260
261 impl PublicKey {
from_slice(slice: &[u8]) -> Result<Self>262 fn from_slice(slice: &[u8]) -> Result<Self> {
263 let key = CoseKey::from_slice(slice)?;
264 let Some(Algorithm::Assigned(cose_alg)) = key.alg else {
265 return Err(BccError::MalformedBcc("Invalid algorithm in public key"));
266 };
267 Ok(Self { cose_alg })
268 }
269 }
270