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 //! This library supports constructing Dice Policies on a Dice chains, enabling various ways to
18 //! specify the constraints. This adheres to the Dice Policy spec at DicePolicy.cddl & works with
19 //! the rust structs exported by libdice_policy.
20
21 #![allow(missing_docs)] // Sadly needed due to use of enumn::N
22
23 use ciborium::Value;
24 use dice_policy::{
25 check_is_explicit_key_dice_chain, deserialize_cbor_array, lookup_in_nested_container,
26 payload_value_from_cose_sign, Constraint, DicePolicy, NodeConstraints, DICE_POLICY_VERSION,
27 EXACT_MATCH_CONSTRAINT, GREATER_OR_EQUAL_CONSTRAINT,
28 };
29 use enumn::N;
30 use itertools::Either;
31 use std::borrow::Cow;
32 use std::iter::once;
33
34 type Error = String;
35
36 /// Supported version of dice policy spec.
37 pub const SUPPORTED_DICE_POLICY_VERSION: u64 = 1;
38 /// Wildcard key to specify all indexes of an array in a nested container. The library only
39 /// supports 1 Wildcard key per `ConstraintSpec`.
40 pub const WILDCARD_FULL_ARRAY: i64 = -1;
41
42 const CONFIG_DESC: i64 = -4670548;
43 const COMPONENT_NAME: i64 = -70002;
44 const PATH_TO_COMPONENT_NAME: [i64; 2] = [CONFIG_DESC, COMPONENT_NAME];
45
46 /// Constraint Types supported in Dice policy.
47 #[repr(u16)]
48 #[non_exhaustive]
49 #[derive(Clone, Copy, Debug, PartialEq, N)]
50 pub enum ConstraintType {
51 /// Enforce exact match criteria, indicating the policy should match
52 /// if the dice chain has exact same specified values.
53 ExactMatch = EXACT_MATCH_CONSTRAINT,
54 /// Enforce Greater than or equal to criteria. When applied on security_version, this
55 /// can be useful to set policy that matches dice chains with same or upgraded images.
56 GreaterOrEqual = GREATER_OR_EQUAL_CONSTRAINT,
57 }
58
59 /// ConstraintSpec is used to specify which constraint type to apply and on which all paths in a
60 /// DiceChainEntry.
61 /// See documentation of `policy_for_dice_chain()` for examples.
62 #[derive(Clone)]
63 pub struct ConstraintSpec {
64 constraint_type: ConstraintType,
65 // `path` is essentially a series of indexes (of map keys or array positions) to locate a
66 // particular value nested inside a DiceChainEntry.
67 path: Vec<i64>,
68 // If set to `Ignore`, the constraint is skipped if the `path` is missing in a
69 // DiceChainEntry while constructing a policy. Note that this does not affect policy
70 // matching.
71 if_path_missing: MissingAction,
72 // Which DiceChainEntry to apply the constraint on.
73 target_entry: TargetEntry,
74 }
75
76 impl ConstraintSpec {
77 /// Constraint the value corresponding to `path`. `constraint_type` specifies how and
78 /// `if_path_missing` specifies what to do if the `path` is missing in a DiceChainEntry.
new( constraint_type: ConstraintType, path: Vec<i64>, if_path_missing: MissingAction, target_entry: TargetEntry, ) -> Self79 pub fn new(
80 constraint_type: ConstraintType,
81 path: Vec<i64>,
82 if_path_missing: MissingAction,
83 target_entry: TargetEntry,
84 ) -> Self {
85 Self { constraint_type, path, if_path_missing, target_entry }
86 }
87
has_wildcard_entry(&self) -> bool88 fn has_wildcard_entry(&self) -> bool {
89 self.path.contains(&WILDCARD_FULL_ARRAY)
90 }
91
92 // Expand the ConstrainSpec into an iterable list. This expand to 1 or more specs depending
93 // on presence of Wildcard entry.
expand( &self, node_payload: &Value, ) -> impl Iterator<Item = Result<Cow<ConstraintSpec>, Error>>94 fn expand(
95 &self,
96 node_payload: &Value,
97 ) -> impl Iterator<Item = Result<Cow<ConstraintSpec>, Error>> {
98 if self.has_wildcard_entry() {
99 expand_wildcard_entry(self, node_payload).map_or_else(
100 |e| Either::Left(once(Err(e))),
101 |cs| Either::Right(cs.into_iter().map(|c| Ok(Cow::Owned(c)))),
102 )
103 } else {
104 Either::Left(once(Ok(Cow::Borrowed(self))))
105 }
106 }
107
use_once(&self) -> bool108 fn use_once(&self) -> bool {
109 matches!(self.target_entry, TargetEntry::ByName(_))
110 }
111 }
112
113 /// Indicates the DiceChainEntry in the chain. Note this covers only DiceChainEntries in
114 /// ExplicitKeyDiceCertChain, ie, this does not include the first 2 dice nodes.
115 #[derive(Clone, PartialEq, Eq, Debug)]
116 pub enum TargetEntry {
117 /// All Entries
118 All,
119 /// Specify a particular DiceChain entry by component name. Lookup is done from the end of the
120 /// chain, i.e., the last entry with given component_name will be targeted.
121 ByName(String),
122 }
123
124 // Extract the component name from the DiceChainEntry, if present.
try_extract_component_name(node_payload: &Value) -> Option<String>125 fn try_extract_component_name(node_payload: &Value) -> Option<String> {
126 let component_name = lookup_in_nested_container(node_payload, &PATH_TO_COMPONENT_NAME)
127 .unwrap_or_else(|e| {
128 log::warn!("Lookup for component_name in the node failed {e:?}- ignoring!");
129 None
130 })?;
131 component_name.into_text().ok()
132 }
133
is_target_node(node_payload: &Value, spec: &ConstraintSpec) -> bool134 fn is_target_node(node_payload: &Value, spec: &ConstraintSpec) -> bool {
135 match &spec.target_entry {
136 TargetEntry::All => true,
137 TargetEntry::ByName(name) => {
138 Some(name) == try_extract_component_name(node_payload).as_ref()
139 }
140 }
141 }
142
143 // Position of the first index marked `WILDCARD_FULL_ARRAY` in path.
wildcard_position(path: &[i64]) -> Result<usize, Error>144 fn wildcard_position(path: &[i64]) -> Result<usize, Error> {
145 let Some(pos) = path.iter().position(|&key| key == WILDCARD_FULL_ARRAY) else {
146 return Err("InvalidInput: Path does not have wildcard key".to_string());
147 };
148 Ok(pos)
149 }
150
151 // Size of array at specified `path` in the nested container.
size_of_array_in_nested_container(container: &Value, path: &[i64]) -> Result<usize, Error>152 fn size_of_array_in_nested_container(container: &Value, path: &[i64]) -> Result<usize, Error> {
153 if let Some(Value::Array(array)) = lookup_in_nested_container(container, path)? {
154 Ok(array.len())
155 } else {
156 Err("InternalError: Expected nested array not found".to_string())
157 }
158 }
159
160 // Expand `ConstraintSpec` containing `WILDCARD_FULL_ARRAY` to entries with path covering each
161 // array index.
expand_wildcard_entry( spec: &ConstraintSpec, target_node: &Value, ) -> Result<Vec<ConstraintSpec>, Error>162 fn expand_wildcard_entry(
163 spec: &ConstraintSpec,
164 target_node: &Value,
165 ) -> Result<Vec<ConstraintSpec>, Error> {
166 let pos = wildcard_position(&spec.path)?;
167 // Notice depth of the array in nested container is same as the position in path.
168 let size = size_of_array_in_nested_container(target_node, &spec.path[..pos])?;
169 Ok((0..size as i64).map(|i| replace_key(spec, pos, i)).collect())
170 }
171
172 // Get a ConstraintSpec same as `spec` except for changing the key at `pos` in its `path`
173 // This method panics if `pos` in out of index of path.
replace_key(spec: &ConstraintSpec, pos: usize, new_key: i64) -> ConstraintSpec174 fn replace_key(spec: &ConstraintSpec, pos: usize, new_key: i64) -> ConstraintSpec {
175 let mut spec: ConstraintSpec = spec.clone();
176 spec.path[pos] = new_key;
177 spec
178 }
179
180 /// Used to define a `ConstraintSpec` requirement. This allows client to set behavior of
181 /// policy construction in case the `path` is missing in a DiceChainEntry.
182 #[derive(Clone, Eq, PartialEq)]
183 pub enum MissingAction {
184 /// Indicates that a constraint `path` is required in each DiceChainEntry. Fail if missing!
185 Fail,
186 /// Indicates that a constraint `path` may be missing in some DiceChainEntry. Ignore!
187 Ignore,
188 }
189
190 /// Construct a dice policy on a given dice chain.
191 /// This can be used by clients to construct a policy to seal secrets.
192 /// Constraints on all but (first & second) dice nodes is applied using constraint_spec
193 /// argument. For the first and second node (version and root public key), the constraint is
194 /// ExactMatch of the whole node.
195 ///
196 /// # Arguments
197 /// `dice_chain`: The serialized CBOR encoded Dice chain, adhering to Explicit-key
198 /// DiceCertChain format. See definition at ExplicitKeyDiceCertChain.cddl
199 ///
200 /// `constraint_spec`: List of constraints to be applied on dice node.
201 /// Each constraint is a ConstraintSpec object.
202 ///
203 /// Note: Dice node is treated as a nested structure (map or array) (& so the lookup is done in
204 /// that fashion).
205 ///
206 /// Examples of constraint_spec:
207 /// 1. For exact_match on auth_hash & greater_or_equal on security_version
208 /// constraint_spec =[
209 /// (ConstraintType::ExactMatch, vec![AUTHORITY_HASH]),
210 /// (ConstraintType::GreaterOrEqual, vec![CONFIG_DESC, COMPONENT_NAME]),
211 /// ];
212 ///
213 /// 2. For hypothetical (and highly simplified) dice chain:
214 ///
215 /// [1, ROT_KEY, [{1 : 'a', 2 : {200 : 5, 201 : 'b'}}]]
216 /// The following can be used
217 /// constraint_spec =[
218 /// ConstraintSpec(ConstraintType::ExactMatch, vec![1]), // exact_matches value 'a'
219 /// ConstraintSpec(ConstraintType::GreaterOrEqual, vec![2, 200]),// matches any value >= 5
220 /// ];
policy_for_dice_chain( explicit_key_dice_chain: &[u8], mut constraint_spec: Vec<ConstraintSpec>, ) -> Result<DicePolicy, Error>221 pub fn policy_for_dice_chain(
222 explicit_key_dice_chain: &[u8],
223 mut constraint_spec: Vec<ConstraintSpec>,
224 ) -> Result<DicePolicy, Error> {
225 let dice_chain = deserialize_cbor_array(explicit_key_dice_chain)?;
226 check_is_explicit_key_dice_chain(&dice_chain)?;
227 let mut constraints_list: Vec<NodeConstraints> = Vec::with_capacity(dice_chain.len());
228 let chain_entries_len = dice_chain.len() - 2;
229 // Iterate on the reversed DICE chain! This is important because some constraint_spec
230 // (with `TargetEntry::ByType`) are applied on the last DiceChainEntry matching the spec.
231 let mut it = dice_chain.into_iter().rev();
232 for i in 0..chain_entries_len {
233 let entry = payload_value_from_cose_sign(it.next().unwrap())
234 .map_err(|e| format!("Unable to get Cose payload at pos {i} from end: {e:?}"))?;
235 constraints_list.push(constraints_on_dice_node(entry, &mut constraint_spec).map_err(
236 |e| format!("Unable to get constraints for payload at {i} from end: {e:?}"),
237 )?);
238 }
239
240 // 1st & 2nd dice node of Explicit-key DiceCertChain format are
241 // EXPLICIT_KEY_DICE_CERT_CHAIN_VERSION & DiceCertChainInitialPayload.
242 constraints_list.push(NodeConstraints(Box::new([Constraint::new(
243 ConstraintType::ExactMatch as u16,
244 Vec::new(),
245 it.next().unwrap(),
246 )?])));
247
248 constraints_list.push(NodeConstraints(Box::new([Constraint::new(
249 ConstraintType::ExactMatch as u16,
250 Vec::new(),
251 it.next().unwrap(),
252 )?])));
253
254 constraints_list.reverse();
255 Ok(DicePolicy {
256 version: DICE_POLICY_VERSION,
257 node_constraints_list: constraints_list.into_boxed_slice(),
258 })
259 }
260
261 // Take the ['node_payload'] of a dice node & construct the [`NodeConstraints`] on it. If the value
262 // corresponding to the a [`constraint_spec`] is not present in payload & iff it is marked
263 // `MissingAction::Ignore`, the corresponding constraint will be missing from the NodeConstraints.
264 // Not all constraint_spec applies to all DiceChainEntries, see `TargetEntry::ByName`.
constraints_on_dice_node( node_payload: Value, constraint_spec: &mut Vec<ConstraintSpec>, ) -> Result<NodeConstraints, Error>265 fn constraints_on_dice_node(
266 node_payload: Value,
267 constraint_spec: &mut Vec<ConstraintSpec>,
268 ) -> Result<NodeConstraints, Error> {
269 let mut node_constraints: Vec<Constraint> = Vec::new();
270 let constraint_spec_with_retention_marker =
271 constraint_spec.iter().map(|c| (c.clone(), is_target_node(&node_payload, c)));
272
273 for (constraint_item, is_target) in constraint_spec_with_retention_marker.clone() {
274 if !is_target {
275 continue;
276 }
277 // Some constraint spec may have wildcard entries, expand those!
278 for constraint_item_expanded in constraint_item.expand(&node_payload) {
279 let constraint_item_expanded = constraint_item_expanded?;
280 if let Some(constraint) =
281 constraint_on_dice_node(&node_payload, &constraint_item_expanded)?
282 {
283 node_constraints.push(constraint);
284 }
285 }
286 }
287
288 *constraint_spec = constraint_spec_with_retention_marker
289 .filter_map(|(c, is_target)| if is_target && c.use_once() { None } else { Some(c) })
290 .collect();
291 Ok(NodeConstraints(node_constraints.into_boxed_slice()))
292 }
293
constraint_on_dice_node( node_payload: &Value, constraint_spec: &ConstraintSpec, ) -> Result<Option<Constraint>, Error>294 fn constraint_on_dice_node(
295 node_payload: &Value,
296 constraint_spec: &ConstraintSpec,
297 ) -> Result<Option<Constraint>, Error> {
298 if constraint_spec.path.is_empty() {
299 return Err("Expected non-empty key spec".to_string());
300 }
301 let constraint = match lookup_in_nested_container(node_payload, &constraint_spec.path) {
302 Ok(Some(val)) => Some(Constraint::new(
303 constraint_spec.constraint_type as u16,
304 constraint_spec.path.to_vec(),
305 val,
306 )?),
307 Ok(None) => {
308 if constraint_spec.if_path_missing == MissingAction::Ignore {
309 log::warn!("Value not found for {:?}, - skipping!", constraint_spec.path);
310 None
311 } else {
312 return Err(format!(
313 "Value not found for : {:?}, constraint\
314 spec is marked to fail on missing path",
315 constraint_spec.path
316 ));
317 }
318 }
319 Err(e) => {
320 if constraint_spec.if_path_missing == MissingAction::Ignore {
321 log::warn!(
322 "Error ({e:?}) getting Value for {:?}, - skipping!",
323 constraint_spec.path
324 );
325 None
326 } else {
327 return Err(format!(
328 "Error ({e:?}) getting Value for {:?}, constraint\
329 spec is marked to fail on missing path",
330 constraint_spec.path
331 ));
332 }
333 }
334 };
335 Ok(constraint)
336 }
337