• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 diced_open_dice::{BccHandover, Cdi, DiceArtifacts, DiceMode};
25 use log::trace;
26 
27 type Result<T> = core::result::Result<T, BccError>;
28 
29 pub enum BccError {
30     CborDecodeError,
31     CborEncodeError,
32     DiceError(diced_open_dice::DiceError),
33     MalformedBcc(&'static str),
34     MissingBcc,
35 }
36 
37 impl fmt::Display for BccError {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result38     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39         match self {
40             Self::CborDecodeError => write!(f, "Error parsing BCC CBOR"),
41             Self::CborEncodeError => write!(f, "Error encoding BCC CBOR"),
42             Self::DiceError(e) => write!(f, "Dice error: {e:?}"),
43             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 
51 /// Return a new CBOR encoded BccHandover that is based on the incoming CDIs but does not chain
52 /// from the received BCC.
truncate(bcc_handover: BccHandover) -> Result<Vec<u8>>53 pub 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())];
66     cbor_util::serialize(&bcc_handover).map_err(|_| BccError::CborEncodeError)
67 }
68 
taint_cdi(cdi: &Cdi, info: &str) -> Result<Cdi>69 fn 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 
84 /// Represents a (partially) decoded BCC DICE chain.
85 pub struct Bcc {
86     is_debug_mode: bool,
87 }
88 
89 impl Bcc {
90     /// Returns whether any node in the received DICE chain is marked as debug (and hence is not
91     /// secure).
new(received_bcc: Option<&[u8]>) -> Result<Bcc>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.
101         let bcc_cbor =
102             cbor_util::deserialize(received_bcc).map_err(|_| BccError::CborDecodeError)?;
103 
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 
is_debug_mode(&self) -> bool119     pub fn is_debug_mode(&self) -> bool {
120         self.is_debug_mode
121     }
122 }
123 
is_any_entry_debug_mode(entries: &[BccEntry]) -> Result<bool>124 fn 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)]
138 struct BccEntry(Value);
139 
140 #[repr(transparent)]
141 struct BccPayload(Value);
142 
143 impl BccEntry {
new(entry: Value) -> Self144     pub fn new(entry: Value) -> Self {
145         Self(entry)
146     }
147 
payload(&self) -> Result<BccPayload>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"))?;
161         let payload = cbor_util::deserialize(payload).map_err(|_| BccError::CborDecodeError)?;
162         trace!("Bcc payload: {payload:?}");
163         Ok(BccPayload(payload))
164     }
165 
payload_bytes(&self) -> Option<&Vec<u8>>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 
175 const KEY_MODE: i32 = -4670551;
176 const MODE_DEBUG: u8 = DiceMode::kDiceModeDebug as u8;
177 
178 impl BccPayload {
is_debug_mode(&self) -> Result<bool>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 
value_from_key(&self, key: i32) -> Option<&Value>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 }
217