Alice Wang | d3a9640 | 2023-11-24 15:37:39 +0000 | [diff] [blame] | 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 | //! This module contains functions related to DICE. |
| 16 | |
| 17 | use alloc::vec::Vec; |
Alice Wang | a95dae5 | 2023-12-06 09:30:22 +0000 | [diff] [blame^] | 18 | use ciborium::value::{Integer, Value}; |
Alice Wang | d3a9640 | 2023-11-24 15:37:39 +0000 | [diff] [blame] | 19 | use core::cell::OnceCell; |
| 20 | use core::result; |
| 21 | use coset::{ |
| 22 | self, iana, AsCborValue, CborSerializable, CoseError, CoseKey, CoseSign1, KeyOperation, |
| 23 | }; |
| 24 | use diced_open_dice::{DiceMode, HASH_SIZE}; |
| 25 | use log::error; |
Alice Wang | a95dae5 | 2023-12-06 09:30:22 +0000 | [diff] [blame^] | 26 | use service_vm_comm::{ |
| 27 | cbor_value_type, to_unexpected_item_error, value_to_bytes, RequestProcessingError, |
| 28 | }; |
Alice Wang | d3a9640 | 2023-11-24 15:37:39 +0000 | [diff] [blame] | 29 | |
| 30 | type Result<T> = result::Result<T, RequestProcessingError>; |
| 31 | |
| 32 | const CODE_HASH: i64 = -4670545; |
| 33 | const CONFIG_DESC: i64 = -4670548; |
| 34 | const AUTHORITY_HASH: i64 = -4670549; |
| 35 | const MODE: i64 = -4670551; |
| 36 | const 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)] |
| 46 | pub(crate) struct ClientVmDiceChain { |
| 47 | pub(crate) payloads: Vec<DiceChainEntryPayload>, |
| 48 | } |
| 49 | |
| 50 | impl 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. |
| 101 | pub(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 Wang | a95dae5 | 2023-12-06 09:30:22 +0000 | [diff] [blame^] | 106 | value_to_array(Value::from_slice(client_vm_dice_chain)?, "client_vm_dice_chain")?; |
Alice Wang | d3a9640 | 2023-11-24 15:37:39 +0000 | [diff] [blame] | 107 | let service_vm_dice_chain = |
Alice Wang | a95dae5 | 2023-12-06 09:30:22 +0000 | [diff] [blame^] | 108 | value_to_array(Value::from_slice(service_vm_dice_chain)?, "service_vm_dice_chain")?; |
Alice Wang | d3a9640 | 2023-11-24 15:37:39 +0000 | [diff] [blame] | 109 | 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)] |
| 137 | pub(crate) struct PublicKey(CoseKey); |
| 138 | |
| 139 | impl 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)] |
| 156 | pub(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 | |
| 171 | impl 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 Wang | a95dae5 | 2023-12-06 09:30:22 +0000 | [diff] [blame^] | 185 | let entries = value_to_map(Value::from_slice(&payload)?, "DiceChainEntryPayload")?; |
Alice Wang | d3a9640 | 2023-11-24 15:37:39 +0000 | [diff] [blame] | 186 | build_payload(entries) |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | fn build_payload(entries: Vec<(Value, Value)>) -> Result<DiceChainEntryPayload> { |
| 191 | let mut builder = PayloadBuilder::default(); |
| 192 | for (key, value) in entries.into_iter() { |
Alice Wang | a95dae5 | 2023-12-06 09:30:22 +0000 | [diff] [blame^] | 193 | let key: i64 = value_to_num(key, "DiceChainEntryPayload key")?; |
Alice Wang | d3a9640 | 2023-11-24 15:37:39 +0000 | [diff] [blame] | 194 | match key { |
| 195 | SUBJECT_PUBLIC_KEY => { |
Alice Wang | a95dae5 | 2023-12-06 09:30:22 +0000 | [diff] [blame^] | 196 | let subject_public_key = value_to_bytes(value, "subject_public_key")?; |
Alice Wang | d3a9640 | 2023-11-24 15:37:39 +0000 | [diff] [blame] | 197 | 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 Wang | a95dae5 | 2023-12-06 09:30:22 +0000 | [diff] [blame^] | 201 | CODE_HASH => { |
| 202 | let code_hash = value_to_byte_array(value, "DiceChainEntryPayload code_hash")?; |
| 203 | builder.code_hash(code_hash)?; |
Alice Wang | d3a9640 | 2023-11-24 15:37:39 +0000 | [diff] [blame] | 204 | } |
Alice Wang | a95dae5 | 2023-12-06 09:30:22 +0000 | [diff] [blame^] | 205 | 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 Wang | d3a9640 | 2023-11-24 15:37:39 +0000 | [diff] [blame] | 214 | _ => {} |
| 215 | } |
| 216 | } |
| 217 | builder.build() |
| 218 | } |
| 219 | |
Alice Wang | a95dae5 | 2023-12-06 09:30:22 +0000 | [diff] [blame^] | 220 | fn 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 Wang | d3a9640 | 2023-11-24 15:37:39 +0000 | [diff] [blame] | 222 | } |
| 223 | |
Alice Wang | a95dae5 | 2023-12-06 09:30:22 +0000 | [diff] [blame^] | 224 | fn 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 | |
| 228 | fn 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 | |
| 236 | fn 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 Wang | d3a9640 | 2023-11-24 15:37:39 +0000 | [diff] [blame] | 238 | error!("The provided value '{context}' is not an array of length {N}: {e:?}"); |
| 239 | RequestProcessingError::InternalError |
| 240 | }) |
| 241 | } |
| 242 | |
| 243 | fn 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)] |
| 271 | struct 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 | |
| 279 | fn 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 | |
| 286 | fn 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 | |
| 293 | impl 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 | } |