blob: 7a13da77bc400b20c4355b1e2bfdb28a15ba5f1e [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 };
112 // Decode all the entries to make sure they are well-formed.
113 let entries: Vec<_> = bcc.into_iter().skip(1).map(BccEntry::new).collect();
114
115 let is_debug_mode = is_any_entry_debug_mode(entries.as_slice())?;
116 Ok(Self { is_debug_mode })
117 }
118
119 pub fn is_debug_mode(&self) -> bool {
120 self.is_debug_mode
121 }
122}
123
124fn is_any_entry_debug_mode(entries: &[BccEntry]) -> Result<bool> {
125 // Check if any entry in the chain is marked as Debug mode, which means the device is not
126 // secure. (Normal means it is a secure boot, for that stage at least; we ignore recovery
127 // & not configured /invalid values, since it's not clear what they would mean in this
128 // context.)
129 for entry in entries {
130 if entry.payload()?.is_debug_mode()? {
131 return Ok(true);
132 }
133 }
134 Ok(false)
135}
136
137#[repr(transparent)]
138struct BccEntry(Value);
139
140#[repr(transparent)]
141struct BccPayload(Value);
142
143impl BccEntry {
144 pub fn new(entry: Value) -> Self {
145 Self(entry)
146 }
147
148 pub fn payload(&self) -> Result<BccPayload> {
149 // BccEntry = [ // COSE_Sign1 (untagged)
150 // protected : bstr .cbor {
151 // 1 : AlgorithmEdDSA / AlgorithmES256, // Algorithm
152 // },
153 // unprotected: {},
154 // payload: bstr .cbor BccPayload,
155 // signature: bstr // PureEd25519(SigningKey, bstr .cbor BccEntryInput) /
156 // // ECDSA(SigningKey, bstr .cbor BccEntryInput)
157 // // See RFC 8032 for details of how to encode the signature value for Ed25519.
158 // ]
159 let payload =
160 self.payload_bytes().ok_or(BccError::MalformedBcc("Invalid payload in BccEntry"))?;
Shikha Panwar8f7fc1a2024-04-10 10:41:34 +0000161 let payload = cbor_util::deserialize(payload).map_err(|_| BccError::CborDecodeError)?;
Alan Stokes4db76eb2023-04-26 14:28:15 +0100162 trace!("Bcc payload: {payload:?}");
163 Ok(BccPayload(payload))
164 }
165
166 fn payload_bytes(&self) -> Option<&Vec<u8>> {
167 let entry = self.0.as_array()?;
168 if entry.len() != 4 {
169 return None;
170 };
171 entry[2].as_bytes()
172 }
173}
174
175const KEY_MODE: i32 = -4670551;
176const MODE_DEBUG: u8 = DiceMode::kDiceModeDebug as u8;
177
178impl BccPayload {
179 pub fn is_debug_mode(&self) -> Result<bool> {
180 // BccPayload = { // CWT
181 // ...
182 // ? -4670551 : bstr, // Mode
183 // ...
184 // }
185
186 let Some(value) = self.value_from_key(KEY_MODE) else { return Ok(false) };
187
188 // Mode is supposed to be encoded as a 1-byte bstr, but some implementations instead
189 // encode it as an integer. Accept either. See b/273552826.
190 // If Mode is omitted, it should be treated as if it was Unknown, according to the Open
191 // Profile for DICE spec.
192 let mode = if let Some(bytes) = value.as_bytes() {
193 if bytes.len() != 1 {
194 return Err(BccError::MalformedBcc("Invalid mode bstr"));
195 }
196 bytes[0].into()
197 } else {
198 value.as_integer().ok_or(BccError::MalformedBcc("Invalid type for mode"))?
199 };
200 Ok(mode == MODE_DEBUG.into())
201 }
202
203 fn value_from_key(&self, key: i32) -> Option<&Value> {
204 // BccPayload is just a map; we only use integral keys, but in general it's legitimate
205 // for other things to be present, or for the key we care about not to be present.
206 // Ciborium represents the map as a Vec, preserving order (and allowing duplicate keys,
207 // which we ignore) but preventing fast lookup.
208 let payload = self.0.as_map()?;
209 for (k, v) in payload {
210 if k.as_integer() == Some(key.into()) {
211 return Some(v);
212 }
213 }
214 None
215 }
216}