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