blob: c8353fa57fcbe63489666364a48f7474b0f9406b [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
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080015//! Code to inspect/manipulate the DICE Chain we receive from our loader.
Alan Stokes4db76eb2023-04-26 14:28:15 +010016
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;
Alice Wang6823ffc2024-10-29 15:54:34 +000024use coset::{iana, Algorithm, CborSerializable, CoseKey};
Alan Stokesa38d3b32023-05-05 12:19:18 +010025use diced_open_dice::{BccHandover, Cdi, DiceArtifacts, DiceMode};
Alan Stokes4db76eb2023-04-26 14:28:15 +010026use log::trace;
27
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080028type Result<T> = core::result::Result<T, DiceChainError>;
Alan Stokes4db76eb2023-04-26 14:28:15 +010029
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080030pub enum DiceChainError {
Shikha Panwar8f7fc1a2024-04-10 10:41:34 +000031 CborDecodeError,
32 CborEncodeError,
Alice Wang6823ffc2024-10-29 15:54:34 +000033 CosetError(coset::CoseError),
Alan Stokesa38d3b32023-05-05 12:19:18 +010034 DiceError(diced_open_dice::DiceError),
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080035 Malformed(&'static str),
36 Missing,
Alan Stokes4db76eb2023-04-26 14:28:15 +010037}
38
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080039impl From<coset::CoseError> for DiceChainError {
Alice Wang6823ffc2024-10-29 15:54:34 +000040 fn from(e: coset::CoseError) -> Self {
41 Self::CosetError(e)
42 }
43}
44
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080045impl fmt::Display for DiceChainError {
Alan Stokes4db76eb2023-04-26 14:28:15 +010046 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080048 Self::CborDecodeError => write!(f, "Error parsing DICE chain CBOR"),
49 Self::CborEncodeError => write!(f, "Error encoding DICE chain CBOR"),
Alice Wang6823ffc2024-10-29 15:54:34 +000050 Self::CosetError(e) => write!(f, "Encountered an error with coset: {e}"),
Alan Stokesa38d3b32023-05-05 12:19:18 +010051 Self::DiceError(e) => write!(f, "Dice error: {e:?}"),
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080052 Self::Malformed(s) => {
53 write!(f, "DICE chain does not have the expected CBOR structure: {s}")
Alan Stokes4db76eb2023-04-26 14:28:15 +010054 }
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080055 Self::Missing => write!(f, "Missing DICE chain"),
Alan Stokes4db76eb2023-04-26 14:28:15 +010056 }
57 }
58}
59
Alan Stokesa38d3b32023-05-05 12:19:18 +010060/// Return a new CBOR encoded BccHandover that is based on the incoming CDIs but does not chain
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080061/// from the received DICE chain.
Pierre-Clément Tosif5d34852025-03-03 11:44:43 -080062#[cfg_attr(test, allow(dead_code))]
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080063pub fn truncate(handover: BccHandover) -> Result<Vec<u8>> {
Alan Stokesa38d3b32023-05-05 12:19:18 +010064 // Note: The strings here are deliberately different from those used in a normal DICE handover
65 // because we want this to not be equivalent to any valid DICE derivation.
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080066 let cdi_seal = taint_cdi(handover.cdi_seal(), "TaintCdiSeal")?;
67 let cdi_attest = taint_cdi(handover.cdi_attest(), "TaintCdiAttest")?;
Alan Stokesa38d3b32023-05-05 12:19:18 +010068
69 // BccHandover = {
70 // 1 : bstr .size 32, ; CDI_Attest
71 // 2 : bstr .size 32, ; CDI_Seal
72 // ? 3 : Bcc, ; Certificate chain
73 // }
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080074 let handover: Vec<(Value, Value)> =
Alan Stokesa38d3b32023-05-05 12:19:18 +010075 vec![(1.into(), cdi_attest.as_slice().into()), (2.into(), cdi_seal.as_slice().into())];
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080076 cbor_util::serialize(&handover).map_err(|_| DiceChainError::CborEncodeError)
Alan Stokesa38d3b32023-05-05 12:19:18 +010077}
78
Pierre-Clément Tosif5d34852025-03-03 11:44:43 -080079#[cfg_attr(test, allow(dead_code))]
Alan Stokesa38d3b32023-05-05 12:19:18 +010080fn taint_cdi(cdi: &Cdi, info: &str) -> Result<Cdi> {
81 // An arbitrary value generated randomly.
82 const SALT: [u8; 64] = [
83 0xdc, 0x0d, 0xe7, 0x40, 0x47, 0x9d, 0x71, 0xb8, 0x69, 0xd0, 0x71, 0x85, 0x27, 0x47, 0xf5,
84 0x65, 0x7f, 0x16, 0xfa, 0x59, 0x23, 0x19, 0x6a, 0x6b, 0x77, 0x41, 0x01, 0x45, 0x90, 0x3b,
85 0xfa, 0x68, 0xad, 0xe5, 0x26, 0x31, 0x5b, 0x40, 0x85, 0x71, 0x97, 0x12, 0xbd, 0x0b, 0x38,
86 0x5c, 0x98, 0xf3, 0x0e, 0xe1, 0x7c, 0x82, 0x23, 0xa4, 0x38, 0x38, 0x85, 0x84, 0x85, 0x0d,
87 0x02, 0x90, 0x60, 0xd3,
88 ];
89 let mut result = [0u8; size_of::<Cdi>()];
90 diced_open_dice::kdf(cdi.as_slice(), &SALT, info.as_bytes(), result.as_mut_slice())
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080091 .map_err(DiceChainError::DiceError)?;
Alan Stokesa38d3b32023-05-05 12:19:18 +010092 Ok(result)
93}
94
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -080095/// Represents a (partially) decoded DICE chain.
96pub struct DiceChainInfo {
Alan Stokes4db76eb2023-04-26 14:28:15 +010097 is_debug_mode: bool,
Alice Wang6823ffc2024-10-29 15:54:34 +000098 leaf_subject_pubkey: PublicKey,
Alan Stokes4db76eb2023-04-26 14:28:15 +010099}
100
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800101impl DiceChainInfo {
102 pub fn new(handover: Option<&[u8]>) -> Result<Self> {
103 let handover = handover.filter(|h| !h.is_empty()).ok_or(DiceChainError::Missing)?;
Alan Stokes4db76eb2023-04-26 14:28:15 +0100104
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800105 // We don't attempt to fully validate the DICE chain (e.g. we don't check the signatures) -
106 // we have to trust our loader. But if it's invalid CBOR or otherwise clearly ill-formed,
Alan Stokes4db76eb2023-04-26 14:28:15 +0100107 // something is very wrong, so we fail.
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800108 let handover_cbor =
109 cbor_util::deserialize(handover).map_err(|_| DiceChainError::CborDecodeError)?;
Alan Stokes4db76eb2023-04-26 14:28:15 +0100110
111 // Bcc = [
112 // PubKeyEd25519 / PubKeyECDSA256, // DK_pub
113 // + BccEntry, // Root -> leaf (KM_pub)
114 // ]
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800115 let dice_chain = match handover_cbor {
Alan Stokes4db76eb2023-04-26 14:28:15 +0100116 Value::Array(v) if v.len() >= 2 => v,
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800117 _ => return Err(DiceChainError::Malformed("Invalid top level value")),
Alan Stokes4db76eb2023-04-26 14:28:15 +0100118 };
Alice Wang1f968b12024-11-04 10:44:27 +0000119 // Decode all the DICE payloads to make sure they are well-formed.
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800120 let payloads = dice_chain
Alice Wang1f968b12024-11-04 10:44:27 +0000121 .into_iter()
122 .skip(1)
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800123 .map(|v| DiceChainEntry::new(v).payload())
Alice Wang1f968b12024-11-04 10:44:27 +0000124 .collect::<Result<Vec<_>>>()?;
Alan Stokes4db76eb2023-04-26 14:28:15 +0100125
Alice Wang1f968b12024-11-04 10:44:27 +0000126 let is_debug_mode = is_any_payload_debug_mode(&payloads)?;
Alice Wang6823ffc2024-10-29 15:54:34 +0000127 // Safe to unwrap because we checked the length above.
128 let leaf_subject_pubkey = payloads.last().unwrap().subject_public_key()?;
129 Ok(Self { is_debug_mode, leaf_subject_pubkey })
Alan Stokes4db76eb2023-04-26 14:28:15 +0100130 }
131
Pierre-Clément Tosi7df34332025-03-03 11:58:01 -0800132 /// Returns whether any node in the received DICE chain is marked as debug (and hence is not
133 /// secure).
Alan Stokes4db76eb2023-04-26 14:28:15 +0100134 pub fn is_debug_mode(&self) -> bool {
135 self.is_debug_mode
136 }
Alice Wang6823ffc2024-10-29 15:54:34 +0000137
138 pub fn leaf_subject_pubkey(&self) -> &PublicKey {
139 &self.leaf_subject_pubkey
140 }
Alan Stokes4db76eb2023-04-26 14:28:15 +0100141}
142
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800143fn is_any_payload_debug_mode(payloads: &[DiceChainEntryPayload]) -> Result<bool> {
Alice Wang1f968b12024-11-04 10:44:27 +0000144 // 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 +0100145 // secure. (Normal means it is a secure boot, for that stage at least; we ignore recovery
146 // & not configured /invalid values, since it's not clear what they would mean in this
147 // context.)
Alice Wang1f968b12024-11-04 10:44:27 +0000148 for payload in payloads {
149 if payload.is_debug_mode()? {
Alan Stokes4db76eb2023-04-26 14:28:15 +0100150 return Ok(true);
151 }
152 }
153 Ok(false)
154}
155
156#[repr(transparent)]
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800157struct DiceChainEntry(Value);
Alan Stokes4db76eb2023-04-26 14:28:15 +0100158
Alice Wang6823ffc2024-10-29 15:54:34 +0000159#[derive(Debug, Clone)]
160pub struct PublicKey {
161 /// The COSE key algorithm for the public key, representing the value of the `alg`
162 /// field in the COSE key format of the public key. See RFC 8152, section 7 for details.
163 pub cose_alg: iana::Algorithm,
164}
165
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800166impl DiceChainEntry {
Alan Stokes4db76eb2023-04-26 14:28:15 +0100167 pub fn new(entry: Value) -> Self {
168 Self(entry)
169 }
170
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800171 pub fn payload(&self) -> Result<DiceChainEntryPayload> {
Alan Stokes4db76eb2023-04-26 14:28:15 +0100172 // BccEntry = [ // COSE_Sign1 (untagged)
173 // protected : bstr .cbor {
174 // 1 : AlgorithmEdDSA / AlgorithmES256, // Algorithm
175 // },
176 // unprotected: {},
177 // payload: bstr .cbor BccPayload,
178 // signature: bstr // PureEd25519(SigningKey, bstr .cbor BccEntryInput) /
179 // // ECDSA(SigningKey, bstr .cbor BccEntryInput)
180 // // See RFC 8032 for details of how to encode the signature value for Ed25519.
181 // ]
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800182 let payload = self
183 .payload_bytes()
184 .ok_or(DiceChainError::Malformed("Invalid DiceChainEntryPayload"))?;
Alan Stokes4db76eb2023-04-26 14:28:15 +0100185 let payload =
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800186 cbor_util::deserialize(payload).map_err(|_| DiceChainError::CborDecodeError)?;
187 trace!("DiceChainEntryPayload: {payload:?}");
188 Ok(DiceChainEntryPayload(payload))
Alan Stokes4db76eb2023-04-26 14:28:15 +0100189 }
190
191 fn payload_bytes(&self) -> Option<&Vec<u8>> {
192 let entry = self.0.as_array()?;
193 if entry.len() != 4 {
194 return None;
195 };
196 entry[2].as_bytes()
197 }
198}
199
200const KEY_MODE: i32 = -4670551;
201const MODE_DEBUG: u8 = DiceMode::kDiceModeDebug as u8;
Alice Wang6823ffc2024-10-29 15:54:34 +0000202const SUBJECT_PUBLIC_KEY: i32 = -4670552;
Alan Stokes4db76eb2023-04-26 14:28:15 +0100203
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800204#[repr(transparent)]
205struct DiceChainEntryPayload(Value);
206
207impl DiceChainEntryPayload {
Alan Stokes4db76eb2023-04-26 14:28:15 +0100208 pub fn is_debug_mode(&self) -> Result<bool> {
209 // BccPayload = { // CWT
210 // ...
211 // ? -4670551 : bstr, // Mode
212 // ...
213 // }
214
215 let Some(value) = self.value_from_key(KEY_MODE) else { return Ok(false) };
216
217 // Mode is supposed to be encoded as a 1-byte bstr, but some implementations instead
218 // encode it as an integer. Accept either. See b/273552826.
219 // If Mode is omitted, it should be treated as if it was Unknown, according to the Open
220 // Profile for DICE spec.
221 let mode = if let Some(bytes) = value.as_bytes() {
222 if bytes.len() != 1 {
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800223 return Err(DiceChainError::Malformed("Invalid mode bstr"));
Alan Stokes4db76eb2023-04-26 14:28:15 +0100224 }
225 bytes[0].into()
226 } else {
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800227 value.as_integer().ok_or(DiceChainError::Malformed("Invalid type for mode"))?
Alan Stokes4db76eb2023-04-26 14:28:15 +0100228 };
229 Ok(mode == MODE_DEBUG.into())
230 }
231
Alice Wang6823ffc2024-10-29 15:54:34 +0000232 fn subject_public_key(&self) -> Result<PublicKey> {
233 // BccPayload = { ; CWT [RFC8392]
234 // ...
235 // -4670552 : bstr .cbor PubKeyEd25519 /
236 // bstr .cbor PubKeyECDSA256 /
237 // bstr .cbor PubKeyECDSA384, ; Subject Public Key
238 // ...
239 // }
240 self.value_from_key(SUBJECT_PUBLIC_KEY)
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800241 .ok_or(DiceChainError::Malformed("Subject public key missing"))?
Alice Wang6823ffc2024-10-29 15:54:34 +0000242 .as_bytes()
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800243 .ok_or(DiceChainError::Malformed("Subject public key is not a byte string"))
Alice Wang6823ffc2024-10-29 15:54:34 +0000244 .and_then(|v| PublicKey::from_slice(v))
245 }
246
Alan Stokes4db76eb2023-04-26 14:28:15 +0100247 fn value_from_key(&self, key: i32) -> Option<&Value> {
248 // BccPayload is just a map; we only use integral keys, but in general it's legitimate
249 // for other things to be present, or for the key we care about not to be present.
250 // Ciborium represents the map as a Vec, preserving order (and allowing duplicate keys,
251 // which we ignore) but preventing fast lookup.
252 let payload = self.0.as_map()?;
253 for (k, v) in payload {
254 if k.as_integer() == Some(key.into()) {
255 return Some(v);
256 }
257 }
258 None
259 }
260}
Alice Wang6823ffc2024-10-29 15:54:34 +0000261
262impl PublicKey {
263 fn from_slice(slice: &[u8]) -> Result<Self> {
264 let key = CoseKey::from_slice(slice)?;
265 let Some(Algorithm::Assigned(cose_alg)) = key.alg else {
Pierre-Clément Tosi520664c2025-03-03 11:51:53 -0800266 return Err(DiceChainError::Malformed("Invalid algorithm in public key"));
Alice Wang6823ffc2024-10-29 15:54:34 +0000267 };
268 Ok(Self { cose_alg })
269 }
270}