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