Dice Policy: Add dice chain matching functionality

This method compares the dice policy against an input dice chain,
returning successfully if the dice chain matches the policy or returning
error in case of mismatch.

We also 3 unit tests to check that a policy:
1 Matches the original dice_chain that it was constructed from.
2 Matches the 'updated' chain.
3 Mismatches the 'downgraded' chain.

Note: The input chain in unit test is highly simplified & hypothetical.
The 'update' means one of the Integer (contrained by 'GreaterOrEqual')
increases in the updated chain & downgraded means the opposite & is for
the purpose of unit testing the constraints only.

Test: atest libdice_policy.test
Bug: 291238565
Change-Id: I575eb96e2356661e5f16e7a9dd3c860a287ad08b
diff --git a/secretkeeper/dice_policy/Android.bp b/secretkeeper/dice_policy/Android.bp
index a7ac5b9..4f1e8b6 100644
--- a/secretkeeper/dice_policy/Android.bp
+++ b/secretkeeper/dice_policy/Android.bp
@@ -13,7 +13,9 @@
         "libanyhow",
         "libciborium",
         "libcoset",
+        "libnum_traits",
     ],
+    proc_macros: ["libnum_derive"],
 }
 
 rust_library {
diff --git a/secretkeeper/dice_policy/src/lib.rs b/secretkeeper/dice_policy/src/lib.rs
index 327b8a4..2e91305 100644
--- a/secretkeeper/dice_policy/src/lib.rs
+++ b/secretkeeper/dice_policy/src/lib.rs
@@ -57,16 +57,20 @@
 //!
 //! value = bool / int / tstr / bstr
 
-use anyhow::{anyhow, bail, Context, Result};
+use anyhow::{anyhow, bail, ensure, Context, Result};
 use ciborium::Value;
 use coset::{AsCborValue, CoseSign1};
+use num_derive::FromPrimitive;
+use num_traits::FromPrimitive;
 use std::borrow::Cow;
+use std::iter::zip;
 
 const DICE_POLICY_VERSION: u64 = 1;
 
 /// Constraint Types supported in Dice policy.
+#[repr(u16)]
 #[non_exhaustive]
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Clone, Copy, Debug, FromPrimitive, PartialEq)]
 pub enum ConstraintType {
     /// Enforce exact match criteria, indicating the policy should match
     /// if the dice chain has exact same specified values.
@@ -133,6 +137,7 @@
     ///    ];
     ///
     /// 2. For hypothetical (and highly simplified) dice chain:
+    ///
     ///    [ROT_KEY, [{1 : 'a', 2 : {200 : 5, 201 : 'b'}}]]
     ///    The following can be used
     ///    constraint_spec =[
@@ -140,13 +145,7 @@
     ///     ConstraintSpec(ConstraintType::GreaterOrEqual, vec![2, 200]),// matches any value >= 5
     ///    ];
     pub fn from_dice_chain(dice_chain: &[u8], constraint_spec: &[ConstraintSpec]) -> Result<Self> {
-        // TODO(b/298217847): Check if the given dice chain adheres to Explicit-key DiceCertChain
-        // format and if not, convert it before policy construction.
-        let dice_chain = value_from_bytes(dice_chain).context("Unable to decode top-level CBOR")?;
-        let dice_chain = match dice_chain {
-            Value::Array(array) if array.len() >= 2 => array,
-            _ => bail!("Expected an array of at least length 2, found: {:?}", dice_chain),
-        };
+        let dice_chain = deserialize_dice_chain(dice_chain)?;
         let mut constraints_list: Vec<NodeConstraints> = Vec::with_capacity(dice_chain.len());
         let mut it = dice_chain.into_iter();
 
@@ -167,6 +166,61 @@
             node_constraints_list: constraints_list.into_boxed_slice(),
         })
     }
