xref: /aosp_15_r20/system/secretkeeper/dice_policy/tests/test.rs (revision 3f8e9d82f4020c68ad19a99fc5fdc1fc90b79379)
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