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