blob: 5317ce94ce4613e884f401677c520c8d1e30acd6 [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
Alan Stokesa38d3b32023-05-05 12:19:18 +010019use alloc::vec;
Alan Stokes4db76eb2023-04-26 14:28:15 +010020use alloc::vec::Vec;
21use ciborium::value::Value;
22use core::fmt;
Alan Stokesa38d3b32023-05-05 12:19:18 +010023use core::mem::size_of;
24use diced_open_dice::{BccHandover, Cdi, DiceArtifacts, DiceMode};
Alan Stokes4db76eb2023-04-26 14:28:15 +010025use log::trace;
26
27type Result<T> = core::result::Result<T, BccError>;
28
29pub enum BccError {
Shikha Panwar8f7fc1a2024-04-10 10:41:34 +000030 CborDecodeError,
31 CborEncodeError,
Alan Stokesa38d3b32023-05-05 12:19:18 +010032 DiceError(diced_open_dice::DiceError),
Alan Stokes4db76eb2023-04-26 14:28:15 +010033 MalformedBcc(&'static str),
34 MissingBcc,
35}
36
37impl fmt::Display for BccError {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
Shikha Panwar8f7fc1a2024-04-10 10:41:34 +000040 Self::CborDecodeError => write!(f, "Error parsing BCC CBOR"),
41 Self::CborEncodeError => write!(f, "Error encoding BCC CBOR"),
Alan Stokesa38d3b32023-05-05 12:19:18 +010042 Self::DiceError(e) => write!(f, "Dice error: {e:?}"),
Alan Stokes4db76eb2023-04-26 14:28:15 +010043 Self::MalformedBcc(s) => {
44 write!(f, "BCC does not have the expected CBOR structure: {s}")
45 }
46 Self::MissingBcc => write!(f, "Missing BCC"),
47 }
48 }
49}
50
Alan Stokesa38d3b32023-05-05 12:19:18 +010051/// Return a new CBOR encoded BccHandover that is based on the incoming CDIs but does not chain
52/// from the received BCC.
53pub fn truncate(bcc_handover: BccHandover) -> Result<Vec<u8>> {
54 // Note: The strings here are deliberately different from those used in a normal DICE handover
55 // because we want this to not be equivalent to any valid DICE derivation.
56 let cdi_seal = taint_cdi(bcc_handover.cdi_seal(), "TaintCdiSeal")?;
57 let cdi_attest = taint_cdi(bcc_handover.cdi_attest(), "TaintCdiAttest")?;
58
59 // BccHandover = {
60 // 1 : bstr .size 32, ; CDI_Attest
61 // 2 : bstr .size 32, ; CDI_Seal
62 // ? 3 : Bcc, ; Certificate chain
63 // }
64 let bcc_handover: Vec<(Value, Value)> =
65 vec![(1.into(), cdi_attest.as_slice().into()), (2.into(), cdi_seal.as_slice().into())];
Shikha Panwar8f7fc1a2024-04-10 10:41:34 +000066 cbor_util::serialize(&bcc_handover).map_err(|_| BccError::CborEncodeError)
Alan Stokesa38d3b32023-05-05 12:19:18 +010067}
68
69fn taint_cdi(cdi: &Cdi, info: &str) -> Result<Cdi> {
70 // An arbitrary value generated randomly.
71 const SALT: [u8; 64] = [
72 0xdc, 0x0d, 0xe7, 0x40, 0x47, 0x9d, 0x71, 0xb8, 0x69, 0xd0, 0x71, 0x85, 0x27, 0x47, 0xf5,
73 0x65, 0x7f, 0x16, 0xfa, 0x59, 0x23, 0x19, 0x6a, 0x6b, 0x77, 0x41, 0x01, 0x45, 0x90, 0x3b,
74 0xfa, 0x68, 0xad, 0xe5, 0x26, 0x31, 0x5b, 0x40, 0x85, 0x71, 0x97, 0x12, 0xbd, 0x0b, 0x38,
75 0x5c, 0x98, 0xf3, 0x0e, 0xe1, 0x7c, 0x82, 0x23, 0xa4, 0x38, 0x38, 0x85, 0x84, 0x85, 0x0d,
76 0x02, 0x90, 0x60, 0xd3,
77 ];
78 let mut result = [0u8; size_of::<Cdi>()];
79 diced_open_dice::kdf(cdi.as_slice(), &SALT, info.as_bytes(), result.as_mut_slice())
80 .map_err(BccError::DiceError)?;
81 Ok(result)
82}
83
Alan Stokes4db76eb2023-04-26 14:28:15 +010084/// Represents a (partially) decoded BCC DICE chain.
85pub struct Bcc {
86 is_debug_mode: bool,
87}
88
89impl Bcc {
90 /// Returns whether any node in the received DICE chain is marked as debug (and hence is not
91 /// secure).
92 pub fn new(received_bcc: Option<&[u8]>) -> Result<Bcc> {
93 let received_bcc = received_bcc.unwrap_or(&[]);
94 if received_bcc.is_empty() {
95 return Err(BccError::MissingBcc);
96 }
97
98 // We don't attempt to fully validate the BCC (e.g. we don't check the signatures) - we
99 // have to trust our loader. But if it's invalid CBOR or otherwise clearly ill-formed,
100 // something is very wrong, so we fail.
Shikha Panwar8f7fc1a2024-04-10 10:41:34 +0000101 let bcc_cbor =
102 cbor_util::deserialize(received_bcc).map_err(|_| BccError::CborDecodeError)?;
Alan Stokes4db76eb2023-04-26 14:28:15 +0100103
104 // Bcc = [
105 // PubKeyEd25519 / PubKeyECDSA256, // DK_pub
106 // + BccEntry, // Root -> leaf (KM_pub)
107 // ]
108 let bcc = match bcc_cbor {
109 Value::Array(v) if v.len() >= 2 => v,
110 _ => return Err(BccError::MalformedBcc("Invalid top level value")),
111 };
Alice Wang1f968b12024-11-04 10:44:27 +0000112 // Decode all the DICE payloads to make sure they are well-formed.
113 let payloads = bcc
114 .into_iter()
115 .skip(1)
116 .map(|v| BccEntry::new(v).payload())
117 .collect::<Result<Vec<_>>>()?;
Alan Stokes4db76eb2023-04-26 14:28:15 +0100118
Alice Wang1f968b12024-11-04 10:44:27 +0000119 let is_debug_mode = is_any_payload_debug_mode(&payloads)?;
Alan Stokes4db76eb2023-04-26 14:28:15 +0100120 Ok(Self { is_debug_mode })
121 }
122
123 pub fn is_debug_mode(&self) -> bool {
124 self.is_debug_mode
125 }
126}
127
Alice Wang1f968b12024-11-04 10:44:27 +0000128fn is_any_payload_debug_mode(payloads: &[BccPayload]) -> Result<bool> {
129 // Check if any payload in the chain is marked as Debug mode, which means the device is not
Alan Stokes4db76eb2023-04-26 14:28:15 +0100130 // secure. (Normal means it is a secure boot, for that stage at least; we ignore recovery
131 // & not configured /invalid values, since it's not clear what they would mean in this
132 // context.)
Alice Wang1f968b12024-11-04 10:44:27 +0000133 for payload in payloads {
134 if payload.is_debug_mode()? {
Alan Stokes4db76eb2023-04-26 14:28:15 +0100135 return Ok(true);
136 }
137 }
138 Ok(false)
139}
140
141#[repr(transparent)]
142struct BccEntry(Value);
143
144#[repr(transparent)]
145struct BccPayload(Value);
146
147impl BccEntry {
148 pub fn new(entry: Value) -> Self {
149 Self(entry)
150 }
151
152 pub fn payload(&self) -> Result<BccPayload> {
153 // BccEntry = [ // COSE_Sign1 (untagged)
154 // protected : bstr .cbor {
155 // 1 : AlgorithmEdDSA / AlgorithmES256, // Algorithm
156 // },
157 // unprotected: {},
158 // payload: bstr .cbor BccPayload,
159 // signature: bstr // PureEd25519(SigningKey, bstr .cbor BccEntryInput) /
160 // // ECDSA(SigningKey, bstr .cbor BccEntryInput)
161 // // See RFC 8032 for details of how to encode the signature value for Ed25519.
162 // ]
163 let payload =
164 self.payload_bytes().ok_or(BccError::MalformedBcc("Invalid payload in BccEntry"))?;
Shikha Panwar8f7fc1a2024-04-10 10:41:34 +0000165 let payload = cbor_util::deserialize(payload).map_err(|_| BccError::CborDecodeError)?;
Alan Stokes4db76eb2023-04-26 14:28:15 +0100166 trace!("Bcc payload: {payload:?}");
167 Ok(BccPayload(payload))
168 }
169
170 fn payload_bytes(&self) -> Option<&Vec<u8>> {
171 let entry = self.0.as_array()?;
172 if entry.len() != 4 {
173 return None;
174 };
175 entry[2].as_bytes()
176 }
177}
178
179const KEY_MODE: i32 = -4670551;
180const MODE_DEBUG: u8 = DiceMode::kDiceModeDebug as u8;
181
182impl BccPayload {
183 pub fn is_debug_mode(&self) -> Result<bool> {
184 // BccPayload = { // CWT
185 // ...
186 // ? -4670551 : bstr, // Mode
187 // ...
188 // }
189
190 let Some(value) = self.value_from_key(KEY_MODE) else { return Ok(false) };
191
192 // Mode is supposed to be encoded as a 1-byte bstr, but some implementations instead
193 // encode it as an integer. Accept either. See b/273552826.
194 // If Mode is omitted, it should be treated as if it was Unknown, according to the Open
195 // Profile for DICE spec.
196 let mode = if let Some(bytes) = value.as_bytes() {
197 if bytes.len() != 1 {
198 return Err(BccError::MalformedBcc("Invalid mode bstr"));
199 }
200 bytes[0].into()
201 } else {
202 value.as_integer().ok_or(BccError::MalformedBcc("Invalid type for mode"))?
203 };
204 Ok(mode == MODE_DEBUG.into())
205 }
206
207 fn value_from_key(&self, key: i32) -> Option<&Value> {
208 // BccPayload is just a map; we only use integral keys, but in general it's legitimate
209 // for other things to be present, or for the key we care about not to be present.
210 // Ciborium represents the map as a Vec, preserving order (and allowing duplicate keys,
211 // which we ignore) but preventing fast lookup.
212 let payload = self.0.as_map()?;
213 for (k, v) in payload {
214 if k.as_integer() == Some(key.into()) {
215 return Some(v);
216 }
217 }
218 None
219 }
220}