1 // Copyright 2025 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ////////////////////////////////////////////////////////////////////////////////
16
17 //! Entry point to the AuthMgr-Common crate used by both AuthMgr-FE and AuthMgr-BE.
18
19 #![no_std]
20 extern crate alloc;
21
22 use alloc::string::String;
23 use authgraph_core::key::{CertChain, DiceChainEntry, Policy};
24 use coset::{CborSerializable, CoseError};
25 use dice_policy::{DicePolicy, DICE_POLICY_VERSION};
26
27 pub mod signed_connection_request;
28
29 /// The command byte to indicate the intent to connect to the `IAuthMgrAuthorization` service via
30 /// RPC binder
31 pub const CMD_RPC: u8 = 0u8;
32 /// The command byte to indicate the intent to establish a raw connection
33 pub const CMD_RAW: u8 = 1u8;
34
35 /// Token length is 32 bytes
36 pub const TOKEN_LENGTH: usize = 32;
37 /// Type alias for a cryptographic random token (sent from AuthMgrFE to BE) of 32 bytes
38 pub type Token = [u8; TOKEN_LENGTH];
39
40 /// AuthMgr common error type
41 #[derive(Debug, PartialEq)]
42 pub struct Error(pub ErrorCode, pub String);
43
44 /// AuthMgr common error codes
45 #[derive(Debug, PartialEq)]
46 pub enum ErrorCode {
47 /// CBOR encoding failed
48 CborEncodingFailed,
49 /// CBOR decoding failed
50 CborDecodingFailed,
51 /// Signing failed
52 SigningFailed,
53 /// Signature verification failed
54 SignatureVerificationFailed,
55 /// Failed to create a DICE policy
56 DicePolicyCreationFailed,
57 /// Failed to match DICE policy
58 DicePolicyMatchingFailed,
59 /// Other error
60 UnknownError,
61 }
62
63 /// AuthMgr common result type
64 pub type Result<T, E = Error> = core::result::Result<T, E>;
65
66 impl core::convert::From<CoseError> for Error {
from(e: CoseError) -> Self67 fn from(e: CoseError) -> Self {
68 match e {
69 CoseError::DecodeFailed(_) => amc_err!(CborDecodingFailed, "{}", e),
70 CoseError::EncodeFailed => amc_err!(CborEncodingFailed, "{}", e),
71 CoseError::UnexpectedItem(_, _) => amc_err!(CborDecodingFailed, "{}", e),
72 _ => amc_err!(UnknownError, "{}", e),
73 }
74 }
75 }
76
77 /// Macro to build an [`Error`] instance.
78 /// E.g. use: `amc_err!(SigningFailed, "some {} format", arg)`.
79 #[macro_export]
80 macro_rules! amc_err {
81 { $error_code:ident, $($arg:tt)+ } => {
82 Error(ErrorCode::$error_code,
83 alloc::format!("{}:{}: {}", file!(), line!(), format_args!($($arg)+))) };
84 }
85
86 /// Wrapper function around the dice_policy library API, to match a DICE chain with a DICE policy,
87 /// to be used in AuthMgr protocol.
match_dice_chain_with_policy(cert_chain: &CertChain, policy: &Policy) -> Result<bool>88 pub fn match_dice_chain_with_policy(cert_chain: &CertChain, policy: &Policy) -> Result<bool> {
89 let cert_chain = cert_chain.clone().to_vec()?;
90 let result = dice_policy::chain_matches_policy(&cert_chain, &policy.0);
91 Ok(result.is_ok())
92 }
93
94 /// Wrapper function around the dice_policy library API, to match a DICE certificate with a DICE
95 /// policy, to be used in AuthMgr protocol. A DICE policy created for a single DICE certificate is
96 /// expected to have up to only one nodeConstraintList, as per the DICE policy specification.
match_dice_cert_with_policy(dice_cert: &DiceChainEntry, policy: &Policy) -> Result<bool>97 pub fn match_dice_cert_with_policy(dice_cert: &DiceChainEntry, policy: &Policy) -> Result<bool> {
98 let dice_policy = DicePolicy::from_slice(&policy.0)?;
99 if dice_policy.node_constraints_list.is_empty() {
100 return Ok(true);
101 }
102 let dice_node = dice_cert
103 .payload
104 .full_map
105 .as_ref()
106 .ok_or(amc_err!(UnknownError, "no certificate payload found"))?;
107 let result =
108 dice_policy::check_constraints_on_node(&dice_policy.node_constraints_list[0], dice_node)
109 .map_err(|_e| {
110 amc_err!(DicePolicyMatchingFailed, "failed to match constraints on DICE cert")
111 });
112 Ok(result.is_ok())
113 }
114
115 /// Get a copy of this DICE policy extended with the given DICE policy. This is an AuthMgr
116 /// specific requirement where we create a DICE policy for a child DICE certificate and later
117 /// need to combine it with the policy for the parent DICE cert chain
extend_dice_policy_with(parent_policy: &Policy, child_policy: &Policy) -> Result<Policy>118 pub fn extend_dice_policy_with(parent_policy: &Policy, child_policy: &Policy) -> Result<Policy> {
119 let parent_policy = DicePolicy::from_slice(&parent_policy.0)?;
120 let child_policy = DicePolicy::from_slice(&child_policy.0)?;
121 // Match the version of the two policies
122 if parent_policy.version != child_policy.version {
123 return Err(amc_err!(DicePolicyCreationFailed, "policy versions do not match"));
124 }
125 let mut parent_node_constraints = parent_policy.node_constraints_list.to_vec();
126 parent_node_constraints.extend_from_slice(&child_policy.node_constraints_list);
127 let extended_policy = DicePolicy {
128 version: DICE_POLICY_VERSION,
129 node_constraints_list: parent_node_constraints.into_boxed_slice(),
130 };
131 Ok(Policy(extended_policy.to_vec()?))
132 }
133
134 #[cfg(test)]
135 mod tests {
136 use super::*;
137 use alloc::string::ToString;
138 use alloc::{vec, vec::Vec};
139 use authgraph_boringssl::ec::BoringEcDsa;
140 use authgraph_core::key::{
141 AUTHORITY_HASH, CONFIG_DESC, GUEST_OS_COMPONENT_NAME, INSTANCE_HASH, MODE, SECURITY_VERSION,
142 };
143 use authgraph_core_test::{
144 create_dice_cert_chain_for_guest_os, create_dice_leaf_cert, SAMPLE_INSTANCE_HASH,
145 };
146 use authmgr_common_util::{
147 get_constraint_spec_for_static_trusty_ta, get_constraints_spec_for_trusty_vm,
148 policy_for_dice_node,
149 };
150 use dice_policy_builder::{ConstraintSpec, ConstraintType, MissingAction, TargetEntry};
151
152 #[test]
test_dice_policy_extend()153 fn test_dice_policy_extend() {
154 // Create DICE chain 1 for a pvm instance
155 let (_sign_key_1, cdi_values_1, cert_chain_bytes_1) =
156 create_dice_cert_chain_for_guest_os(Some(SAMPLE_INSTANCE_HASH), 1);
157 let cert_chain_1 =
158 CertChain::from_slice(&cert_chain_bytes_1).expect("failed to decode cert_chain 1");
159 // Create constraints spec 1
160 let constraint_spec_1 = get_constraints_spec_for_trusty_vm();
161 // Create policy 1 given constraints spec 1 and DICE chain 1
162 let policy_1 = Policy(
163 dice_policy_builder::policy_for_dice_chain(
164 &cert_chain_bytes_1,
165 constraint_spec_1.clone(),
166 )
167 .expect("failed to building policy 1")
168 .to_vec()
169 .expect("failed to encode policy 1"),
170 );
171 // Match DICE chain 1 to policy 1 and expect a match
172 let result1 = match_dice_chain_with_policy(&cert_chain_1, &policy_1);
173 assert!(result1.unwrap());
174 // Create (child) DICE cert entry for DICE chain 1
175 let leaf_cert_bytes_1 = create_dice_leaf_cert(cdi_values_1, "keymint", 1);
176 let leaf_cert_1 =
177 DiceChainEntry::from_slice(&leaf_cert_bytes_1).expect("failed to decode leaf cert 1");
178 // Create a constraints spec for client 1
179 let client_constraint_spec_km = get_constraint_spec_for_static_trusty_ta();
180 // Create the client policy given the constraints spec and DICE cert entry
181 let client_policy_1 = Policy(
182 policy_for_dice_node(&leaf_cert_1, client_constraint_spec_km)
183 .expect("failed to create dice policy for keymint")
184 .to_vec()
185 .expect("failed to encode policy for client TA 1"),
186 );
187 // Match the client's DICE cert entry and policy and expect a match
188 let result_1a = match_dice_cert_with_policy(&leaf_cert_1, &client_policy_1);
189 assert!(result_1a.unwrap());
190
191 // Extend DICE chain 1 with leaf DICE cert 1
192 let ecdsa = BoringEcDsa;
193 let extended_dice_chain_1 =
194 cert_chain_1.extend_with(&leaf_cert_1, &ecdsa).expect("failed to extend DICE chain 1");
195 // Extend DICE policy 1 with client 1's DICE policy
196 let extended_policy_1 = extend_dice_policy_with(&policy_1, &client_policy_1)
197 .expect("failed to extend DICE policy 1");
198
199 // Match the extended DICE chain 1 with the extended DICE policy 1 and expect a match
200 let result1 = match_dice_chain_with_policy(&extended_dice_chain_1, &extended_policy_1);
201 assert!(result1.unwrap());
202 }
203
204 // This test simulates the DICE chain to policy matching that takes place in the full AuthMgr
205 // protocol, using:
206 // 1) two different DICE chains representing two pvm instances,
207 // 2) DICE policies for the two pvm instances,
208 // 3) two individual DICE certificates (which are extension of the aforementioned two DICE
209 // chains) representing the client TAs in the aforementioned two pvm instances,
210 // 4) DICE policies created for the client TAs.
211 // We do the following checks:
212 // 1) Matching of the DICE cert chain and the policy of a given pvm instance succeeds.
213 // 2) Matching of the DICE cert chain of one pvm instance with the policy of the other pvm
214 // instance fails.
215 // 3) DICE policies of the two instances do not pass the equality match
216 // 4) Matching of the DICE certificate and the policy of a given client TA in a pvm instance
217 // succeeds
218 // 5) Matching of the DICE certificate of one client TA and the policy of the other client
219 // TA fails.
220 // 6) Matching of the extended DICE chain created for a client TA in a pvm and the extended
221 // DICE policy created for the same client TA succeeds.
222 // 7) The extended DICE policies created for the two client TAs do not pass the equality
223 // check.
224 // 8) Matching of the extended DICE chain created for one client and the extended DICE
225 // policy created for the other client fails.
226 // 9) Rollback protection for pvm instance 1 with a new DICE chain created with a higher
227 // security version and an updated policy.
228 #[test]
simulate_authmgr_dice_verification()229 fn simulate_authmgr_dice_verification() {
230 // Create DICE chain 1 for a pvm instance
231 let (_sign_key_1, cdi_values_1, cert_chain_bytes_1) =
232 create_dice_cert_chain_for_guest_os(Some(SAMPLE_INSTANCE_HASH), 1);
233 let cert_chain_1 =
234 CertChain::from_slice(&cert_chain_bytes_1).expect("failed to decode cert_chain 1");
235 // Create constraints spec 1
236 let constraint_spec_1 = get_constraints_spec_for_trusty_vm();
237 // Create policy 1 given constraints spec 1 and DICE chain 1
238 let policy_1 = Policy(
239 dice_policy_builder::policy_for_dice_chain(
240 &cert_chain_bytes_1,
241 constraint_spec_1.clone(),
242 )
243 .expect("failed to building policy 1")
244 .to_vec()
245 .expect("failed to encode policy 1"),
246 );
247 // Match DICE chain 1 to policy 1 and expect a match
248 let result1 = match_dice_chain_with_policy(&cert_chain_1, &policy_1);
249 assert_eq!(result1, Ok(true));
250
251 // Create DICE chain 2 for a pvm instance (with different instance hash and security
252 // version)
253 let instance_hash: [u8; 64] = [
254 0x1a, 0x3f, 0xc9, 0x6b, 0xe3, 0x95, 0x59, 0x40, 0x21, 0x09, 0x9d, 0xf3, 0xcd, 0xc7,
255 0xa4, 0x2a, 0x7d, 0x7e, 0xf5, 0x8e, 0xe6, 0x4d, 0x84, 0x25, 0x1a, 0x51, 0x27, 0x9d,
256 0x55, 0x8a, 0xe9, 0x90, 0xf5, 0x8e, 0xd6, 0x4d, 0x84, 0x25, 0x1a, 0x51, 0x86, 0x9d,
257 0x5b, 0x3f, 0xc9, 0x6b, 0xe3, 0x95, 0x59, 0x40, 0x21, 0x09, 0x9d, 0xf3, 0xcd, 0xc7,
258 0xa4, 0x2a, 0x7d, 0x7e, 0xf5, 0x8e, 0xf5, 0x3f,
259 ];
260 let (_sign_key_2, cdi_values_2, cert_chain_bytes_2) =
261 create_dice_cert_chain_for_guest_os(Some(instance_hash), 2);
262 let cert_chain_2 =
263 CertChain::from_slice(&cert_chain_bytes_2).expect("failed to decode cert_chain 2");
264 // Create (different) constraints spec 2
265 let constraint_spec_2 = get_relaxed_constraints_spec_for_trusty_vm();
266 // Create policy 2 given constraints spec 2 and DICE chain 2
267 let policy_2 = Policy(
268 dice_policy_builder::policy_for_dice_chain(&cert_chain_bytes_2, constraint_spec_2)
269 .expect("failed to building policy 2")
270 .to_vec()
271 .expect("failed to encode policy 2"),
272 );
273 // Match DICE chain 2 with policy 2 and expect a match
274 let result2 = match_dice_chain_with_policy(&cert_chain_2, &policy_2);
275 assert_eq!(result2, Ok(true));
276
277 // Compare policy 1 and policy 2 and expect a non-match
278 assert_ne!(policy_1, policy_2);
279 // Match DICE chain 1 with policy 2 and expect a non-match
280 let result_12 = match_dice_chain_with_policy(&cert_chain_1, &policy_2);
281 assert_eq!(result_12, Ok(false));
282 // Match DICE chain 2 with policy 1 and expect a non-match
283 let result_21 = match_dice_chain_with_policy(&cert_chain_2, &policy_1);
284 assert_eq!(result_21, Ok(false));
285
286 // Create (child) DICE cert entry for DICE chain 1
287 let leaf_cert_bytes_1 = create_dice_leaf_cert(cdi_values_1, "keymint", 1);
288 let leaf_cert_1 =
289 DiceChainEntry::from_slice(&leaf_cert_bytes_1).expect("failed to decode leaf cert 1");
290 // Create a constraints spec for client 1
291 let client_constraint_spec_km = get_constraint_spec_for_static_trusty_ta();
292 // Create the client policy given the constraints spec and DICE cert entry
293 let client_policy_1 = Policy(
294 policy_for_dice_node(&leaf_cert_1, client_constraint_spec_km)
295 .expect("failed to create dice policy for keymint")
296 .to_vec()
297 .expect("failed to encode policy for client TA 1"),
298 );
299 // Match the client's DICE cert entry and policy and expect a match
300 let result_1a = match_dice_cert_with_policy(&leaf_cert_1, &client_policy_1);
301 assert_eq!(result_1a, Ok(true));
302
303 // Create (child) DICE cert entry for DICE chain 2
304 let leaf_cert_bytes_2 = create_dice_leaf_cert(cdi_values_2, "widevine", 1);
305 let leaf_cert_2 =
306 DiceChainEntry::from_slice(&leaf_cert_bytes_2).expect("failed to decode leaf cert 2");
307 // Create constraints spec for client 2
308 let client_constraint_spec_wv = get_constraint_spec_for_static_trusty_ta();
309 // Create client policy giiven the constraints spec and DICE cert entry
310 let client_policy_2 = Policy(
311 policy_for_dice_node(&leaf_cert_2, client_constraint_spec_wv)
312 .expect("failed to create dice policy for widevine")
313 .to_vec()
314 .expect("failed to encode policy for client TA 2"),
315 );
316 // Match the client's DICE cert entry and policy and expect a match
317 let result_2a = match_dice_cert_with_policy(&leaf_cert_2, &client_policy_2);
318 assert_eq!(result_2a, Ok(true));
319
320 // Compare client 1's policy and client 2's policy and expect a non-match
321 assert_ne!(client_policy_1, client_policy_2);
322 // Match client 1's DICE cert entry with client 2's policy and expect a non-match
323 let result_12 = match_dice_cert_with_policy(&leaf_cert_1, &client_policy_2);
324 assert_eq!(result_12, Ok(false));
325 // Match client 2's DICE cert entry with client 1's policy and expect a non-match
326 let result_21 = match_dice_cert_with_policy(&leaf_cert_2, &client_policy_1);
327 assert_eq!(result_21, Ok(false));
328
329 // Extend DICE chain 1 with leaf DICE cert 1
330 let ecdsa = BoringEcDsa;
331 let extended_dice_chain_1 =
332 cert_chain_1.extend_with(&leaf_cert_1, &ecdsa).expect("failed to extend DICE chain 1");
333 // Extend DICE policy 1 with client 1's DICE policy
334 let extended_dice_policy_1 = extend_dice_policy_with(&policy_1, &client_policy_1)
335 .expect("failed to extend DICE policy 1");
336 // Match the extended DICE chain 1 with the extended DICE policy 1 and expect a match
337 let result1 = match_dice_chain_with_policy(&extended_dice_chain_1, &extended_dice_policy_1);
338 assert_eq!(result1, Ok(true));
339
340 // Extend DICE chain 2 with DICE cert entry 2
341 let extended_dice_chain_2 =
342 cert_chain_2.extend_with(&leaf_cert_2, &ecdsa).expect("failed to extend DICE chain 2");
343 // Extend DICE policy 2 with client 2's DICE policy
344 let extended_dice_policy_2 = extend_dice_policy_with(&policy_2, &client_policy_2)
345 .expect("failed to extend DICE policy 2");
346 // Match the extended DICE chain 2 with the extended DICE policy 2 and expect a match
347 let result2 = match_dice_chain_with_policy(&extended_dice_chain_2, &extended_dice_policy_2);
348 assert_eq!(result2, Ok(true));
349
350 // Compare the extended policy 1 and the extended policy 2 and expect a non-match
351 assert_ne!(extended_dice_policy_1, extended_dice_policy_2);
352 // Match the extended DICE chain 1 with the extended policy 2 and expect a non-match
353 let result12 =
354 match_dice_chain_with_policy(&extended_dice_chain_1, &extended_dice_policy_2);
355 assert_eq!(result12, Ok(false));
356 // Match the extended DICE chain 2 with the extended policy 1 and expect a non-match
357 let result21 =
358 match_dice_chain_with_policy(&extended_dice_chain_2, &extended_dice_policy_1);
359 assert_eq!(result21, Ok(false));
360
361 // Test rollback protection via DICE policy:
362 // Create a DICE chain 3 with the same instance hash as DICE chain 1, but with a higher
363 // security version.
364 let (_sign_key_3, _cdi_values_3, cert_chain_bytes_3) =
365 create_dice_cert_chain_for_guest_os(Some(SAMPLE_INSTANCE_HASH), 3);
366 let cert_chain_3 =
367 CertChain::from_slice(&cert_chain_bytes_3).expect("failed to decode cert_chain 3");
368 // Create policy 3 given constraint spec 1 and DICE chain 3
369 let policy_3 = Policy(
370 dice_policy_builder::policy_for_dice_chain(&cert_chain_bytes_3, constraint_spec_1)
371 .expect("failed to building policy 3")
372 .to_vec()
373 .expect("failed to encode policy 3"),
374 );
375 // Match DICE chain 3 to policy 3 and expect a match
376 let result3 = match_dice_chain_with_policy(&cert_chain_3, &policy_3);
377 assert_eq!(result3, Ok(true));
378 // Match DICE chain 3 to policy 1 and expect a match
379 let result31 = match_dice_chain_with_policy(&cert_chain_3, &policy_1);
380 assert_eq!(result31, Ok(true));
381 // Match DICE chain 1 to policy 3 and expect a non-match
382 let result13 = match_dice_chain_with_policy(&cert_chain_1, &policy_3);
383 assert_eq!(result13, Ok(false));
384 }
385
386 // Returns a constraint spec as above, but without the security version check for
387 // the node except the "vm_entry" node
get_relaxed_constraints_spec_for_trusty_vm() -> Vec<ConstraintSpec>388 fn get_relaxed_constraints_spec_for_trusty_vm() -> Vec<ConstraintSpec> {
389 vec![
390 ConstraintSpec::new(
391 ConstraintType::ExactMatch,
392 vec![AUTHORITY_HASH],
393 MissingAction::Fail,
394 TargetEntry::All,
395 ),
396 ConstraintSpec::new(
397 ConstraintType::ExactMatch,
398 vec![MODE],
399 MissingAction::Fail,
400 TargetEntry::All,
401 ),
402 ConstraintSpec::new(
403 ConstraintType::ExactMatch,
404 vec![CONFIG_DESC, INSTANCE_HASH],
405 MissingAction::Fail,
406 TargetEntry::ByName(GUEST_OS_COMPONENT_NAME.to_string()),
407 ),
408 ConstraintSpec::new(
409 ConstraintType::GreaterOrEqual,
410 vec![CONFIG_DESC, SECURITY_VERSION],
411 MissingAction::Fail,
412 TargetEntry::ByName(GUEST_OS_COMPONENT_NAME.to_string()),
413 ),
414 ]
415 }
416 }
417