• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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