+
+    /// Dice chain policy verifier - Compare the input dice chain against this Dice policy.
+    /// The method returns Ok() if the dice chain meets the constraints set in Dice policy,
+    /// otherwise returns error in case of mismatch.
+    /// TODO(b/291238565) Create a separate error module for DicePolicy mismatches.
+    pub fn matches_dice_chain(&self, dice_chain: &[u8]) -> Result<()> {
+        let dice_chain = deserialize_dice_chain(dice_chain)?;
+        ensure!(
+            dice_chain.len() == self.node_constraints_list.len(),
+            format!(
+                "Dice chain size({}) does not match policy({})",
+                dice_chain.len(),
+                self.node_constraints_list.len()
+            )
+        );
+
+        for (n, (dice_node, node_constraints)) in
+            zip(dice_chain, self.node_constraints_list.iter()).enumerate()
+        {
+            let dice_node_payload = if n == 0 {
+                dice_node
+            } else {
+                cbor_value_from_cose_sign(dice_node)
+                    .with_context(|| format!("Unable to get Cose payload at: {}", n))?
+            };
+            check_constraints_on_node(node_constraints, &dice_node_payload)
+                .context(format!("Mismatch found at {}", n))?;
+        }
+        Ok(())
+    }
+}
+
+fn check_constraints_on_node(node_constraints: &NodeConstraints, dice_node: &Value) -> Result<()> {
+    for constraint in node_constraints.0.iter() {
+        check_constraint_on_node(constraint, dice_node)?;
+    }
+    Ok(())
+}
+
+fn check_constraint_on_node(constraint: &Constraint, dice_node: &Value) -> Result<()> {
+    let Constraint(cons_type, path, value_in_constraint) = constraint;
+    let value_in_node = lookup_value_in_nested_map(dice_node, path)?;
+    match ConstraintType::from_u16(*cons_type).ok_or(anyhow!("Unexpected Constraint type"))? {
+        ConstraintType::ExactMatch => ensure!(value_in_node == *value_in_constraint),
+        ConstraintType::GreaterOrEqual => {
+            let value_in_node = value_in_node
+                .as_integer()
+                .ok_or(anyhow!("Mismatch type: expected a cbor integer"))?;
+            let value_min = value_in_constraint
+                .as_integer()
+                .ok_or(anyhow!("Mismatch type: expected a cbor integer"))?;
+            ensure!(value_in_node >= value_min);
+        }
+    };
+    Ok(())
 }
 
 // Take the payload of a dice node & construct the constraints on it.
@@ -231,6 +285,17 @@
         Some(payload) => Ok(value_from_bytes(&payload)?),
     }
 }
+fn deserialize_dice_chain(dice_chain_bytes: &[u8]) -> Result<Vec<Value>> {
+    // TODO(b/298217847): Check if the given dice chain adheres to Explicit-key DiceCertChain
+    // format and if not, convert it.
+    let dice_chain =
+        value_from_bytes(dice_chain_bytes).context("Unable to decode top-level CBOR")?;
+    let dice_chain = match dice_chain {
+        Value::Array(array) if array.len() >= 2 => array,
+        _ => bail!("Expected an array of at least length 2, found: {:?}", dice_chain),
+    };
+    Ok(dice_chain)
+}
 
 /// Decodes the provided binary CBOR-encoded value and returns a
 /// ciborium::Value struct wrapped in Result.
@@ -266,38 +331,29 @@
         constraint_spec: Vec<ConstraintSpec>,
         // The expected dice policy if above constraint_spec is applied to input_dice.
         expected_dice_policy: DicePolicy,
+        // Another dice chain, which is almost same as the input_dice, but (roughly) imitates
+        // an 'updated' one, ie, some int entries are higher than corresponding entry
+        // in input_chain.
+        updated_input_dice: Vec<u8>,
     }
 
     impl TestArtifacts {
         // Get an example instance of TestArtifacts. This uses a hard coded, hypothetical
         // chain of certificates & a list of constraint_spec on this.
         fn get_example() -> Self {
-            const EXAMPLE_NUM: i64 = 59765;
+            const EXAMPLE_NUM_1: i64 = 59765;
+            const EXAMPLE_NUM_2: i64 = 59766;
             const EXAMPLE_STRING: &str = "testing_dice_policy";
+            const UNCONSTRAINED_STRING: &str = "unconstrained_string";
+            const ANOTHER_UNCONSTRAINED_STRING: &str = "another_unconstrained_string";
 
             let rot_key = CoseKey::default().to_cbor_value().unwrap();
-            let nested_payload = cbor!({
-                100 => EXAMPLE_NUM
-            })
-            .unwrap();
-            let payload = cbor!({
-                1 => EXAMPLE_STRING,
-                2 => "some_other_example_string",
-                3 => Value::Bytes(value_to_bytes(&nested_payload).unwrap()),
-            })
-            .unwrap();
-            let payload = value_to_bytes(&payload).unwrap();
-            let dice_node = CoseSign1 {
-                protected: ProtectedHeader::default(),
-                unprotected: Header::default(),
-                payload: Some(payload),
-                signature: b"ddef".to_vec(),
-            }
-            .to_cbor_value()
-            .unwrap();
-            let input_dice = Value::Array([rot_key.clone(), dice_node].to_vec());
-
-            let input_dice = value_to_bytes(&input_dice).unwrap();
+            let input_dice = Self::get_dice_chain_helper(
+                rot_key.clone(),
+                EXAMPLE_NUM_1,
+                EXAMPLE_STRING,
+                UNCONSTRAINED_STRING,
+            );
 
             // Now construct constraint_spec on the input dice, note this will use the keys
             // which are also hardcoded within the get_dice_chain_helper.
@@ -305,7 +361,7 @@
             let constraint_spec = vec![
                 ConstraintSpec::new(ConstraintType::ExactMatch, vec![1]).unwrap(),
                 // Notice how key "2" is (deliberately) absent in ConstraintSpec
-                // so policy should not constraint it.
+                // so policy should not constrain it.
                 ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![3, 100]).unwrap(),
             ];
             let expected_dice_policy = DicePolicy {
@@ -325,12 +381,53 @@
                         Constraint(
                             ConstraintType::GreaterOrEqual as u16,
                             vec![3, 100],
-                            Value::from(EXAMPLE_NUM),
+                            Value::from(EXAMPLE_NUM_1),
                         ),
                     ])),
                 ]),
             };
