Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2023 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | //! A “DICE policy” is a format for setting constraints on a DICE chain. A DICE chain policy |
| 18 | //! verifier takes a policy and a DICE chain, and returns a boolean indicating whether the |
| 19 | //! DICE chain meets the constraints set out on a policy. |
| 20 | //! |
| 21 | //! This forms the foundation of Dice Policy aware Authentication (DPA-Auth), where the server |
| 22 | //! authenticates a client by comparing its dice chain against a set policy. |
| 23 | //! |
| 24 | //! Another use is "sealing", where clients can use an appropriately constructed dice policy to |
| 25 | //! seal a secret. Unsealing is only permitted if dice chain of the component requesting unsealing |
| 26 | //! complies with the policy. |
| 27 | //! |
| 28 | //! A typical policy will assert things like: |
| 29 | //! # DK_pub must have this value |
| 30 | //! # The DICE chain must be exactly five certificates long |
| 31 | //! # authorityHash in the third certificate must have this value |
| 32 | //! securityVersion in the fourth certificate must be an integer greater than 8 |
| 33 | //! |
| 34 | //! These constraints used to express policy are (for now) limited to following 2 types: |
| 35 | //! 1. Exact Match: useful for enforcing rules like authority hash should be exactly equal. |
| 36 | //! 2. Greater than or equal to: Useful for setting policies that seal |
| 37 | //! Anti-rollback protected entities (should be accessible to versions >= present). |
| 38 | //! |
| 39 | //! Dice Policy CDDL: |
| 40 | //! |
| 41 | //! dicePolicy = [ |
| 42 | //! 1, ; dice policy version |
| 43 | //! + nodeConstraintList ; for each entry in dice chain |
| 44 | //! ] |
| 45 | //! |
| 46 | //! nodeConstraintList = [ |
| 47 | //! * nodeConstraint |
| 48 | //! ] |
| 49 | //! |
| 50 | //! ; We may add a hashConstraint item later |
| 51 | //! nodeConstraint = exactMatchConstraint / geConstraint |
| 52 | //! |
| 53 | //! exactMatchConstraint = [1, keySpec, value] |
| 54 | //! geConstraint = [2, keySpec, int] |
| 55 | //! |
| 56 | //! keySpec = [value+] |
| 57 | //! |
| 58 | //! value = bool / int / tstr / bstr |
| 59 | |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 60 | use anyhow::{anyhow, bail, ensure, Context, Result}; |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 61 | use ciborium::Value; |
| 62 | use coset::{AsCborValue, CoseSign1}; |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 63 | use num_derive::FromPrimitive; |
| 64 | use num_traits::FromPrimitive; |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 65 | use std::borrow::Cow; |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 66 | use std::iter::zip; |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 67 | |
| 68 | const DICE_POLICY_VERSION: u64 = 1; |
| 69 | |
| 70 | /// Constraint Types supported in Dice policy. |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 71 | #[repr(u16)] |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 72 | #[non_exhaustive] |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 73 | #[derive(Clone, Copy, Debug, FromPrimitive, PartialEq)] |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 74 | pub enum ConstraintType { |
| 75 | /// Enforce exact match criteria, indicating the policy should match |
| 76 | /// if the dice chain has exact same specified values. |
| 77 | ExactMatch = 1, |
| 78 | /// Enforce Greater than or equal to criteria. When applied on security_version, this |
| 79 | /// can be useful to set policy that matches dice chains with same or upgraded images. |
| 80 | GreaterOrEqual = 2, |
| 81 | } |
| 82 | |
| 83 | /// ConstraintSpec is used to specify which constraint type to apply and |
| 84 | /// on which all entries in a dice node. |
| 85 | /// See documentation of `from_dice_chain()` for examples. |
| 86 | pub struct ConstraintSpec { |
| 87 | constraint_type: ConstraintType, |
| 88 | // path is essentially a list of label/int. |
| 89 | // It identifies which entry (in a dice node) to be applying constraints on. |
| 90 | path: Vec<i64>, |
| 91 | } |
| 92 | |
| 93 | impl ConstraintSpec { |
| 94 | /// Construct the ConstraintSpec. |
| 95 | pub fn new(constraint_type: ConstraintType, path: Vec<i64>) -> Result<Self> { |
| 96 | Ok(ConstraintSpec { constraint_type, path }) |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | // TODO(b/291238565): Restrict (nested_)key & value type to (bool/int/tstr/bstr). |
| 101 | // and maybe convert it into struct. |
| 102 | /// Each constraint (on a dice node) is a tuple: (ConstraintType, constraint_path, value) |
| 103 | #[derive(Debug, PartialEq)] |
| 104 | struct Constraint(u16, Vec<i64>, Value); |
| 105 | |
| 106 | /// List of all constraints on a dice node. |
| 107 | #[derive(Debug, PartialEq)] |
| 108 | struct NodeConstraints(Box<[Constraint]>); |
| 109 | |
| 110 | /// Module for working with dice policy. |
| 111 | #[derive(Debug, PartialEq)] |
| 112 | pub struct DicePolicy { |
| 113 | version: u64, |
| 114 | node_constraints_list: Box<[NodeConstraints]>, // Constraint on each entry in dice chain. |
| 115 | } |
| 116 | |
| 117 | impl DicePolicy { |
| 118 | /// Construct a dice policy from a given dice chain. |
| 119 | /// This can be used by clients to construct a policy to seal secrets. |
| 120 | /// Constraints on all but first dice node is applied using constraint_spec argument. |
| 121 | /// For the first node (which is a ROT key), the constraint is ExactMatch of the whole node. |
| 122 | /// |
| 123 | /// # Arguments |
| 124 | /// `dice_chain`: The serialized CBOR encoded Dice chain, adhering to Android Profile for DICE. |
| 125 | /// https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md |
| 126 | /// |
| 127 | /// `constraint_spec`: List of constraints to be applied on dice node. |
| 128 | /// Each constraint is a ConstraintSpec object. |
| 129 | /// |
| 130 | /// Note: Dice node is treated as a nested map (& so the lookup is done in that fashion). |
| 131 | /// |
| 132 | /// Examples of constraint_spec: |
| 133 | /// 1. For exact_match on auth_hash & greater_or_equal on security_version |
| 134 | /// constraint_spec =[ |
| 135 | /// (ConstraintType::ExactMatch, vec![AUTHORITY_HASH]), |
| 136 | /// (ConstraintType::GreaterOrEqual, vec![CONFIG_DESC, COMPONENT_NAME]), |
| 137 | /// ]; |
| 138 | /// |
| 139 | /// 2. For hypothetical (and highly simplified) dice chain: |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 140 | /// |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 141 | /// [ROT_KEY, [{1 : 'a', 2 : {200 : 5, 201 : 'b'}}]] |
| 142 | /// The following can be used |
| 143 | /// constraint_spec =[ |
| 144 | /// ConstraintSpec(ConstraintType::ExactMatch, vec![1]), // exact_matches value 'a' |
| 145 | /// ConstraintSpec(ConstraintType::GreaterOrEqual, vec![2, 200]),// matches any value >= 5 |
| 146 | /// ]; |
| 147 | pub fn from_dice_chain(dice_chain: &[u8], constraint_spec: &[ConstraintSpec]) -> Result<Self> { |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 148 | let dice_chain = deserialize_dice_chain(dice_chain)?; |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 149 | let mut constraints_list: Vec<NodeConstraints> = Vec::with_capacity(dice_chain.len()); |
| 150 | let mut it = dice_chain.into_iter(); |
| 151 | |
| 152 | constraints_list.push(NodeConstraints(Box::new([Constraint( |
| 153 | ConstraintType::ExactMatch as u16, |
| 154 | Vec::new(), |
| 155 | it.next().unwrap(), |
| 156 | )]))); |
| 157 | |
| 158 | for (n, value) in it.enumerate() { |
| 159 | let entry = cbor_value_from_cose_sign(value) |
| 160 | .with_context(|| format!("Unable to get Cose payload at: {}", n))?; |
| 161 | constraints_list.push(payload_to_constraints(entry, constraint_spec)?); |
| 162 | } |
| 163 | |
| 164 | Ok(DicePolicy { |
| 165 | version: DICE_POLICY_VERSION, |
| 166 | node_constraints_list: constraints_list.into_boxed_slice(), |
| 167 | }) |
| 168 | } |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 169 | |
| 170 | /// Dice chain policy verifier - Compare the input dice chain against this Dice policy. |
| 171 | /// The method returns Ok() if the dice chain meets the constraints set in Dice policy, |
| 172 | /// otherwise returns error in case of mismatch. |
| 173 | /// TODO(b/291238565) Create a separate error module for DicePolicy mismatches. |
| 174 | pub fn matches_dice_chain(&self, dice_chain: &[u8]) -> Result<()> { |
| 175 | let dice_chain = deserialize_dice_chain(dice_chain)?; |
| 176 | ensure!( |
| 177 | dice_chain.len() == self.node_constraints_list.len(), |
| 178 | format!( |
| 179 | "Dice chain size({}) does not match policy({})", |
| 180 | dice_chain.len(), |
| 181 | self.node_constraints_list.len() |
| 182 | ) |
| 183 | ); |
| 184 | |
| 185 | for (n, (dice_node, node_constraints)) in |
| 186 | zip(dice_chain, self.node_constraints_list.iter()).enumerate() |
| 187 | { |
| 188 | let dice_node_payload = if n == 0 { |
| 189 | dice_node |
| 190 | } else { |
| 191 | cbor_value_from_cose_sign(dice_node) |
| 192 | .with_context(|| format!("Unable to get Cose payload at: {}", n))? |
| 193 | }; |
| 194 | check_constraints_on_node(node_constraints, &dice_node_payload) |
| 195 | .context(format!("Mismatch found at {}", n))?; |
| 196 | } |
| 197 | Ok(()) |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | fn check_constraints_on_node(node_constraints: &NodeConstraints, dice_node: &Value) -> Result<()> { |
| 202 | for constraint in node_constraints.0.iter() { |
| 203 | check_constraint_on_node(constraint, dice_node)?; |
| 204 | } |
| 205 | Ok(()) |
| 206 | } |
| 207 | |
| 208 | fn check_constraint_on_node(constraint: &Constraint, dice_node: &Value) -> Result<()> { |
| 209 | let Constraint(cons_type, path, value_in_constraint) = constraint; |
| 210 | let value_in_node = lookup_value_in_nested_map(dice_node, path)?; |
| 211 | match ConstraintType::from_u16(*cons_type).ok_or(anyhow!("Unexpected Constraint type"))? { |
| 212 | ConstraintType::ExactMatch => ensure!(value_in_node == *value_in_constraint), |
| 213 | ConstraintType::GreaterOrEqual => { |
| 214 | let value_in_node = value_in_node |
| 215 | .as_integer() |
| 216 | .ok_or(anyhow!("Mismatch type: expected a cbor integer"))?; |
| 217 | let value_min = value_in_constraint |
| 218 | .as_integer() |
| 219 | .ok_or(anyhow!("Mismatch type: expected a cbor integer"))?; |
| 220 | ensure!(value_in_node >= value_min); |
| 221 | } |
| 222 | }; |
| 223 | Ok(()) |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 224 | } |
| 225 | |
| 226 | // Take the payload of a dice node & construct the constraints on it. |
| 227 | fn payload_to_constraints( |
| 228 | payload: Value, |
| 229 | constraint_spec: &[ConstraintSpec], |
| 230 | ) -> Result<NodeConstraints> { |
Shikha Panwar | 1d01686 | 2023-10-20 18:36:29 +0000 | [diff] [blame] | 231 | let mut node_constraints: Vec<Constraint> = Vec::with_capacity(constraint_spec.len()); |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 232 | for constraint_item in constraint_spec { |
| 233 | let constraint_path = constraint_item.path.to_vec(); |
| 234 | if constraint_path.is_empty() { |
| 235 | bail!("Expected non-empty key spec"); |
| 236 | } |
| 237 | let val = lookup_value_in_nested_map(&payload, &constraint_path) |
| 238 | .context(format!("Value not found for constraint_path {:?}", constraint_path))?; |
| 239 | let constraint = Constraint(constraint_item.constraint_type as u16, constraint_path, val); |
| 240 | node_constraints.push(constraint); |
| 241 | } |
| 242 | Ok(NodeConstraints(node_constraints.into_boxed_slice())) |
| 243 | } |
| 244 | |
| 245 | // Lookup value corresponding to constraint path in nested map. |
| 246 | // This function recursively calls itself. |
| 247 | // The depth of recursion is limited by the size of constraint_path. |
| 248 | fn lookup_value_in_nested_map(cbor_map: &Value, constraint_path: &[i64]) -> Result<Value> { |
| 249 | if constraint_path.is_empty() { |
| 250 | return Ok(cbor_map.clone()); |
| 251 | } |
| 252 | let explicit_map = get_map_from_value(cbor_map)?; |
| 253 | let val = lookup_value_in_map(&explicit_map, constraint_path[0]) |
| 254 | .ok_or(anyhow!("Value not found for constraint key: {:?}", constraint_path[0]))?; |
| 255 | lookup_value_in_nested_map(val, &constraint_path[1..]) |
| 256 | } |
| 257 | |
| 258 | fn get_map_from_value(cbor_map: &Value) -> Result<Cow<Vec<(Value, Value)>>> { |
| 259 | match cbor_map { |
| 260 | Value::Bytes(b) => value_from_bytes(b)? |
| 261 | .into_map() |
| 262 | .map(Cow::Owned) |
| 263 | .map_err(|e| anyhow!("Expected a cbor map: {:?}", e)), |
| 264 | Value::Map(map) => Ok(Cow::Borrowed(map)), |
| 265 | _ => bail!("/Expected a cbor map {:?}", cbor_map), |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | fn lookup_value_in_map(map: &[(Value, Value)], key: i64) -> Option<&Value> { |
| 270 | let key = Value::Integer(key.into()); |
| 271 | for (k, v) in map.iter() { |
| 272 | if k == &key { |
| 273 | return Some(v); |
| 274 | } |
| 275 | } |
| 276 | None |
| 277 | } |
| 278 | |
| 279 | /// Extract the payload from the COSE Sign |
| 280 | fn cbor_value_from_cose_sign(cbor: Value) -> Result<Value> { |
| 281 | let sign1 = |
| 282 | CoseSign1::from_cbor_value(cbor).map_err(|e| anyhow!("Error extracting CoseKey: {}", e))?; |
| 283 | match sign1.payload { |
| 284 | None => bail!("Missing payload"), |
| 285 | Some(payload) => Ok(value_from_bytes(&payload)?), |
| 286 | } |
| 287 | } |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 288 | fn deserialize_dice_chain(dice_chain_bytes: &[u8]) -> Result<Vec<Value>> { |
| 289 | // TODO(b/298217847): Check if the given dice chain adheres to Explicit-key DiceCertChain |
| 290 | // format and if not, convert it. |
| 291 | let dice_chain = |
| 292 | value_from_bytes(dice_chain_bytes).context("Unable to decode top-level CBOR")?; |
| 293 | let dice_chain = match dice_chain { |
| 294 | Value::Array(array) if array.len() >= 2 => array, |
| 295 | _ => bail!("Expected an array of at least length 2, found: {:?}", dice_chain), |
| 296 | }; |
| 297 | Ok(dice_chain) |
| 298 | } |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 299 | |
| 300 | /// Decodes the provided binary CBOR-encoded value and returns a |
| 301 | /// ciborium::Value struct wrapped in Result. |
| 302 | fn value_from_bytes(mut bytes: &[u8]) -> Result<Value> { |
| 303 | let value = ciborium::de::from_reader(&mut bytes)?; |
| 304 | // Ciborium tries to read one Value, & doesn't care if there is trailing data after it. We do. |
| 305 | if !bytes.is_empty() { |
| 306 | bail!("Unexpected trailing data while converting to CBOR value"); |
| 307 | } |
| 308 | Ok(value) |
| 309 | } |
| 310 | |
| 311 | #[cfg(test)] |
| 312 | rdroidtest::test_main!(); |
| 313 | |
| 314 | #[cfg(test)] |
| 315 | mod tests { |
| 316 | use super::*; |
| 317 | use ciborium::cbor; |
| 318 | use coset::{CoseKey, Header, ProtectedHeader}; |
| 319 | use rdroidtest::test; |
| 320 | |
| 321 | const AUTHORITY_HASH: i64 = -4670549; |
| 322 | const CONFIG_DESC: i64 = -4670548; |
| 323 | const COMPONENT_NAME: i64 = -70002; |
| 324 | const KEY_MODE: i64 = -4670551; |
| 325 | |
Shikha Panwar | 1d01686 | 2023-10-20 18:36:29 +0000 | [diff] [blame] | 326 | // Helper struct to encapsulate artifacts that are useful for unit tests. |
| 327 | struct TestArtifacts { |
| 328 | // A dice chain. |
| 329 | input_dice: Vec<u8>, |
| 330 | // A list of ConstraintSpec that can be applied on the input_dice to get a dice policy. |
| 331 | constraint_spec: Vec<ConstraintSpec>, |
| 332 | // The expected dice policy if above constraint_spec is applied to input_dice. |
| 333 | expected_dice_policy: DicePolicy, |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 334 | // Another dice chain, which is almost same as the input_dice, but (roughly) imitates |
| 335 | // an 'updated' one, ie, some int entries are higher than corresponding entry |
| 336 | // in input_chain. |
| 337 | updated_input_dice: Vec<u8>, |
Shikha Panwar | 1d01686 | 2023-10-20 18:36:29 +0000 | [diff] [blame] | 338 | } |
| 339 | |
| 340 | impl TestArtifacts { |
| 341 | // Get an example instance of TestArtifacts. This uses a hard coded, hypothetical |
| 342 | // chain of certificates & a list of constraint_spec on this. |
| 343 | fn get_example() -> Self { |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 344 | const EXAMPLE_NUM_1: i64 = 59765; |
| 345 | const EXAMPLE_NUM_2: i64 = 59766; |
Shikha Panwar | 1d01686 | 2023-10-20 18:36:29 +0000 | [diff] [blame] | 346 | const EXAMPLE_STRING: &str = "testing_dice_policy"; |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 347 | const UNCONSTRAINED_STRING: &str = "unconstrained_string"; |
| 348 | const ANOTHER_UNCONSTRAINED_STRING: &str = "another_unconstrained_string"; |
Shikha Panwar | 1d01686 | 2023-10-20 18:36:29 +0000 | [diff] [blame] | 349 | |
| 350 | let rot_key = CoseKey::default().to_cbor_value().unwrap(); |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 351 | let input_dice = Self::get_dice_chain_helper( |
| 352 | rot_key.clone(), |
| 353 | EXAMPLE_NUM_1, |
| 354 | EXAMPLE_STRING, |
| 355 | UNCONSTRAINED_STRING, |
| 356 | ); |
Shikha Panwar | 1d01686 | 2023-10-20 18:36:29 +0000 | [diff] [blame] | 357 | |
| 358 | // Now construct constraint_spec on the input dice, note this will use the keys |
| 359 | // which are also hardcoded within the get_dice_chain_helper. |
| 360 | |
| 361 | let constraint_spec = vec![ |
| 362 | ConstraintSpec::new(ConstraintType::ExactMatch, vec![1]).unwrap(), |
| 363 | // Notice how key "2" is (deliberately) absent in ConstraintSpec |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 364 | // so policy should not constrain it. |
Shikha Panwar | 1d01686 | 2023-10-20 18:36:29 +0000 | [diff] [blame] | 365 | ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![3, 100]).unwrap(), |
| 366 | ]; |
| 367 | let expected_dice_policy = DicePolicy { |
| 368 | version: 1, |
| 369 | node_constraints_list: Box::new([ |
| 370 | NodeConstraints(Box::new([Constraint( |
| 371 | ConstraintType::ExactMatch as u16, |
| 372 | vec![], |
| 373 | rot_key.clone(), |
| 374 | )])), |
| 375 | NodeConstraints(Box::new([ |
| 376 | Constraint( |
| 377 | ConstraintType::ExactMatch as u16, |
| 378 | vec![1], |
| 379 | Value::Text(EXAMPLE_STRING.to_string()), |
| 380 | ), |
| 381 | Constraint( |
| 382 | ConstraintType::GreaterOrEqual as u16, |
| 383 | vec![3, 100], |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 384 | Value::from(EXAMPLE_NUM_1), |
Shikha Panwar | 1d01686 | 2023-10-20 18:36:29 +0000 | [diff] [blame] | 385 | ), |
| 386 | ])), |
| 387 | ]), |
| 388 | }; |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 389 | |
| 390 | let updated_input_dice = Self::get_dice_chain_helper( |
| 391 | rot_key.clone(), |
| 392 | EXAMPLE_NUM_2, |
| 393 | EXAMPLE_STRING, |
| 394 | ANOTHER_UNCONSTRAINED_STRING, |
| 395 | ); |
| 396 | Self { input_dice, constraint_spec, expected_dice_policy, updated_input_dice } |
| 397 | } |
| 398 | |
| 399 | // Helper method method to generate a dice chain with a given rot_key. |
| 400 | // Other arguments are ad-hoc values in the nested map. Callers use these to |
| 401 | // construct appropriate constrains in dice policies. |
| 402 | fn get_dice_chain_helper( |
| 403 | rot_key: Value, |
| 404 | version: i64, |
| 405 | constrained_string: &str, |
| 406 | unconstrained_string: &str, |
| 407 | ) -> Vec<u8> { |
| 408 | let nested_payload = cbor!({ |
| 409 | 100 => version |
| 410 | }) |
| 411 | .unwrap(); |
| 412 | |
| 413 | let payload = cbor!({ |
| 414 | 1 => constrained_string, |
| 415 | 2 => unconstrained_string, |
| 416 | 3 => Value::Bytes(value_to_bytes(&nested_payload).unwrap()), |
| 417 | }) |
| 418 | .unwrap(); |
| 419 | let payload = value_to_bytes(&payload).unwrap(); |
| 420 | let dice_node = CoseSign1 { |
| 421 | protected: ProtectedHeader::default(), |
| 422 | unprotected: Header::default(), |
| 423 | payload: Some(payload), |
| 424 | signature: b"ddef".to_vec(), |
| 425 | } |
| 426 | .to_cbor_value() |
| 427 | .unwrap(); |
| 428 | let input_dice = Value::Array([rot_key.clone(), dice_node].to_vec()); |
| 429 | |
| 430 | value_to_bytes(&input_dice).unwrap() |
Shikha Panwar | 1d01686 | 2023-10-20 18:36:29 +0000 | [diff] [blame] | 431 | } |
| 432 | } |
| 433 | |
| 434 | test!(policy_structure_check); |
| 435 | fn policy_structure_check() { |
| 436 | let example = TestArtifacts::get_example(); |
| 437 | let policy = |
| 438 | DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec).unwrap(); |
| 439 | |
| 440 | // Assert policy is exactly as expected! |
| 441 | assert_eq!(policy, example.expected_dice_policy); |
| 442 | } |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 443 | |
Shikha Panwar | efe261f | 2023-10-20 20:46:05 +0000 | [diff] [blame^] | 444 | test!(policy_matches_original_dice_chain); |
| 445 | fn policy_matches_original_dice_chain() { |
| 446 | let example = TestArtifacts::get_example(); |
| 447 | assert!( |
| 448 | DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec) |
| 449 | .unwrap() |
| 450 | .matches_dice_chain(&example.input_dice) |
| 451 | .is_ok(), |
| 452 | "The dice chain did not match the policy constructed out of it!" |
| 453 | ); |
| 454 | } |
| 455 | |
| 456 | test!(policy_matches_updated_dice_chain); |
| 457 | fn policy_matches_updated_dice_chain() { |
| 458 | let example = TestArtifacts::get_example(); |
| 459 | assert!( |
| 460 | DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec) |
| 461 | .unwrap() |
| 462 | .matches_dice_chain(&example.updated_input_dice) |
| 463 | .is_ok(), |
| 464 | "The updated dice chain did not match the original policy!" |
| 465 | ); |
| 466 | } |
| 467 | |
| 468 | test!(policy_mismatch_downgraded_dice_chain); |
| 469 | fn policy_mismatch_downgraded_dice_chain() { |
| 470 | let example = TestArtifacts::get_example(); |
| 471 | assert!( |
| 472 | DicePolicy::from_dice_chain(&example.updated_input_dice, &example.constraint_spec) |
| 473 | .unwrap() |
| 474 | .matches_dice_chain(&example.input_dice) |
| 475 | .is_err(), |
| 476 | "The (downgraded) dice chain matched the policy constructed out of the 'updated'\ |
| 477 | dice chain!!" |
| 478 | ); |
| 479 | } |
| 480 | |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 481 | test!(policy_dice_size_is_same); |
| 482 | fn policy_dice_size_is_same() { |
Shikha Panwar | 1d01686 | 2023-10-20 18:36:29 +0000 | [diff] [blame] | 483 | // This is the number of certs in compos bcc (including the first ROT) |
| 484 | // To analyze a bcc use hwtrust tool from /tools/security/remote_provisioning/hwtrust |
| 485 | // `hwtrust --verbose dice-chain [path]/composbcc` |
| 486 | let compos_dice_chain_size: usize = 5; |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 487 | let input_dice = include_bytes!("../testdata/composbcc"); |
| 488 | let constraint_spec = [ |
| 489 | ConstraintSpec::new(ConstraintType::ExactMatch, vec![AUTHORITY_HASH]).unwrap(), |
| 490 | ConstraintSpec::new(ConstraintType::ExactMatch, vec![KEY_MODE]).unwrap(), |
| 491 | ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![CONFIG_DESC, COMPONENT_NAME]) |
| 492 | .unwrap(), |
| 493 | ]; |
| 494 | let policy = DicePolicy::from_dice_chain(input_dice, &constraint_spec).unwrap(); |
Shikha Panwar | 1d01686 | 2023-10-20 18:36:29 +0000 | [diff] [blame] | 495 | assert_eq!(policy.node_constraints_list.len(), compos_dice_chain_size); |
Shikha Panwar | 71b4f83 | 2023-08-21 17:43:09 +0000 | [diff] [blame] | 496 | } |
| 497 | |
| 498 | /// Encodes a ciborium::Value into bytes. |
| 499 | fn value_to_bytes(value: &Value) -> Result<Vec<u8>> { |
| 500 | let mut bytes: Vec<u8> = Vec::new(); |
| 501 | ciborium::ser::into_writer(&value, &mut bytes)?; |
| 502 | Ok(bytes) |
| 503 | } |
| 504 | } |