blob: 327b8a41eb94362a5be13eda39f3abe9900e6f6a [file] [log] [blame]
Shikha Panwar71b4f832023-08-21 17:43:09 +00001/*
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
60use anyhow::{anyhow, bail, Context, Result};
61use ciborium::Value;
62use coset::{AsCborValue, CoseSign1};
63use std::borrow::Cow;
64
65const DICE_POLICY_VERSION: u64 = 1;
66
67/// Constraint Types supported in Dice policy.
68#[non_exhaustive]
69#[derive(Clone, Copy, Debug, PartialEq)]
70pub enum ConstraintType {
71 /// Enforce exact match criteria, indicating the policy should match
72 /// if the dice chain has exact same specified values.
73 ExactMatch = 1,
74 /// Enforce Greater than or equal to criteria. When applied on security_version, this
75 /// can be useful to set policy that matches dice chains with same or upgraded images.
76 GreaterOrEqual = 2,
77}
78
79/// ConstraintSpec is used to specify which constraint type to apply and
80/// on which all entries in a dice node.
81/// See documentation of `from_dice_chain()` for examples.
82pub struct ConstraintSpec {
83 constraint_type: ConstraintType,
84 // path is essentially a list of label/int.
85 // It identifies which entry (in a dice node) to be applying constraints on.
86 path: Vec<i64>,
87}
88
89impl ConstraintSpec {
90 /// Construct the ConstraintSpec.
91 pub fn new(constraint_type: ConstraintType, path: Vec<i64>) -> Result<Self> {
92 Ok(ConstraintSpec { constraint_type, path })
93 }
94}
95
96// TODO(b/291238565): Restrict (nested_)key & value type to (bool/int/tstr/bstr).
97// and maybe convert it into struct.
98/// Each constraint (on a dice node) is a tuple: (ConstraintType, constraint_path, value)
99#[derive(Debug, PartialEq)]
100struct Constraint(u16, Vec<i64>, Value);
101
102/// List of all constraints on a dice node.
103#[derive(Debug, PartialEq)]
104struct NodeConstraints(Box<[Constraint]>);
105
106/// Module for working with dice policy.
107#[derive(Debug, PartialEq)]
108pub struct DicePolicy {
109 version: u64,
110 node_constraints_list: Box<[NodeConstraints]>, // Constraint on each entry in dice chain.
111}
112
113impl DicePolicy {
114 /// Construct a dice policy from a given dice chain.
115 /// This can be used by clients to construct a policy to seal secrets.
116 /// Constraints on all but first dice node is applied using constraint_spec argument.
117 /// For the first node (which is a ROT key), the constraint is ExactMatch of the whole node.
118 ///
119 /// # Arguments
120 /// `dice_chain`: The serialized CBOR encoded Dice chain, adhering to Android Profile for DICE.
121 /// https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md
122 ///
123 /// `constraint_spec`: List of constraints to be applied on dice node.
124 /// Each constraint is a ConstraintSpec object.
125 ///
126 /// Note: Dice node is treated as a nested map (& so the lookup is done in that fashion).
127 ///
128 /// Examples of constraint_spec:
129 /// 1. For exact_match on auth_hash & greater_or_equal on security_version
130 /// constraint_spec =[
131 /// (ConstraintType::ExactMatch, vec![AUTHORITY_HASH]),
132 /// (ConstraintType::GreaterOrEqual, vec![CONFIG_DESC, COMPONENT_NAME]),
133 /// ];
134 ///
135 /// 2. For hypothetical (and highly simplified) dice chain:
136 /// [ROT_KEY, [{1 : 'a', 2 : {200 : 5, 201 : 'b'}}]]
137 /// The following can be used
138 /// constraint_spec =[
139 /// ConstraintSpec(ConstraintType::ExactMatch, vec![1]), // exact_matches value 'a'
140 /// ConstraintSpec(ConstraintType::GreaterOrEqual, vec![2, 200]),// matches any value >= 5
141 /// ];
142 pub fn from_dice_chain(dice_chain: &[u8], constraint_spec: &[ConstraintSpec]) -> Result<Self> {
143 // TODO(b/298217847): Check if the given dice chain adheres to Explicit-key DiceCertChain
144 // format and if not, convert it before policy construction.
145 let dice_chain = value_from_bytes(dice_chain).context("Unable to decode top-level CBOR")?;
146 let dice_chain = match dice_chain {
147 Value::Array(array) if array.len() >= 2 => array,
148 _ => bail!("Expected an array of at least length 2, found: {:?}", dice_chain),
149 };
150 let mut constraints_list: Vec<NodeConstraints> = Vec::with_capacity(dice_chain.len());
151 let mut it = dice_chain.into_iter();
152
153 constraints_list.push(NodeConstraints(Box::new([Constraint(
154 ConstraintType::ExactMatch as u16,
155 Vec::new(),
156 it.next().unwrap(),
157 )])));
158
159 for (n, value) in it.enumerate() {
160 let entry = cbor_value_from_cose_sign(value)
161 .with_context(|| format!("Unable to get Cose payload at: {}", n))?;
162 constraints_list.push(payload_to_constraints(entry, constraint_spec)?);
163 }
164
165 Ok(DicePolicy {
166 version: DICE_POLICY_VERSION,
167 node_constraints_list: constraints_list.into_boxed_slice(),
168 })
169 }
170}
171
172// Take the payload of a dice node & construct the constraints on it.
173fn payload_to_constraints(
174 payload: Value,
175 constraint_spec: &[ConstraintSpec],
176) -> Result<NodeConstraints> {
Shikha Panwar1d016862023-10-20 18:36:29 +0000177 let mut node_constraints: Vec<Constraint> = Vec::with_capacity(constraint_spec.len());
Shikha Panwar71b4f832023-08-21 17:43:09 +0000178 for constraint_item in constraint_spec {
179 let constraint_path = constraint_item.path.to_vec();
180 if constraint_path.is_empty() {
181 bail!("Expected non-empty key spec");
182 }
183 let val = lookup_value_in_nested_map(&payload, &constraint_path)
184 .context(format!("Value not found for constraint_path {:?}", constraint_path))?;
185 let constraint = Constraint(constraint_item.constraint_type as u16, constraint_path, val);
186 node_constraints.push(constraint);
187 }
188 Ok(NodeConstraints(node_constraints.into_boxed_slice()))
189}
190
191// Lookup value corresponding to constraint path in nested map.
192// This function recursively calls itself.
193// The depth of recursion is limited by the size of constraint_path.
194fn lookup_value_in_nested_map(cbor_map: &Value, constraint_path: &[i64]) -> Result<Value> {
195 if constraint_path.is_empty() {
196 return Ok(cbor_map.clone());
197 }
198 let explicit_map = get_map_from_value(cbor_map)?;
199 let val = lookup_value_in_map(&explicit_map, constraint_path[0])
200 .ok_or(anyhow!("Value not found for constraint key: {:?}", constraint_path[0]))?;
201 lookup_value_in_nested_map(val, &constraint_path[1..])
202}
203
204fn get_map_from_value(cbor_map: &Value) -> Result<Cow<Vec<(Value, Value)>>> {
205 match cbor_map {
206 Value::Bytes(b) => value_from_bytes(b)?
207 .into_map()
208 .map(Cow::Owned)
209 .map_err(|e| anyhow!("Expected a cbor map: {:?}", e)),
210 Value::Map(map) => Ok(Cow::Borrowed(map)),
211 _ => bail!("/Expected a cbor map {:?}", cbor_map),
212 }
213}
214
215fn lookup_value_in_map(map: &[(Value, Value)], key: i64) -> Option<&Value> {
216 let key = Value::Integer(key.into());
217 for (k, v) in map.iter() {
218 if k == &key {
219 return Some(v);
220 }
221 }
222 None
223}
224
225/// Extract the payload from the COSE Sign
226fn cbor_value_from_cose_sign(cbor: Value) -> Result<Value> {
227 let sign1 =
228 CoseSign1::from_cbor_value(cbor).map_err(|e| anyhow!("Error extracting CoseKey: {}", e))?;
229 match sign1.payload {
230 None => bail!("Missing payload"),
231 Some(payload) => Ok(value_from_bytes(&payload)?),
232 }
233}
234
235/// Decodes the provided binary CBOR-encoded value and returns a
236/// ciborium::Value struct wrapped in Result.
237fn value_from_bytes(mut bytes: &[u8]) -> Result<Value> {
238 let value = ciborium::de::from_reader(&mut bytes)?;
239 // Ciborium tries to read one Value, & doesn't care if there is trailing data after it. We do.
240 if !bytes.is_empty() {
241 bail!("Unexpected trailing data while converting to CBOR value");
242 }
243 Ok(value)
244}
245
246#[cfg(test)]
247rdroidtest::test_main!();
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use ciborium::cbor;
253 use coset::{CoseKey, Header, ProtectedHeader};
254 use rdroidtest::test;
255
256 const AUTHORITY_HASH: i64 = -4670549;
257 const CONFIG_DESC: i64 = -4670548;
258 const COMPONENT_NAME: i64 = -70002;
259 const KEY_MODE: i64 = -4670551;
260
Shikha Panwar1d016862023-10-20 18:36:29 +0000261 // Helper struct to encapsulate artifacts that are useful for unit tests.
262 struct TestArtifacts {
263 // A dice chain.
264 input_dice: Vec<u8>,
265 // A list of ConstraintSpec that can be applied on the input_dice to get a dice policy.
266 constraint_spec: Vec<ConstraintSpec>,
267 // The expected dice policy if above constraint_spec is applied to input_dice.
268 expected_dice_policy: DicePolicy,
269 }
270
271 impl TestArtifacts {
272 // Get an example instance of TestArtifacts. This uses a hard coded, hypothetical
273 // chain of certificates & a list of constraint_spec on this.
274 fn get_example() -> Self {
275 const EXAMPLE_NUM: i64 = 59765;
276 const EXAMPLE_STRING: &str = "testing_dice_policy";
277
278 let rot_key = CoseKey::default().to_cbor_value().unwrap();
279 let nested_payload = cbor!({
280 100 => EXAMPLE_NUM
281 })
282 .unwrap();
283 let payload = cbor!({
284 1 => EXAMPLE_STRING,
285 2 => "some_other_example_string",
286 3 => Value::Bytes(value_to_bytes(&nested_payload).unwrap()),
287 })
288 .unwrap();
289 let payload = value_to_bytes(&payload).unwrap();
290 let dice_node = CoseSign1 {
291 protected: ProtectedHeader::default(),
292 unprotected: Header::default(),
293 payload: Some(payload),
294 signature: b"ddef".to_vec(),
295 }
296 .to_cbor_value()
297 .unwrap();
298 let input_dice = Value::Array([rot_key.clone(), dice_node].to_vec());
299
300 let input_dice = value_to_bytes(&input_dice).unwrap();
301
302 // Now construct constraint_spec on the input dice, note this will use the keys
303 // which are also hardcoded within the get_dice_chain_helper.
304
305 let constraint_spec = vec![
306 ConstraintSpec::new(ConstraintType::ExactMatch, vec![1]).unwrap(),
307 // Notice how key "2" is (deliberately) absent in ConstraintSpec
308 // so policy should not constraint it.
309 ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![3, 100]).unwrap(),
310 ];
311 let expected_dice_policy = DicePolicy {
312 version: 1,
313 node_constraints_list: Box::new([
314 NodeConstraints(Box::new([Constraint(
315 ConstraintType::ExactMatch as u16,
316 vec![],
317 rot_key.clone(),
318 )])),
319 NodeConstraints(Box::new([
320 Constraint(
321 ConstraintType::ExactMatch as u16,
322 vec![1],
323 Value::Text(EXAMPLE_STRING.to_string()),
324 ),
325 Constraint(
326 ConstraintType::GreaterOrEqual as u16,
327 vec![3, 100],
328 Value::from(EXAMPLE_NUM),
329 ),
330 ])),
331 ]),
332 };
333 Self { input_dice, constraint_spec, expected_dice_policy }
334 }
335 }
336
337 test!(policy_structure_check);
338 fn policy_structure_check() {
339 let example = TestArtifacts::get_example();
340 let policy =
341 DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec).unwrap();
342
343 // Assert policy is exactly as expected!
344 assert_eq!(policy, example.expected_dice_policy);
345 }
Shikha Panwar71b4f832023-08-21 17:43:09 +0000346
347 test!(policy_dice_size_is_same);
348 fn policy_dice_size_is_same() {
Shikha Panwar1d016862023-10-20 18:36:29 +0000349 // This is the number of certs in compos bcc (including the first ROT)
350 // To analyze a bcc use hwtrust tool from /tools/security/remote_provisioning/hwtrust
351 // `hwtrust --verbose dice-chain [path]/composbcc`
352 let compos_dice_chain_size: usize = 5;
Shikha Panwar71b4f832023-08-21 17:43:09 +0000353 let input_dice = include_bytes!("../testdata/composbcc");
354 let constraint_spec = [
355 ConstraintSpec::new(ConstraintType::ExactMatch, vec![AUTHORITY_HASH]).unwrap(),
356 ConstraintSpec::new(ConstraintType::ExactMatch, vec![KEY_MODE]).unwrap(),
357 ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![CONFIG_DESC, COMPONENT_NAME])
358 .unwrap(),
359 ];
360 let policy = DicePolicy::from_dice_chain(input_dice, &constraint_spec).unwrap();
Shikha Panwar1d016862023-10-20 18:36:29 +0000361 assert_eq!(policy.node_constraints_list.len(), compos_dice_chain_size);
Shikha Panwar71b4f832023-08-21 17:43:09 +0000362 }
363
364 /// Encodes a ciborium::Value into bytes.
365 fn value_to_bytes(value: &Value) -> Result<Vec<u8>> {
366 let mut bytes: Vec<u8> = Vec::new();
367 ciborium::ser::into_writer(&value, &mut bytes)?;
368 Ok(bytes)
369 }
370}