-            Self { input_dice, constraint_spec, expected_dice_policy }
+
+            let updated_input_dice = Self::get_dice_chain_helper(
+                rot_key.clone(),
+                EXAMPLE_NUM_2,
+                EXAMPLE_STRING,
+                ANOTHER_UNCONSTRAINED_STRING,
+            );
+            Self { input_dice, constraint_spec, expected_dice_policy, updated_input_dice }
+        }
+
+        // Helper method method to generate a dice chain with a given rot_key.
+        // Other arguments are ad-hoc values in the nested map. Callers use these to
+        // construct appropriate constrains in dice policies.
+        fn get_dice_chain_helper(
+            rot_key: Value,
+            version: i64,
+            constrained_string: &str,
+            unconstrained_string: &str,
+        ) -> Vec<u8> {
+            let nested_payload = cbor!({
+                100 => version
+            })
+            .unwrap();
+
+            let payload = cbor!({
+                1 => constrained_string,
+                2 => unconstrained_string,
+                3 => Value::Bytes(value_to_bytes(&nested_payload).unwrap()),
+            })
+            .unwrap();
+            let payload = value_to_bytes(&payload).unwrap();
+            let dice_node = CoseSign1 {
+                protected: ProtectedHeader::default(),
+                unprotected: Header::default(),
+                payload: Some(payload),
+                signature: b"ddef".to_vec(),
+            }
+            .to_cbor_value()
+            .unwrap();
+            let input_dice = Value::Array([rot_key.clone(), dice_node].to_vec());
+
+            value_to_bytes(&input_dice).unwrap()
         }
     }
 
@@ -344,6 +441,43 @@
         assert_eq!(policy, example.expected_dice_policy);
     }
 
+    test!(policy_matches_original_dice_chain);
+    fn policy_matches_original_dice_chain() {
+        let example = TestArtifacts::get_example();
+        assert!(
+            DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec)
+                .unwrap()
+                .matches_dice_chain(&example.input_dice)
+                .is_ok(),
+            "The dice chain did not match the policy constructed out of it!"
+        );
+    }
+
+    test!(policy_matches_updated_dice_chain);
+    fn policy_matches_updated_dice_chain() {
+        let example = TestArtifacts::get_example();
+        assert!(
+            DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec)
+                .unwrap()
+                .matches_dice_chain(&example.updated_input_dice)
+                .is_ok(),
+            "The updated dice chain did not match the original policy!"
+        );
+    }
+
+    test!(policy_mismatch_downgraded_dice_chain);
+    fn policy_mismatch_downgraded_dice_chain() {
+        let example = TestArtifacts::get_example();
+        assert!(
+            DicePolicy::from_dice_chain(&example.updated_input_dice, &example.constraint_spec)
+                .unwrap()
+                .matches_dice_chain(&example.input_dice)
+                .is_err(),
+            "The (downgraded) dice chain matched the policy constructed out of the 'updated'\
+            dice chain!!"
+        );
+    }
+
     test!(policy_dice_size_is_same);
     fn policy_dice_size_is_same() {
         // This is the number of certs in compos bcc (including the first ROT)