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 //! Implementation of the structures defined in hardware/interfaces/security/see/authmgr/aidl/ 18 //! android/hardware/security/see/authmgr/SignedConnectionRequest.cddl 19 20 use crate::{amc_err, Error, ErrorCode, Result}; 21 use alloc::vec::Vec; 22 use authgraph_core::key::{EcSignKey, EcVerifyKey}; 23 use authgraph_core::traits::EcDsa; 24 use coset::iana::Algorithm; 25 use coset::{ 26 cbor::Value, AsCborValue, CborSerializable, CoseError, CoseSign1, CoseSign1Builder, 27 HeaderBuilder, Result as CoseResult, TaggedCborSerializable, 28 }; 29 use num_derive::FromPrimitive; 30 use num_traits::FromPrimitive; 31 32 // According to the SignedConnectionRequest.cddl; 33 const CBOR_CONNECTION_REQUEST_ARRAY_LEN: usize = 3; 34 35 // According to the SignedConnectionRequest.cddl; 36 const CBOR_FFA_TRANSPORT_ID_ARRAY_LEN: usize = 2; 37 38 // Tag for the CBOR type buuid = #6.37(bstr .size 16) - see RFC8610 - Section 3.6 39 const BUUID_TAG: u64 = 37; 40 41 /// The unique id assigned to the `ConnectionRequest` payload structure 42 pub const CONNECTION_REQUEST_UUID: [u8; 16] = [ 43 0x34, 0xc8, 0x29, 0x16, 0x95, 0x79, 0x4d, 0x86, 0xba, 0xef, 0x59, 0x2a, 0x06, 0x64, 0x19, 0xe4, 44 ]; 45 46 /// Type alias for a cryptographic random of 32 bytes 47 pub type Challenge = [u8; 32]; 48 49 /// Type alias for transport id (a.k.a. VM ID) 50 pub type TransportID = u16; 51 52 /// This is a temporary measure to use a hardcoded transport id for AuthMgr BE. 53 /// TODO: b/392905377 54 pub const TEMP_AUTHMGR_BE_TRANSPORT_ID: u16 = u16::from_le_bytes([0x24, 0x12]); 55 /// This is a temporary measure to use a hardcoded transport id for AuthMgr FE. 56 /// TODO: b/392905377 57 pub const TEMP_AUTHMGR_FE_TRANSPORT_ID: u16 = u16::from_le_bytes([0x06, 0x12]); 58 59 /// External AAD to be added to any Sig_structure signed by the DICE signing key, with the mandatory 60 /// field of `uuid_of_payload_struct` of type UUID v4 (RFC 9562). This field is required to ensure 61 /// that both the signer and the verifier refer to the same payload structure, given that there are 62 /// various payload structures signed by the DICE signing key in different protocols in Android. 63 pub struct ExternalAADForDICESigned { 64 uuid_of_payload_struct: [u8; 16], 65 } 66 67 impl AsCborValue for ExternalAADForDICESigned { from_cbor_value(value: Value) -> CoseResult<Self>68 fn from_cbor_value(value: Value) -> CoseResult<Self> { 69 if let Value::Bytes(uuid) = value { 70 return Ok(Self { 71 uuid_of_payload_struct: uuid.as_slice().try_into().map_err(|_e| { 72 CoseError::UnexpectedItem("an invalid vector of bytes", "A valid UUID [u8; 16]") 73 })?, 74 }); 75 } 76 Err(CoseError::UnexpectedItem("a vector of 16 bytes", "Cbor value of non-bytes")) 77 } 78 to_cbor_value(self) -> CoseResult<Value>79 fn to_cbor_value(self) -> CoseResult<Value> { 80 Ok(Value::Bytes(self.uuid_of_payload_struct.to_vec())) 81 } 82 } 83 84 impl CborSerializable for ExternalAADForDICESigned {} 85 86 impl TaggedCborSerializable for ExternalAADForDICESigned { 87 const TAG: u64 = BUUID_TAG; 88 } 89 90 /// An integer that identifies the type of the transport used for communication between clients and 91 /// trusted services 92 #[repr(u8)] 93 #[derive(Clone, Debug, FromPrimitive, PartialEq)] 94 pub enum TransportType { 95 /// FFA transport 96 FFA = 1, 97 } 98 99 /// Identity information of the peers provided by the transport layer 100 #[derive(Clone, Debug, PartialEq)] 101 pub enum TransportIdInfo { 102 /// FFA transport identity information of the peers 103 FFATransportId { 104 /// AuthMgr FE's transport ID (a.k.a VM ID) 105 fe_id: TransportID, 106 /// AuthMgr BE's transport ID (a.k.a VM ID) 107 be_id: TransportID, 108 }, 109 } 110 111 /// The payload structure signed by the DICE signing key for authentication 112 #[derive(Clone, Debug, PartialEq)] 113 pub struct ConnectionRequest { 114 challenge: Challenge, 115 transport_type: TransportType, 116 transport_id_info: TransportIdInfo, 117 } 118 119 impl AsCborValue for ConnectionRequest { from_cbor_value(value: Value) -> CoseResult<Self>120 fn from_cbor_value(value: Value) -> CoseResult<Self> { 121 let mut connection_req_cbor_array = match value { 122 Value::Array(a) if a.len() == CBOR_CONNECTION_REQUEST_ARRAY_LEN => a, 123 _ => { 124 return Err(CoseError::UnexpectedItem("_", "array with three items")); 125 } 126 }; 127 let transport_id_info_cbor_val = connection_req_cbor_array.remove(2); 128 let transport_type = match connection_req_cbor_array.remove(1) { 129 Value::Integer(ttype) => (TransportType::from_u8(ttype.try_into()?)) 130 .ok_or(CoseError::UnexpectedItem("_", "valid integer for transport id"))?, 131 _ => { 132 return Err(CoseError::UnexpectedItem("_", "Integer")); 133 } 134 }; 135 let transport_id_info = match transport_type { 136 TransportType::FFA => { 137 let mut transport_id_info_cbor_array = match transport_id_info_cbor_val { 138 Value::Array(a) if a.len() == CBOR_FFA_TRANSPORT_ID_ARRAY_LEN => a, 139 _ => { 140 return Err(CoseError::UnexpectedItem("_", "array with two items")); 141 } 142 }; 143 let be_id: TransportID = match transport_id_info_cbor_array.remove(1) { 144 Value::Integer(i) => i.try_into()?, 145 _ => { 146 return Err(CoseError::UnexpectedItem("_", "Integer")); 147 } 148 }; 149 let fe_id: TransportID = match transport_id_info_cbor_array.remove(0) { 150 Value::Integer(i) => i.try_into()?, 151 _ => { 152 return Err(CoseError::UnexpectedItem("_", "Integer")); 153 } 154 }; 155 TransportIdInfo::FFATransportId { fe_id, be_id } 156 } 157 }; 158 159 let challenge: Challenge = match connection_req_cbor_array.remove(0) { 160 Value::Bytes(c) => c.as_slice().try_into().map_err(|_e| { 161 CoseError::UnexpectedItem("array of bytes with incorrect length", "[u8; 32]") 162 })?, 163 _ => { 164 return Err(CoseError::UnexpectedItem( 165 "a cbor value that is not bytes", 166 "[u8; 32]", 167 )); 168 } 169 }; 170 Ok(Self { challenge, transport_type, transport_id_info }) 171 } 172 to_cbor_value(self) -> CoseResult<Value>173 fn to_cbor_value(self) -> CoseResult<Value> { 174 let mut array = Vec::<Value>::new(); 175 array 176 .try_reserve(CBOR_CONNECTION_REQUEST_ARRAY_LEN) 177 .map_err(|_| CoseError::EncodeFailed)?; 178 array.push(Value::Bytes(self.challenge.to_vec())); 179 array.push(Value::Integer((self.transport_type as u8).into())); 180 match self.transport_id_info { 181 TransportIdInfo::FFATransportId { fe_id, be_id } => { 182 let mut arr = Vec::<Value>::new(); 183 arr.try_reserve(CBOR_FFA_TRANSPORT_ID_ARRAY_LEN) 184 .map_err(|_| CoseError::EncodeFailed)?; 185 arr.push(Value::Integer(fe_id.into())); 186 arr.push(Value::Integer(be_id.into())); 187 array.push(Value::Array(arr)) 188 } 189 } 190 Ok(Value::Array(array)) 191 } 192 } 193 194 impl CborSerializable for ConnectionRequest {} 195 196 impl ConnectionRequest { 197 /// Create a new connection request on top of FFA transport new_for_ffa_transport( challenge: Challenge, fe_id: TransportID, be_id: TransportID, ) -> Self198 pub fn new_for_ffa_transport( 199 challenge: Challenge, 200 fe_id: TransportID, 201 be_id: TransportID, 202 ) -> Self { 203 Self { 204 challenge, 205 transport_type: TransportType::FFA, 206 transport_id_info: TransportIdInfo::FFATransportId { fe_id, be_id }, 207 } 208 } 209 210 /// Sign the given `ConnectionRequest`. We use the EcDsa trait from the authgraph traits. 211 /// Note: for now, we expect the EcSignKey to be passed into this method, but for the 212 /// environments where the private signing key cannot be passed directly, we need to introduce 213 /// something similar to the `sign_data` trait method in the authgraph `Device` trait. sign( self, signing_key: &EcSignKey, ecdsa: &dyn EcDsa, cose_sign_algorithm: Algorithm, ) -> Result<Vec<u8>>214 pub fn sign( 215 self, 216 signing_key: &EcSignKey, 217 ecdsa: &dyn EcDsa, 218 cose_sign_algorithm: Algorithm, 219 ) -> Result<Vec<u8>> { 220 let conn_req_encoded = self.to_vec()?; 221 let aad_encoded = 222 ExternalAADForDICESigned { uuid_of_payload_struct: CONNECTION_REQUEST_UUID } 223 .to_vec()?; 224 let protected = HeaderBuilder::new().algorithm(cose_sign_algorithm).build(); 225 let cose_sign1 = CoseSign1Builder::new() 226 .protected(protected) 227 .try_create_detached_signature(&conn_req_encoded, &aad_encoded, |data| { 228 ecdsa.sign(signing_key, data) 229 }) 230 .map_err(|e| amc_err!(SigningFailed, "failed to sign data: {:?}", e))? 231 .build(); 232 Ok(cose_sign1.to_vec()?) 233 } 234 235 /// Verify a signature on the given `ConnectionRequest`. verify( self, signature: &[u8], verify_key: &EcVerifyKey, ecdsa: &dyn EcDsa, ) -> Result<()>236 pub fn verify( 237 self, 238 signature: &[u8], 239 verify_key: &EcVerifyKey, 240 ecdsa: &dyn EcDsa, 241 ) -> Result<()> { 242 let cose_sign1 = CoseSign1::from_slice(signature)?; 243 let conn_req_encoded = self.to_vec()?; 244 let aad_encoded = 245 ExternalAADForDICESigned { uuid_of_payload_struct: CONNECTION_REQUEST_UUID } 246 .to_vec()?; 247 verify_key.validate_cose_key_params().map_err(|e| { 248 amc_err!(SignatureVerificationFailed, "failed to validate verification key: {:?}", e) 249 })?; 250 cose_sign1 251 .verify_detached_signature(&conn_req_encoded, &aad_encoded, |signature, data| { 252 ecdsa.verify_signature(verify_key, data, signature) 253 }) 254 .map_err(|e| { 255 amc_err!(SignatureVerificationFailed, "failed to verify signature: {:?}", e) 256 }) 257 } 258 } 259 260 #[cfg(test)] 261 mod tests { 262 use super::*; 263 use authgraph_boringssl::ec::BoringEcDsa; 264 use authgraph_boringssl::BoringRng; 265 use authgraph_core::key::CertChain; 266 use authgraph_core::traits::Rng; 267 use authgraph_core_test::create_dice_cert_chain_for_guest_os; 268 269 #[test] test_cbor_enc_dec_connection_request()270 fn test_cbor_enc_dec_connection_request() { 271 let conn_req = ConnectionRequest { 272 challenge: [1; 32], 273 transport_type: TransportType::FFA, 274 transport_id_info: TransportIdInfo::FFATransportId { fe_id: 16, be_id: 30 }, 275 }; 276 let encoded_conn_req = conn_req.clone().to_vec().expect("cbor encoding failed."); 277 let decoded_conn_req = 278 ConnectionRequest::from_slice(&encoded_conn_req).expect("cbor decoding failed."); 279 assert_eq!(conn_req, decoded_conn_req); 280 } 281 #[test] test_sign_verify_connection_request()282 fn test_sign_verify_connection_request() { 283 // Create a cert chain with a private key 284 let (sign_key, _, cert_chain_bytes) = create_dice_cert_chain_for_guest_os(None, 1); 285 // Retrieve the public signing key from the cert chain 286 let cert_chain = 287 CertChain::from_slice(&cert_chain_bytes).expect("failed to decode the dice chain"); 288 let ecdsa = BoringEcDsa; 289 let verify_key = cert_chain.validate(&ecdsa).expect("failed to validate the cert chain"); 290 let signing_algorithm = verify_key.get_cose_sign_algorithm(); 291 // Sign the connection request 292 let rng = BoringRng; 293 let mut challenge = [0u8; 32]; 294 rng.fill_bytes(&mut challenge); 295 let fe_id = u16::from_le_bytes([0x34, 0x12]); 296 let be_id = u16::from_le_bytes([0x24, 0x13]); 297 let conn_req = ConnectionRequest { 298 challenge, 299 transport_type: TransportType::FFA, 300 transport_id_info: TransportIdInfo::FFATransportId { fe_id, be_id }, 301 }; 302 let signature = conn_req 303 .clone() 304 .sign(&sign_key, &ecdsa, signing_algorithm) 305 .expect("failure in signing"); 306 // Verify 307 assert!(conn_req.verify(&signature, &verify_key, &ecdsa).is_ok()); 308 // Try to verify signature against a different connection request and expect failure 309 let mut challenge_d = [0u8; 32]; 310 rng.fill_bytes(&mut challenge_d); 311 let conn_req_d = ConnectionRequest { 312 challenge: challenge_d, 313 transport_type: TransportType::FFA, 314 transport_id_info: TransportIdInfo::FFATransportId { fe_id, be_id }, 315 }; 316 assert!(conn_req_d.verify(&signature, &verify_key, &ecdsa).is_err()) 317 } 318 } 319