1 use crate::cbor::field_value::FieldValue;
2 use crate::cbor::value_from_bytes;
3 use crate::dice::ChainForm;
4 use crate::eek;
5 use crate::publickey::{KeyAgreementPublicKey, PublicKey};
6 use crate::rkp::{ProtectedData, UdsCerts, UdsCertsEntry};
7 use crate::session::Session;
8 use anyhow::{anyhow, bail, Context, Result};
9 use ciborium::value::Value;
10 use coset::{iana, Algorithm, AsCborValue, CoseEncrypt, CoseKey, CoseRecipient, CoseSign1, Label};
11 use openssl::cipher::Cipher;
12 use openssl::cipher_ctx::CipherCtx;
13 use openssl::pkey::{Id, PKey, Private};
14 
15 const COSE_RECIPIENT_PUBKEY_LABEL: i64 = -1;
16 
17 impl ProtectedData {
from_cose_encrypt( session: &Session, protected_data: CoseEncrypt, challenge: &[u8], verified_device_info: &Value, tag: &[u8], ) -> Result<ProtectedData>18     pub(crate) fn from_cose_encrypt(
19         session: &Session,
20         protected_data: CoseEncrypt,
21         challenge: &[u8],
22         verified_device_info: &Value,
23         tag: &[u8],
24     ) -> Result<ProtectedData> {
25         let (recipient, eek) = Self::match_recipient(&protected_data.recipients)?;
26 
27         let mut headers = recipient.unprotected.rest;
28         let pubkey_index = headers
29             .iter()
30             .position(|p| p.0 == Label::Int(COSE_RECIPIENT_PUBKEY_LABEL))
31             .context("Unable to locate public key in COSE_encrypt recipients.")?;
32         let mut pubkey_cose = match CoseKey::from_cbor_value(headers.remove(pubkey_index).1) {
33             Ok(key) => key,
34             Err(e) => bail!("Error converting CBOR into COSE_key: {:?}", e),
35         };
36 
37         Self::work_around_recipient_key_missing_alg(&mut pubkey_cose, &eek)?;
38 
39         let pubkey = KeyAgreementPublicKey::from_cose_key(&pubkey_cose)?;
40         let encryption_key = eek::derive_ephemeral_symmetric_key(&eek, pubkey.pkey())
41             .with_context(|| format!("for pubkey {:?}", pubkey_cose))?;
42 
43         let protected_data_plaintext = protected_data
44             .decrypt(&[], |ciphertext, aad| {
45                 let (ciphertext, tag) = ciphertext.split_at(ciphertext.len() - 16);
46                 let mut plaintext = Vec::new();
47                 let mut ctx = CipherCtx::new().context("Unable to load cipher context")?;
48                 // decrypt_init must be called twice because our IV is not 12 bytes, which is what
49                 // AES-GCM wants by default. The first init tells openssl the cipher+mode, then we
50                 // can tell openssl the IV len, and only after that can we set the non-standard IV.
51                 ctx.decrypt_init(Some(Cipher::aes_256_gcm()), Some(&encryption_key), None)?;
52                 ctx.set_iv_length(protected_data.unprotected.iv.len())?;
53                 ctx.decrypt_init(None, None, Some(&protected_data.unprotected.iv))?;
54                 ctx.set_tag(tag)?;
55                 ctx.cipher_update(aad, None).context("Error setting AAD on cipher")?;
56                 ctx.cipher_update_vec(ciphertext, &mut plaintext)
57                     .context("Error decrypting ciphertext")?;
58                 ctx.cipher_final_vec(&mut plaintext).context("Error finalizing decryption")?;
59                 Ok::<Vec<u8>, anyhow::Error>(plaintext)
60             })
61             .context("while decrypting ProtectedData")?;
62 
63         Self::from_cbor_bytes(
64             session,
65             &protected_data_plaintext,
66             challenge,
67             verified_device_info,
68             tag,
69         )
70     }
71 
from_cbor_bytes( session: &Session, plaintext_cbor: &[u8], challenge: &[u8], verified_device_info: &Value, tag: &[u8], ) -> Result<Self>72     fn from_cbor_bytes(
73         session: &Session,
74         plaintext_cbor: &[u8],
75         challenge: &[u8],
76         verified_device_info: &Value,
77         tag: &[u8],
78     ) -> Result<Self> {
79         let mut array = match value_from_bytes(plaintext_cbor) {
80             Ok(Value::Array(a)) => a,
81             Ok(other) => bail!("Expected array for ProtectedDataPayload, found {other:?}"),
82             Err(e) => bail!(
83                 "Error '{e:?}' parsing ProtectedDataPayload '{}'",
84                 hex::encode(plaintext_cbor)
85             ),
86         };
87 
88         if array.len() != 2 && array.len() != 3 {
89             bail!("ProtectedDataPayload size must be 2 or 3, found {array:?}");
90         }
91 
92         // pull items out in reverse order to avoid shifting the vector
93         let uds_certs = if array.len() != 3 {
94             None
95         } else {
96             let uds_certs_field = FieldValue::from_optional_value("UdsCerts", array.pop());
97             Self::to_uds_certs(uds_certs_field.into_map()?)?
98         };
99 
100         let dice_chain =
101             Value::Array(FieldValue::from_optional_value("DiceChain", array.pop()).into_array()?);
102         let dice_chain = ChainForm::from_value(session, dice_chain)?;
103 
104         let mac_key = Self::validate_mac_key(
105             challenge,
106             verified_device_info,
107             tag,
108             FieldValue::from_optional_value("SignedMac", array.pop()).into_cose_sign1()?,
109             dice_chain.leaf_public_key(),
110         )?;
111 
112         Ok(ProtectedData::new(mac_key, dice_chain, uds_certs))
113     }
114 
validate_mac_key( challenge: &[u8], verified_device_info: &Value, tag: &[u8], signed_mac: CoseSign1, signer: &PublicKey, ) -> Result<Vec<u8>>115     fn validate_mac_key(
116         challenge: &[u8],
117         verified_device_info: &Value,
118         tag: &[u8],
119         signed_mac: CoseSign1,
120         signer: &PublicKey,
121     ) -> Result<Vec<u8>> {
122         let mut aad: Vec<u8> = vec![];
123         ciborium::ser::into_writer(
124             // This can be optimized if/when ciborium exposes lower-level serialization routines
125             &Value::Array(vec![
126                 Value::Bytes(challenge.to_vec()),
127                 verified_device_info.clone(),
128                 Value::Bytes(tag.to_vec()),
129             ]),
130             &mut aad,
131         )?;
132         signer.verify_cose_sign1(&signed_mac, &aad).context("verifying signed MAC")?;
133         signed_mac.payload.ok_or(anyhow!("SignedMac is missing the payload"))
134     }
135 
to_uds_certs(kv_pairs: Vec<(Value, Value)>) -> Result<Option<UdsCerts>>136     fn to_uds_certs(kv_pairs: Vec<(Value, Value)>) -> Result<Option<UdsCerts>> {
137         if kv_pairs.is_empty() {
138             return Ok(None);
139         }
140 
141         let mut uds_certs = UdsCerts::new();
142         for pair in kv_pairs {
143             match pair {
144                 (Value::Text(signer), value) => uds_certs.add_signer(signer, value)?,
145                 (k, v) => bail!("Expected (string, value), but found ({k:?}, {v:?}"),
146             }
147         }
148         Ok(Some(uds_certs))
149     }
150 
work_around_recipient_key_missing_alg( cose_key: &mut CoseKey, eek: &PKey<Private>, ) -> Result<()>151     fn work_around_recipient_key_missing_alg(
152         cose_key: &mut CoseKey,
153         eek: &PKey<Private>,
154     ) -> Result<()> {
155         let cose_alg = match eek.id() {
156             Id::X25519 => iana::Algorithm::ECDH_ES_HKDF_256,
157             Id::EC if eek.bits() == 256 => iana::Algorithm::ES256,
158             other => bail!("Unsupported EEK: {:?}, key size: {}", other, eek.bits()),
159         };
160 
161         match &cose_key.alg {
162             None => cose_key.alg = Some(Algorithm::Assigned(cose_alg)),
163             Some(Algorithm::Assigned(alg)) if *alg == cose_alg => (),
164             Some(Algorithm::Assigned(alg)) => {
165                 bail!("Algorithm mismatch between EEK ({cose_alg:?}) and recipient ({alg:?})")
166             }
167             other => bail!("COSE_Encrypt recipient pubkey has unexpected algorithm: {other:?}"),
168         }
169         Ok(())
170     }
171 
172     /// Look through a set of COSE_recipients to see if any of them match a known EEK. If so,V
173     /// return the matching recipieint and EEK to the caller so they can perform key agreement.
match_recipient(recipients: &Vec<CoseRecipient>) -> Result<(CoseRecipient, PKey<Private>)>174     fn match_recipient(recipients: &Vec<CoseRecipient>) -> Result<(CoseRecipient, PKey<Private>)> {
175         for r in recipients {
176             if r.unprotected.key_id == eek::X25519_EEK_ID {
177                 return Ok((r.clone(), eek::x25519_geek()));
178             } else if r.unprotected.key_id == eek::P256_EEK_ID {
179                 return Ok((r.clone(), eek::p256_geek()));
180             }
181         }
182         Err(anyhow!("Unable to locate a COSE_recipient matching any known EEK"))
183     }
184 }
185 
186 impl UdsCerts {
add_signer(&mut self, signer: String, data: Value) -> Result<()>187     pub fn add_signer(&mut self, signer: String, data: Value) -> Result<()> {
188         // For now, assume all signers are using x.509 certs. This may change in the future for
189         // platforms that need custom certification mechanisms for UDS_pub.
190         match self.0.get_mut(&signer) {
191             Some(_) => bail!("Signer '{signer}' entry found twice in the UdsCerts"),
192             None => self.0.insert(signer, UdsCertsEntry::from_cbor_value(data)?),
193         };
194         Ok(())
195     }
196 }
197 
198 impl UdsCertsEntry {
from_cbor_value(data: Value) -> Result<Self>199     fn from_cbor_value(data: Value) -> Result<Self> {
200         match data {
201             Value::Array(certs) => {
202                 let mut cert_buffers = vec![];
203                 for cert in certs {
204                     match cert {
205                         Value::Bytes(b) => cert_buffers.push(b),
206                         other => bail!("Expected UDS cert byte array found '{other:?}'"),
207                     }
208                 }
209                 UdsCertsEntry::new_x509_chain(cert_buffers)
210             }
211             other => Err(anyhow!("Expected CBOR array of certificates, found {other:?}")),
212         }
213     }
214 }
215