blob: 0a5eac1ba1503d3b30d7c7e2c120082bd1aea74a [file] [log] [blame]
Alice Wangd3a96402023-11-24 15:37:39 +00001// 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//! This module contains functions related to DICE.
16
17use alloc::vec::Vec;
Alice Wanga95dae52023-12-06 09:30:22 +000018use ciborium::value::{Integer, Value};
Alice Wangd3a96402023-11-24 15:37:39 +000019use core::cell::OnceCell;
20use core::result;
21use coset::{
22 self, iana, AsCborValue, CborSerializable, CoseError, CoseKey, CoseSign1, KeyOperation,
23};
24use diced_open_dice::{DiceMode, HASH_SIZE};
25use log::error;
Alice Wanga95dae52023-12-06 09:30:22 +000026use service_vm_comm::{
27 cbor_value_type, to_unexpected_item_error, value_to_bytes, RequestProcessingError,
28};
Alice Wangd3a96402023-11-24 15:37:39 +000029
30type Result<T> = result::Result<T, RequestProcessingError>;
31
32const CODE_HASH: i64 = -4670545;
33const CONFIG_DESC: i64 = -4670548;
34const AUTHORITY_HASH: i64 = -4670549;
35const MODE: i64 = -4670551;
36const SUBJECT_PUBLIC_KEY: i64 = -4670552;
37
38/// Represents a partially decoded `DiceCertChain` from the client VM.
39/// The whole chain is defined as following:
40///
41/// DiceCertChain = [
42/// PubKeyEd25519 / PubKeyECDSA256 / PubKeyECDSA384, ; UDS_Pub
43/// + DiceChainEntry, ; First CDI_Certificate -> Last CDI_Certificate
44/// ]
45#[derive(Debug, Clone)]
46pub(crate) struct ClientVmDiceChain {
47 pub(crate) payloads: Vec<DiceChainEntryPayload>,
48}
49
50impl ClientVmDiceChain {
51 /// Validates the signatures of the entries in the `client_vm_dice_chain` as following:
52 ///
53 /// - The first entry of the `client_vm_dice_chain` must be signed with the root public key.
54 /// - After the first entry, each entry of the `client_vm_dice_chain` must be signed with the
55 /// subject public key of the previous entry.
56 ///
57 /// Returns a partially decoded client VM's DICE chain if the verification succeeds.
58 pub(crate) fn validate_signatures_and_parse_dice_chain(
59 mut client_vm_dice_chain: Vec<Value>,
60 ) -> Result<Self> {
61 let root_public_key =
62 CoseKey::from_cbor_value(client_vm_dice_chain.remove(0))?.try_into()?;
63
64 let mut payloads = Vec::with_capacity(client_vm_dice_chain.len());
65 let mut previous_public_key = &root_public_key;
66 for (i, value) in client_vm_dice_chain.into_iter().enumerate() {
67 let payload = DiceChainEntryPayload::validate_cose_signature_and_extract_payload(
68 value,
69 previous_public_key,
70 )
71 .map_err(|e| {
72 error!("Failed to verify the DICE chain entry {}: {:?}", i, e);
73 e
74 })?;
75 payloads.push(payload);
76 previous_public_key = &payloads.last().unwrap().subject_public_key;
77 }
78 // After successfully calling `validate_client_vm_dice_chain_prefix_match`, we can be
79 // certain that the client VM's DICE chain must contain at least three entries that
80 // describe:
81 // - pvmfw
82 // - Microdroid kernel
83 // - Apk/Apexes
84 assert!(
85 payloads.len() >= 3,
86 "The client VM DICE chain must contain at least three DiceChainEntryPayloads"
87 );
88 Ok(Self { payloads })
89 }
90
91 /// Returns true if all payloads in the DICE chain are in normal mode.
92 pub(crate) fn all_entries_are_secure(&self) -> bool {
93 self.payloads.iter().all(|p| p.mode == DiceMode::kDiceModeNormal)
94 }
95}
96
97/// Validates that the `client_vm_dice_chain` matches the `service_vm_dice_chain` up to the pvmfw
98/// entry.
99///
100/// Returns a CBOR value array of the client VM's DICE chain if the verification succeeds.
101pub(crate) fn validate_client_vm_dice_chain_prefix_match(
102 client_vm_dice_chain: &[u8],
103 service_vm_dice_chain: &[u8],
104) -> Result<Vec<Value>> {
105 let client_vm_dice_chain =
Alice Wanga95dae52023-12-06 09:30:22 +0000106 value_to_array(Value::from_slice(client_vm_dice_chain)?, "client_vm_dice_chain")?;
Alice Wangd3a96402023-11-24 15:37:39 +0000107 let service_vm_dice_chain =
Alice Wanga95dae52023-12-06 09:30:22 +0000108 value_to_array(Value::from_slice(service_vm_dice_chain)?, "service_vm_dice_chain")?;
Alice Wangd3a96402023-11-24 15:37:39 +0000109 if service_vm_dice_chain.len() < 3 {
110 // The service VM's DICE chain must contain the root key and at least two other entries
111 // that describe:
112 // - pvmfw
113 // - Service VM kernel
114 error!("The service VM DICE chain must contain at least three entries");
115 return Err(RequestProcessingError::InternalError);
116 }
117 // Ignores the last entry that describes service VM
118 let entries_up_to_pvmfw = &service_vm_dice_chain[0..(service_vm_dice_chain.len() - 1)];
119 if entries_up_to_pvmfw.len() + 2 != client_vm_dice_chain.len() {
120 // Client VM DICE chain = entries_up_to_pvmfw
121 // + Microdroid kernel entry (added in pvmfw)
122 // + Apk/Apexes entry (added in microdroid)
123 error!("The client VM's DICE chain must contain exactly two extra entries");
124 return Err(RequestProcessingError::InvalidDiceChain);
125 }
126 if entries_up_to_pvmfw != &client_vm_dice_chain[0..entries_up_to_pvmfw.len()] {
127 error!(
128 "The client VM's DICE chain does not match service VM's DICE chain up to \
129 the pvmfw entry"
130 );
131 return Err(RequestProcessingError::InvalidDiceChain);
132 }
133 Ok(client_vm_dice_chain)
134}
135
136#[derive(Debug, Clone)]
137pub(crate) struct PublicKey(CoseKey);
138
139impl TryFrom<CoseKey> for PublicKey {
140 type Error = RequestProcessingError;
141
142 fn try_from(key: CoseKey) -> Result<Self> {
143 if !key.key_ops.contains(&KeyOperation::Assigned(iana::KeyOperation::Verify)) {
144 error!("Public key does not support verification");
145 return Err(RequestProcessingError::InvalidDiceChain);
146 }
147 Ok(Self(key))
148 }
149}
150
151/// Represents a partially decoded `DiceChainEntryPayload`. The whole payload is defined in:
152///
153/// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
154/// generateCertificateRequestV2.cddl
155#[derive(Debug, Clone)]
156pub(crate) struct DiceChainEntryPayload {
157 /// TODO(b/310931749): Verify the DICE chain entry using the subject public key.
158 #[allow(dead_code)]
159 subject_public_key: PublicKey,
160 mode: DiceMode,
161 /// TODO(b/271275206): Verify Microdroid kernel authority and code hashes.
162 #[allow(dead_code)]
163 code_hash: [u8; HASH_SIZE],
164 #[allow(dead_code)]
165 authority_hash: [u8; HASH_SIZE],
166 /// TODO(b/313815907): Parse the config descriptor and read Apk/Apexes info in it.
167 #[allow(dead_code)]
168 config_descriptor: Vec<u8>,
169}
170
171impl DiceChainEntryPayload {
172 /// Validates the signature of the provided CBOR value with the provided public key and
173 /// extracts payload from the value.
174 fn validate_cose_signature_and_extract_payload(
175 value: Value,
176 _authority_public_key: &PublicKey,
177 ) -> Result<Self> {
178 let cose_sign1 = CoseSign1::from_cbor_value(value)?;
179 // TODO(b/310931749): Verify the DICE chain entry using `authority_public_key`.
180
181 let payload = cose_sign1.payload.ok_or_else(|| {
182 error!("No payload found in the DICE chain entry");
183 RequestProcessingError::InvalidDiceChain
184 })?;
Alice Wanga95dae52023-12-06 09:30:22 +0000185 let entries = value_to_map(Value::from_slice(&payload)?, "DiceChainEntryPayload")?;
Alice Wangd3a96402023-11-24 15:37:39 +0000186 build_payload(entries)
187 }
188}
189
190fn build_payload(entries: Vec<(Value, Value)>) -> Result<DiceChainEntryPayload> {
191 let mut builder = PayloadBuilder::default();
192 for (key, value) in entries.into_iter() {
Alice Wanga95dae52023-12-06 09:30:22 +0000193 let key: i64 = value_to_num(key, "DiceChainEntryPayload key")?;
Alice Wangd3a96402023-11-24 15:37:39 +0000194 match key {
195 SUBJECT_PUBLIC_KEY => {
Alice Wanga95dae52023-12-06 09:30:22 +0000196 let subject_public_key = value_to_bytes(value, "subject_public_key")?;
Alice Wangd3a96402023-11-24 15:37:39 +0000197 let subject_public_key = CoseKey::from_slice(&subject_public_key)?.try_into()?;
198 builder.subject_public_key(subject_public_key)?;
199 }
200 MODE => builder.mode(to_mode(value)?)?,
Alice Wanga95dae52023-12-06 09:30:22 +0000201 CODE_HASH => {
202 let code_hash = value_to_byte_array(value, "DiceChainEntryPayload code_hash")?;
203 builder.code_hash(code_hash)?;
Alice Wangd3a96402023-11-24 15:37:39 +0000204 }
Alice Wanga95dae52023-12-06 09:30:22 +0000205 AUTHORITY_HASH => {
206 let authority_hash =
207 value_to_byte_array(value, "DiceChainEntryPayload authority_hash")?;
208 builder.authority_hash(authority_hash)?;
209 }
210 CONFIG_DESC => {
211 let config_descriptor = value_to_bytes(value, "config_descriptor")?;
212 builder.config_descriptor(config_descriptor)?;
213 }
Alice Wangd3a96402023-11-24 15:37:39 +0000214 _ => {}
215 }
216 }
217 builder.build()
218}
219
Alice Wanga95dae52023-12-06 09:30:22 +0000220fn value_to_array(v: Value, context: &'static str) -> coset::Result<Vec<Value>> {
221 v.into_array().map_err(|e| to_unexpected_item_error(&e, "array", context))
Alice Wangd3a96402023-11-24 15:37:39 +0000222}
223
Alice Wanga95dae52023-12-06 09:30:22 +0000224fn value_to_map(v: Value, context: &'static str) -> coset::Result<Vec<(Value, Value)>> {
225 v.into_map().map_err(|e| to_unexpected_item_error(&e, "map", context))
226}
227
228fn value_to_num<T: TryFrom<Integer>>(v: Value, context: &'static str) -> Result<T> {
229 let num = v.into_integer().map_err(|e| to_unexpected_item_error(&e, "int", context))?;
230 num.try_into().map_err(|_| {
231 error!("The provided value '{num:?}' is not a valid number: {context}");
232 RequestProcessingError::InvalidDiceChain
233 })
234}
235
236fn value_to_byte_array<const N: usize>(v: Value, context: &'static str) -> Result<[u8; N]> {
237 value_to_bytes(v, context)?.try_into().map_err(|e| {
Alice Wangd3a96402023-11-24 15:37:39 +0000238 error!("The provided value '{context}' is not an array of length {N}: {e:?}");
239 RequestProcessingError::InternalError
240 })
241}
242
243fn to_mode(value: Value) -> Result<DiceMode> {
244 let mode = match value {
245 // Mode is supposed to be encoded as a 1-byte bstr, but some implementations instead
246 // encode it as an integer. Accept either. See b/273552826.
247 // If Mode is omitted, it should be treated as if it was NotConfigured, according to
248 // the Open Profile for DICE spec.
249 Value::Bytes(bytes) => {
250 if bytes.len() != 1 {
251 error!("Bytes array with invalid length for mode: {:?}", bytes.len());
252 return Err(RequestProcessingError::InvalidDiceChain);
253 }
254 bytes[0].into()
255 }
256 Value::Integer(i) => i,
257 v => return Err(CoseError::UnexpectedItem(cbor_value_type(&v), "bstr or int").into()),
258 };
259 let mode = match mode {
260 x if x == (DiceMode::kDiceModeNormal as i64).into() => DiceMode::kDiceModeNormal,
261 x if x == (DiceMode::kDiceModeDebug as i64).into() => DiceMode::kDiceModeDebug,
262 x if x == (DiceMode::kDiceModeMaintenance as i64).into() => DiceMode::kDiceModeMaintenance,
263 // If Mode is invalid, it should be treated as if it was NotConfigured, according to
264 // the Open Profile for DICE spec.
265 _ => DiceMode::kDiceModeNotInitialized,
266 };
267 Ok(mode)
268}
269
270#[derive(Default, Debug, Clone)]
271struct PayloadBuilder {
272 subject_public_key: OnceCell<PublicKey>,
273 mode: OnceCell<DiceMode>,
274 code_hash: OnceCell<[u8; HASH_SIZE]>,
275 authority_hash: OnceCell<[u8; HASH_SIZE]>,
276 config_descriptor: OnceCell<Vec<u8>>,
277}
278
279fn set_once<T>(field: &OnceCell<T>, value: T, field_name: &str) -> Result<()> {
280 field.set(value).map_err(|_| {
281 error!("Field '{field_name}' is duplicated in the Payload");
282 RequestProcessingError::InvalidDiceChain
283 })
284}
285
286fn take_value<T>(field: &mut OnceCell<T>, field_name: &str) -> Result<T> {
287 field.take().ok_or_else(|| {
288 error!("Field '{field_name}' is missing in the Payload");
289 RequestProcessingError::InvalidDiceChain
290 })
291}
292
293impl PayloadBuilder {
294 fn subject_public_key(&mut self, key: PublicKey) -> Result<()> {
295 set_once(&self.subject_public_key, key, "subject_public_key")
296 }
297
298 fn mode(&mut self, mode: DiceMode) -> Result<()> {
299 set_once(&self.mode, mode, "mode")
300 }
301
302 fn code_hash(&mut self, code_hash: [u8; HASH_SIZE]) -> Result<()> {
303 set_once(&self.code_hash, code_hash, "code_hash")
304 }
305
306 fn authority_hash(&mut self, authority_hash: [u8; HASH_SIZE]) -> Result<()> {
307 set_once(&self.authority_hash, authority_hash, "authority_hash")
308 }
309
310 fn config_descriptor(&mut self, config_descriptor: Vec<u8>) -> Result<()> {
311 set_once(&self.config_descriptor, config_descriptor, "config_descriptor")
312 }
313
314 fn build(mut self) -> Result<DiceChainEntryPayload> {
315 let subject_public_key = take_value(&mut self.subject_public_key, "subject_public_key")?;
316 // If Mode is omitted, it should be treated as if it was NotConfigured, according to
317 // the Open Profile for DICE spec.
318 let mode = self.mode.take().unwrap_or(DiceMode::kDiceModeNotInitialized);
319 let code_hash = take_value(&mut self.code_hash, "code_hash")?;
320 let authority_hash = take_value(&mut self.authority_hash, "authority_hash")?;
321 let config_descriptor = take_value(&mut self.config_descriptor, "config_descriptor")?;
322 Ok(DiceChainEntryPayload {
323 subject_public_key,
324 mode,
325 code_hash,
326 authority_hash,
327 config_descriptor,
328 })
329 }
330}