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 //! Test libsdice_policy_builder & libdice_policy. Uses `rdroidtest` attribute macro.
18 use ciborium::{cbor, Value};
19 use coset::{AsCborValue, CborSerializable, CoseKey, CoseSign1, Header, ProtectedHeader};
20 use dice_policy::{lookup_in_nested_container, Constraint, DicePolicy, NodeConstraints};
21 use dice_policy_builder::{
22 policy_for_dice_chain, ConstraintSpec, ConstraintType, MissingAction, TargetEntry,
23 WILDCARD_FULL_ARRAY,
24 };
25 use rdroidtest::rdroidtest;
26
27 const AUTHORITY_HASH: i64 = -4670549;
28 const CONFIG_DESC: i64 = -4670548;
29 const COMPONENT_NAME: i64 = -70002;
30 const COMPONENT_VERSION: i64 = -70003;
31 const SECURITY_VERSION: i64 = -70005;
32 const KEY_MODE: i64 = -4670551;
33 const EXPLICIT_KEY_DICE_CERT_CHAIN_VERSION: u64 = 1;
34 const COMPOS_CHAIN_SIZE_EXPLICIT_KEY: usize = 6;
35 const EXAMPLE_COMPONENT_NAME: &str = "example_component_name";
36
37 // Helper struct to encapsulate artifacts that are useful for unit tests.
38 struct TestArtifacts {
39 // A dice chain.
40 input_dice: Vec<u8>,
41 // A list of ConstraintSpec that can be applied on the input_dice to get a dice policy.
42 constraint_spec: Vec<ConstraintSpec>,
43 // The expected dice policy if above constraint_spec is applied to input_dice.
44 expected_dice_policy: DicePolicy,
45 // Another dice chain, which is almost same as the input_dice, but (roughly) imitates
46 // an 'updated' one, ie, some int entries are higher than corresponding entry
47 // in input_chain.
48 updated_input_dice: Vec<u8>,
49 }
50
51 impl TestArtifacts {
52 // Get an example instance of TestArtifacts. This uses a hard coded, hypothetical
53 // chain of certificates & a list of constraint_spec on this.
get_example() -> Self54 fn get_example() -> Self {
55 const EXAMPLE_NUM_1: i64 = 59765;
56 const EXAMPLE_NUM_2: i64 = 59766;
57 const EXAMPLE_STRING: &str = "testing_dice_policy";
58 const UNCONSTRAINED_STRING: &str = "unconstrained_string";
59 const ANOTHER_UNCONSTRAINED_STRING: &str = "another_unconstrained_string";
60 const CONSTRAINED_ARRAY: [&str; 4] = ["Array", "IN", "LAST", "DICE_CHAIN_ENTRY"];
61 let constrained_array = cbor!(CONSTRAINED_ARRAY).unwrap();
62
63 let rot_key = Value::Bytes(CoseKey::default().to_vec().unwrap());
64 let input_dice = Self::get_dice_chain_helper(
65 rot_key.clone(),
66 EXAMPLE_NUM_1,
67 EXAMPLE_STRING,
68 UNCONSTRAINED_STRING,
69 constrained_array.clone(),
70 );
71
72 // Now construct constraint_spec on the input dice, note this will use the keys
73 // which are also hardcoded within the get_dice_chain_helper.
74
75 let constraint_spec = vec![
76 ConstraintSpec::new(
77 ConstraintType::ExactMatch,
78 vec![1],
79 MissingAction::Fail,
80 TargetEntry::All,
81 ),
82 // Notice how key "2" is (deliberately) absent in ConstraintSpec
83 // so policy should not constrain it.
84 ConstraintSpec::new(
85 ConstraintType::GreaterOrEqual,
86 vec![3, 100],
87 MissingAction::Fail,
88 TargetEntry::All,
89 ),
90 ConstraintSpec::new(
91 ConstraintType::ExactMatch,
92 vec![4, WILDCARD_FULL_ARRAY],
93 MissingAction::Fail,
94 // Notice although 2 DiceChainEntries have the same component name, only the last
95 // one is expected to be constrained in the DicePolicy.
96 TargetEntry::ByName(EXAMPLE_COMPONENT_NAME.to_string()),
97 ),
98 ];
99 let expected_dice_policy = DicePolicy {
100 version: 1,
101 node_constraints_list: Box::new([
102 NodeConstraints(Box::new([Constraint::new(
103 // 1st Node -> version
104 ConstraintType::ExactMatch as u16,
105 vec![],
106 Value::from(EXPLICIT_KEY_DICE_CERT_CHAIN_VERSION),
107 )
108 .unwrap()])),
109 NodeConstraints(Box::new([Constraint::new(
110 // 2nd Node -> root key encoding
111 ConstraintType::ExactMatch as u16,
112 vec![],
113 rot_key.clone(),
114 )
115 .unwrap()])),
116 NodeConstraints(Box::new([
117 // 3rd Node -> DiceChainEntry 0
118 Constraint::new(
119 ConstraintType::ExactMatch as u16,
120 vec![1],
121 Value::Text(EXAMPLE_STRING.to_string()),
122 )
123 .unwrap(),
124 Constraint::new(
125 ConstraintType::GreaterOrEqual as u16,
126 vec![3, 100],
127 Value::from(EXAMPLE_NUM_1),
128 )
129 .unwrap(),
130 ])),
131 NodeConstraints(Box::new([
132 // 4rd Node -> DiceChainEntry 1 (Also 0th from last)
133 Constraint::new(
134 ConstraintType::ExactMatch as u16,
135 vec![1],
136 Value::Text(EXAMPLE_STRING.to_string()),
137 )
138 .unwrap(),
139 Constraint::new(
140 ConstraintType::GreaterOrEqual as u16,
141 vec![3, 100],
142 Value::from(EXAMPLE_NUM_1),
143 )
144 .unwrap(),
145 Constraint::new(
146 ConstraintType::ExactMatch as u16,
147 vec![4, 0],
148 Value::from(CONSTRAINED_ARRAY[0]),
149 )
150 .unwrap(),
151 Constraint::new(
152 ConstraintType::ExactMatch as u16,
153 vec![4, 1],
154 Value::from(CONSTRAINED_ARRAY[1]),
155 )
156 .unwrap(),
157 Constraint::new(
158 ConstraintType::ExactMatch as u16,
159 vec![4, 2],
160 Value::from(CONSTRAINED_ARRAY[2]),
161 )
162 .unwrap(),
163 Constraint::new(
164 ConstraintType::ExactMatch as u16,
165 vec![4, 3],
166 Value::from(CONSTRAINED_ARRAY[3]),
167 )
168 .unwrap(),
169 ])),
170 ]),
171 };
172
173 let updated_input_dice = Self::get_dice_chain_helper(
174 rot_key,
175 EXAMPLE_NUM_2,
176 EXAMPLE_STRING,
177 ANOTHER_UNCONSTRAINED_STRING,
178 constrained_array,
179 );
180 Self { input_dice, constraint_spec, expected_dice_policy, updated_input_dice }
181 }
182
183 // Helper method method to generate a dice chain with a given rot_key.
184 // Other arguments are ad-hoc values in the nested map. Callers use these to
185 // construct appropriate constrains in dice policies.
get_dice_chain_helper( rot_key: Value, version: i64, constrained_string: &str, unconstrained_string: &str, constrained_array: Value, ) -> Vec<u8>186 fn get_dice_chain_helper(
187 rot_key: Value,
188 version: i64,
189 constrained_string: &str,
190 unconstrained_string: &str,
191 constrained_array: Value,
192 ) -> Vec<u8> {
193 let nested_payload = cbor!({
194 100 => version
195 })
196 .unwrap();
197
198 let payload0 = cbor!({
199 1 => constrained_string,
200 2 => unconstrained_string,
201 3 => Value::Bytes(nested_payload.clone().to_vec().unwrap()),
202 CONFIG_DESC => {COMPONENT_NAME => EXAMPLE_COMPONENT_NAME}
203 })
204 .unwrap();
205 let payload0 = payload0.clone().to_vec().unwrap();
206 let dice_chain_entry0 = CoseSign1 {
207 protected: ProtectedHeader::default(),
208 unprotected: Header::default(),
209 payload: Some(payload0),
210 signature: b"ddef".to_vec(),
211 }
212 .to_cbor_value()
213 .unwrap();
214
215 let payload1 = cbor!({
216 1 => constrained_string,
217 2 => unconstrained_string,
218 3 => Value::Bytes(nested_payload.to_vec().unwrap()),
219 4 => constrained_array,
220 CONFIG_DESC => {COMPONENT_NAME => EXAMPLE_COMPONENT_NAME}
221 })
222 .unwrap();
223 let payload1 = payload1.clone().to_vec().unwrap();
224 let dice_chain_entry1 = CoseSign1 {
225 protected: ProtectedHeader::default(),
226 unprotected: Header::default(),
227 payload: Some(payload1),
228 signature: b"ddef".to_vec(),
229 }
230 .to_cbor_value()
231 .unwrap();
232 let input_dice = Value::Array(vec![
233 Value::from(EXPLICIT_KEY_DICE_CERT_CHAIN_VERSION),
234 rot_key,
235 dice_chain_entry0,
236 dice_chain_entry1,
237 ]);
238
239 input_dice.to_vec().unwrap()
240 }
241 }
242
243 #[rdroidtest]
policy_structure_check()244 fn policy_structure_check() {
245 let example = TestArtifacts::get_example();
246 let policy = policy_for_dice_chain(&example.input_dice, example.constraint_spec).unwrap();
247
248 // Assert policy is exactly as expected!
249 assert_eq!(policy, example.expected_dice_policy);
250 }
251
252 #[rdroidtest]
policy_enc_dec_test()253 fn policy_enc_dec_test() {
254 // An example dice policy
255 let dice_policy: DicePolicy = TestArtifacts::get_example().expected_dice_policy;
256 // Encode & then decode back!
257 let dice_policy_enc_dec =
258 DicePolicy::from_slice(&dice_policy.clone().to_vec().unwrap()).unwrap();
259 assert_eq!(dice_policy, dice_policy_enc_dec);
260 }
261
262 #[rdroidtest]
policy_matches_original_dice_chain()263 fn policy_matches_original_dice_chain() {
264 let example = TestArtifacts::get_example();
265 policy_for_dice_chain(&example.input_dice, example.constraint_spec)
266 .unwrap()
267 .matches_dice_chain(&example.input_dice)
268 .unwrap();
269 }
270
271 #[rdroidtest]
policy_matches_updated_dice_chain()272 fn policy_matches_updated_dice_chain() {
273 let example = TestArtifacts::get_example();
274 policy_for_dice_chain(&example.input_dice, example.constraint_spec)
275 .unwrap()
276 .matches_dice_chain(&example.updated_input_dice)
277 .unwrap();
278 }
279
280 #[rdroidtest]
policy_mismatch_downgraded_dice_chain()281 fn policy_mismatch_downgraded_dice_chain() {
282 let example = TestArtifacts::get_example();
283 assert!(
284 policy_for_dice_chain(&example.updated_input_dice, example.constraint_spec)
285 .unwrap()
286 .matches_dice_chain(&example.input_dice)
287 .is_err(),
288 "The (downgraded) dice chain matched the policy constructed out of the 'updated'\
289 dice chain!!"
290 );
291 }
292
293 #[rdroidtest]
policy_constructional_required_constraint()294 fn policy_constructional_required_constraint() {
295 let mut example = TestArtifacts::get_example();
296 // The constraint_path [9,8,7] is not present in example.input_dice
297 example.constraint_spec.push(ConstraintSpec::new(
298 ConstraintType::ExactMatch,
299 vec![9, 8, 7],
300 MissingAction::Fail,
301 TargetEntry::All,
302 ));
303 assert!(
304 policy_for_dice_chain(&example.input_dice, example.constraint_spec).is_err(),
305 "DicePolicy creation should've failed on Required constraint"
306 );
307 }
308
309 #[rdroidtest]
policy_constructional_optional_constraint()310 fn policy_constructional_optional_constraint() {
311 let mut example = TestArtifacts::get_example();
312 // The constraint_path [9,8,7] is not present in example.input_dice
313 example.constraint_spec.push(ConstraintSpec::new(
314 ConstraintType::ExactMatch,
315 vec![9, 8, 7],
316 MissingAction::Ignore,
317 TargetEntry::All,
318 ));
319 // However for optional constraint, policy construction should succeed!
320 _ = policy_for_dice_chain(&example.input_dice, example.constraint_spec).unwrap()
321 }
322
323 #[rdroidtest]
policy_dice_size_is_same()324 fn policy_dice_size_is_same() {
325 // This is the number of certs in compos bcc (including the first ROT)
326 // To analyze a bcc use hwtrust tool from /tools/security/remote_provisioning/hwtrust
327 // `hwtrust --verbose dice-chain [path]/composbcc`
328 let input_dice = include_bytes!("./testdata/compos_chain_explicit");
329 let constraint_spec = vec![
330 ConstraintSpec::new(
331 ConstraintType::ExactMatch,
332 vec![AUTHORITY_HASH],
333 MissingAction::Fail,
334 TargetEntry::All,
335 ),
336 ConstraintSpec::new(
337 ConstraintType::ExactMatch,
338 vec![KEY_MODE],
339 MissingAction::Fail,
340 TargetEntry::All,
341 ),
342 ConstraintSpec::new(
343 ConstraintType::ExactMatch,
344 vec![CONFIG_DESC, COMPONENT_NAME],
345 MissingAction::Fail,
346 TargetEntry::All,
347 ),
348 ConstraintSpec::new(
349 ConstraintType::GreaterOrEqual,
350 vec![CONFIG_DESC, COMPONENT_VERSION],
351 MissingAction::Ignore,
352 TargetEntry::All,
353 ),
354 ConstraintSpec::new(
355 ConstraintType::GreaterOrEqual,
356 vec![CONFIG_DESC, SECURITY_VERSION],
357 MissingAction::Ignore,
358 TargetEntry::All,
359 ),
360 ];
361 let policy = policy_for_dice_chain(input_dice, constraint_spec).unwrap();
362 assert_eq!(policy.node_constraints_list.len(), COMPOS_CHAIN_SIZE_EXPLICIT_KEY);
363 // Check that original dice chain matches the policy.
364 policy.matches_dice_chain(input_dice).unwrap();
365 }
366
367 #[rdroidtest]
policy_matcher_builder_in_sync()368 fn policy_matcher_builder_in_sync() {
369 // DicePolicy builder & matcher are closely coupled.
370 assert_eq!(
371 dice_policy::DICE_POLICY_VERSION,
372 dice_policy_builder::SUPPORTED_DICE_POLICY_VERSION
373 );
374 }
375 #[rdroidtest]
lookup_in_nested_container_test()376 fn lookup_in_nested_container_test() {
377 const TARGET: &str = "TARGET";
378 let nested_container = cbor!({ // MAP
379 1 => 5,
380 2 => cbor!([ // ARRAY
381 "Index0",
382 "Index1",
383 cbor!({ // Map
384 1 => 5,
385 2 => cbor!([ // ARRAY
386 "Index0",
387 "Index1",
388 TARGET,
389 ]).unwrap(),
390 }).unwrap(),
391 ]).unwrap(),
392 })
393 .unwrap();
394 let path_present = vec![2, 2, 2, 2];
395 let path_missing1 = vec![2, 2, 2, 3];
396 let path_missing2 = vec![2, 2, 3];
397 assert_eq!(
398 lookup_in_nested_container(&nested_container, &path_present).unwrap(),
399 Some(Value::from(TARGET))
400 );
401 assert_eq!(lookup_in_nested_container(&nested_container, &path_missing1).unwrap(), None);
402 assert_eq!(lookup_in_nested_container(&nested_container, &path_missing2).unwrap(), None);
403 }
404
405 rdroidtest::test_main!();
406