1 use std::collections::HashMap; 2 3 use crate::cbor::field_value::FieldValue; 4 use crate::cbor::{canonicalize_map, serialize, value_from_bytes}; 5 use crate::dice::ChainForm; 6 use crate::rkp::{Csr, CsrPayload, DeviceInfo, DeviceInfoVersion, KeysToSign, ProtectedData}; 7 use crate::session::{RkpInstance, Session}; 8 use anyhow::{anyhow, bail, ensure, Context, Result}; 9 use base64::{prelude::BASE64_STANDARD, Engine}; 10 use ciborium::value::Value; 11 use coset::{AsCborValue, CoseKey}; 12 use openssl::pkey::Id; 13 use openssl::stack::Stack; 14 use openssl::x509::store::X509StoreBuilder; 15 use openssl::x509::verify::X509VerifyFlags; 16 use openssl::x509::{X509StoreContext, X509}; 17 18 const VERSION_OR_DEVICE_INFO_INDEX: usize = 0; 19 20 impl KeysToSign { from_bytes(buffer: &[u8]) -> Result<Self>21 pub(crate) fn from_bytes(buffer: &[u8]) -> Result<Self> { 22 let value = value_from_bytes(buffer)?; 23 let field_value = FieldValue::from_value("KeysToSign", value); 24 Self::from_value(field_value) 25 } from_value(value: FieldValue) -> Result<KeysToSign>26 fn from_value(value: FieldValue) -> Result<KeysToSign> { 27 Ok(KeysToSign( 28 value.into_array()?.into_iter().map(|v| CoseKey::from_cbor_value(v).unwrap()).collect(), 29 )) 30 } 31 } 32 33 impl CsrPayload { from_value(value: &Value, session: &Session) -> Result<Self>34 fn from_value(value: &Value, session: &Session) -> Result<Self> { 35 let serialized = match value.clone().into_bytes() { 36 Ok(bytes) => bytes, 37 Err(_) => bail!("CsrPayload had no bytes"), 38 }; 39 40 let mut csr_payload = match value_from_bytes(serialized.as_slice())? { 41 Value::Array(a) => a, 42 other => bail!("CsrPayload is expected to be an array, found {other:?}"), 43 }; 44 45 let keys_to_sign = FieldValue::from_optional_value("KeysToSign", csr_payload.pop()); 46 let device_info = FieldValue::from_optional_value("DeviceInfo", csr_payload.pop()); 47 let certificate_type = 48 FieldValue::from_optional_value("CertificateType", csr_payload.pop()); 49 let version = FieldValue::from_optional_value("Version", csr_payload.pop()).into_u64()?; 50 if version != 3 { 51 bail!("Invalid CsrPayload version. Only '3' is supported"); 52 } 53 54 let certificate_type = certificate_type.into_string()?; 55 56 const CERTIFICATE_TYPE_RKPVM: &str = "rkp-vm"; 57 match session.options.rkp_instance { 58 RkpInstance::Avf => ensure!( 59 CERTIFICATE_TYPE_RKPVM == certificate_type, 60 "CertificateType must be 'rkp-vm' for AVF" 61 ), 62 _ => ensure!( 63 CERTIFICATE_TYPE_RKPVM != certificate_type, 64 "CertificateType must not be 'rkp-vm' for non-AVF" 65 ), 66 } 67 68 let device_info = DeviceInfo::from_cbor_values( 69 device_info.into_map()?, 70 Some(DeviceInfoVersion::V3), 71 session.options.is_factory, 72 )?; 73 let keys_to_sign = KeysToSign::from_value(keys_to_sign)?; 74 75 Ok(CsrPayload { serialized, certificate_type, device_info, keys_to_sign }) 76 } 77 } 78 79 impl Csr { 80 /// Parse base64-encoded CBOR data as a Certificate Signing Request. from_base64_cbor<S: AsRef<[u8]>>(session: &Session, base64: &S) -> Result<Self>81 pub fn from_base64_cbor<S: AsRef<[u8]>>(session: &Session, base64: &S) -> Result<Self> { 82 let cbor: Vec<u8> = BASE64_STANDARD.decode(base64).context("invalid base64 CSR")?; 83 Self::from_cbor(session, cbor.as_slice()) 84 } 85 86 /// Read and parse CBOR data as a Certificate Signing Request. from_cbor<S: std::io::Read>(session: &Session, cbor: S) -> Result<Self>87 pub fn from_cbor<S: std::io::Read>(session: &Session, cbor: S) -> Result<Self> { 88 let value: Value = ciborium::de::from_reader(cbor).context("invalid CBOR")?; 89 let mut array = match value { 90 Value::Array(a) if a.is_empty() => bail!("CSR CBOR is an empty array"), 91 Value::Array(a) => a, 92 other => bail!("expected array, found {other:?}"), 93 }; 94 let version_or_device_info = 95 std::mem::replace(&mut array[VERSION_OR_DEVICE_INFO_INDEX], Value::Null); 96 match version_or_device_info { 97 Value::Array(device_info) => Self::v2_from_cbor_values(session, array, device_info), 98 Value::Integer(i) => Self::v3_from_authenticated_request(session, array, i.into()), 99 other => Err(anyhow!( 100 "Expected integer or array at index {VERSION_OR_DEVICE_INFO_INDEX}, \ 101 found {other:?}" 102 )), 103 } 104 } 105 v2_from_cbor_values( session: &Session, mut csr: Vec<Value>, mut device_info: Vec<Value>, ) -> Result<Self>106 fn v2_from_cbor_values( 107 session: &Session, 108 mut csr: Vec<Value>, 109 mut device_info: Vec<Value>, 110 ) -> Result<Self> { 111 let maced_keys_to_sign = 112 FieldValue::from_optional_value("MacedKeysToSign", csr.pop()).into_cose_mac0()?; 113 let encrypted_protected_data = 114 FieldValue::from_optional_value("ProtectedData", csr.pop()).into_cose_encrypt()?; 115 let challenge = FieldValue::from_optional_value("Challenge", csr.pop()).into_bytes()?; 116 117 ensure!(device_info.len() == 2, "Device info should contain exactly 2 entries"); 118 device_info.pop(); // ignore unverified info 119 let device_info = device_info.pop().unwrap(); 120 let device_info_serialized = serialize(device_info.clone()); 121 let device_info_canonicalized = canonicalize_map(device_info.clone())?; 122 if device_info_canonicalized != device_info_serialized { 123 match session.options.verbose { 124 true => bail!( 125 "Device info is not canonical:\nexpected: {:?}\nactual: {:?}", 126 &hex::encode(device_info_canonicalized), 127 &hex::encode(device_info_serialized) 128 ), 129 false => bail!("Device info is not canonical"), 130 } 131 } 132 let verified_device_info = match device_info { 133 Value::Map(d) => Value::Map(d), 134 other => bail!("Expected a map for verified device info, found '{:?}'", other), 135 }; 136 137 let protected_data = ProtectedData::from_cose_encrypt( 138 session, 139 encrypted_protected_data, 140 &challenge, 141 &verified_device_info, 142 &maced_keys_to_sign.tag, 143 )?; 144 145 let verified_device_info = match verified_device_info { 146 Value::Map(m) => m, 147 _ => unreachable!("verified device info is always a map"), 148 }; 149 150 Ok(Self::V2 { 151 device_info: DeviceInfo::from_cbor_values( 152 verified_device_info, 153 None, // version must be determined by "version" in DeviceInfo 154 session.options.is_factory, 155 )?, 156 challenge, 157 protected_data, 158 }) 159 } 160 v3_from_authenticated_request( session: &Session, mut csr: Vec<Value>, version: i128, ) -> Result<Self>161 fn v3_from_authenticated_request( 162 session: &Session, 163 mut csr: Vec<Value>, 164 version: i128, 165 ) -> Result<Self> { 166 if version != 1 { 167 bail!( 168 "Invalid AuthenticatedRequest version. Only '1' is supported, found '{}", 169 version 170 ); 171 } 172 173 // CSRs that are uploaded to the backend have an additional unverified info field tacked 174 // onto them. We just ignore that, so if it's there pop it and move on. 175 if csr.len() == 5 { 176 FieldValue::from_optional_value("UnverifiedDeviceInfo", csr.pop()); 177 } 178 if csr.len() != 4 { 179 bail!("AuthenticatedRequest should have 4 elements. Found {}.", csr.len()); 180 } 181 182 let signed_data = 183 FieldValue::from_optional_value("SignedData", csr.pop()).into_cose_sign1()?; 184 let raw_dice_chain = csr.pop().ok_or(anyhow!("Missing DiceCertChain"))?; 185 let uds_certs = FieldValue::from_optional_value("UdsCerts", csr.pop()).into_map()?; 186 187 let dice_chain = ChainForm::from_value(session, raw_dice_chain)?; 188 let uds_certs = Self::parse_and_validate_uds_certs(&dice_chain, uds_certs)?; 189 190 let signing_key = dice_chain.leaf_public_key(); 191 signing_key.verify_cose_sign1(&signed_data, &[]).context("verifying SignedData failed")?; 192 193 let signed_data_payload = signed_data.payload.context("missing payload in SignedData")?; 194 195 let mut signed_data_value = value_from_bytes(signed_data_payload.as_slice()) 196 .context("SignedData is not valid CBOR")?; 197 198 let signed_data_array = 199 signed_data_value.as_array_mut().context("SignedData is not a CBOR array")?; 200 201 let csr_payload_value = 202 signed_data_array.pop().context("Missing CsrPayload in SignedData")?; 203 204 let csr_payload = CsrPayload::from_value(&csr_payload_value, session) 205 .context("Unable to parse CsrPayload")?; 206 207 let challenge = match signed_data_array.pop().context("missing challenge")?.into_bytes() { 208 Ok(challenge) => challenge, 209 Err(_) => bail!("Challenge is not bytes"), 210 }; 211 212 Ok(Self::V3 { dice_chain, uds_certs, challenge, csr_payload }) 213 } 214 parse_and_validate_uds_certs( dice_chain: &ChainForm, uds_certs: Vec<(Value, Value)>, ) -> Result<HashMap<String, Vec<X509>>>215 fn parse_and_validate_uds_certs( 216 dice_chain: &ChainForm, 217 uds_certs: Vec<(Value, Value)>, 218 ) -> Result<HashMap<String, Vec<X509>>> { 219 let expected_uds = match dice_chain { 220 ChainForm::Degenerate(chain) => chain.public_key(), 221 ChainForm::Proper(chain) => chain.root_public_key(), 222 } 223 .pkey(); 224 225 let mut parsed = HashMap::new(); 226 for (signer, der_certs) in uds_certs { 227 let signer = FieldValue::from_value("SignerName", signer).into_string()?; 228 let x509_certs = FieldValue::from_value("UdsCertChain", der_certs) 229 .into_array()? 230 .into_iter() 231 .map(|v| match FieldValue::from_value("X509Certificate", v).into_bytes() { 232 Ok(b) => X509::from_der(&b).context("Unable to parse DER X509Certificate"), 233 Err(e) => Err(e).context("Invalid type for X509Certificate"), 234 }) 235 .collect::<Result<Vec<X509>>>()?; 236 Self::validate_uds_cert_path(&signer, &x509_certs)?; 237 ensure!( 238 x509_certs.last().unwrap().public_key()?.public_eq(expected_uds), 239 "UdsCert leaf for SignerName '{signer}' does not match the DICE chain root" 240 ); 241 ensure!( 242 parsed.insert(signer.clone(), x509_certs).is_none(), 243 "Duplicate signer found: '{signer}'" 244 ); 245 } 246 Ok(parsed) 247 } 248 validate_uds_cert_path(signer: &String, certs: &Vec<X509>) -> Result<()>249 fn validate_uds_cert_path(signer: &String, certs: &Vec<X509>) -> Result<()> { 250 ensure!( 251 certs.len() > 1, 252 "Certificate chain for signer '{signer}' is too short: {certs:#?}" 253 ); 254 255 for cert in certs { 256 let id = cert.public_key()?.id(); 257 ensure!( 258 matches!(id, Id::RSA | Id::EC | Id::ED25519), 259 "Certificate has an unsupported public algorithm id {id:?}" 260 ); 261 } 262 263 // OpenSSL wants us to split up root trust anchor, leaf, and intermediates 264 let mut certs_copy = certs.clone(); 265 let leaf = certs_copy.pop().unwrap(); 266 let mut intermediates = Stack::new()?; 267 while certs_copy.len() > 1 { 268 intermediates.push(certs_copy.pop().unwrap())?; 269 } 270 let root = certs_copy.pop().unwrap(); 271 272 let mut root_store_builder = X509StoreBuilder::new()?; 273 root_store_builder.add_cert(root)?; 274 // Setting this flag causes the signature on the root certificate to be checked. 275 // This ensures that the root certificate has not been corrupted. 276 root_store_builder.set_flags(X509VerifyFlags::CHECK_SS_SIGNATURE)?; 277 278 let root_store = root_store_builder.build(); 279 280 let mut store = X509StoreContext::new()?; 281 let result = store.init(&root_store, &leaf, &intermediates, |context| { 282 // the with_context function must return Result<T, ErrorStack>, so we have to get 283 // tricky and return Result<Result<()>, ErrorStack> so we can bubble up custom errors. 284 match context.verify_cert() { 285 Ok(true) => (), 286 Ok(false) => return Ok(Err(anyhow!("Cert failed to verify: {}", context.error()))), 287 Err(e) => return Err(e), 288 }; 289 290 if let Some(chain) = context.chain() { 291 // OpenSSL returns the leaf at the bottom of the stack. 292 if !chain.iter().rev().eq(certs.iter()) { 293 let chain: Vec<_> = chain.iter().rev().map(|r| r.to_owned()).collect(); 294 return Ok(Err(anyhow!( 295 "Verified chain doesn't match input: {chain:#?} vs {certs:#?}" 296 ))); 297 } 298 } else { 299 return Ok(Err(anyhow!("Cert chain is missing (impossible!)"))); 300 } 301 Ok(Ok(())) 302 }); 303 304 match result { 305 Ok(e) => e, 306 Err(e) => bail!("Error verifying cert chain: {e:?}"), 307 } 308 } 309 } 310 311 #[cfg(test)] 312 mod tests { 313 // More complete testing happens in the factorycsr module, as the test data 314 // generation spits out full JSON files, not just a CSR. Therefore, only a 315 // minimal number of smoke tests are here. 316 use super::*; 317 use crate::cbor::rkp::csr::testutil::{parse_pem_public_key_or_panic, test_device_info}; 318 use crate::dice::{ChainForm, DegenerateChain, DiceMode}; 319 use crate::rkp::{DeviceInfoSecurityLevel, DeviceInfoVersion}; 320 use crate::session::{Options, Session}; 321 use std::fs; 322 323 #[test] from_base64_valid_v2()324 fn from_base64_valid_v2() { 325 let input = fs::read_to_string("testdata/csr/v2_csr.base64").unwrap().trim().to_owned(); 326 let csr = Csr::from_base64_cbor(&Session::default(), &input).unwrap(); 327 328 let device_info = testutil::test_device_info(DeviceInfoVersion::V2); 329 let challenge = 330 b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10".to_vec(); 331 let pem = "-----BEGIN PUBLIC KEY-----\n\ 332 MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERd9pHZbUJ/b4IleUGDN8fs8+LDxE\n\ 333 vG6VX1dkw0sClFs4imbzfXGbocEq74S7TQiyZkd1LhY6HRZnTC51KoGDIA==\n\ 334 -----END PUBLIC KEY-----\n"; 335 let subject_public_key = testutil::parse_pem_public_key_or_panic(pem); 336 let degenerate = ChainForm::Degenerate( 337 DegenerateChain::new("self-signed", "self-signed", subject_public_key).unwrap(), 338 ); 339 let protected_data = ProtectedData::new(vec![0; 32], degenerate, None); 340 assert_eq!(csr, Csr::V2 { device_info, challenge, protected_data }); 341 } 342 343 #[test] from_base64_valid_v3()344 fn from_base64_valid_v3() { 345 let input = fs::read_to_string("testdata/csr/v3_csr.base64").unwrap().trim().to_owned(); 346 let csr = Csr::from_base64_cbor(&Session::default(), &input).unwrap(); 347 if let Csr::V3 { dice_chain, uds_certs, csr_payload, .. } = csr { 348 assert_eq!(csr_payload.device_info, test_device_info(DeviceInfoVersion::V3)); 349 let root_public_key = parse_pem_public_key_or_panic( 350 "-----BEGIN PUBLIC KEY-----\n\ 351 MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEh5NUV4872vKEL3XPSp8lfkV4AN3J\n\ 352 KJti1Y5kbbR9ucTpSyoOjX9UmBCM/uuPU/MGXMWrgbBf3++02ALzC+V3eQ==\n\ 353 -----END PUBLIC KEY-----\n", 354 ); 355 match dice_chain { 356 ChainForm::Proper(proper_chain) => { 357 assert_eq!(proper_chain.root_public_key(), &root_public_key); 358 let payloads = proper_chain.payloads(); 359 assert_eq!(payloads.len(), 1); 360 assert_eq!(payloads[0].issuer(), "issuer"); 361 assert_eq!(payloads[0].subject(), "subject"); 362 assert_eq!(payloads[0].mode(), DiceMode::Normal); 363 assert_eq!(payloads[0].code_hash(), &[0x55; 32]); 364 let expected_config_hash: &[u8] = 365 b"\xb8\x96\x54\xe2\x2c\xa4\xd2\x4a\x9c\x0e\x45\x11\xc8\xf2\x63\xf0\ 366 \x66\x0d\x2e\x20\x48\x96\x90\x14\xf4\x54\x63\xc4\xf4\x39\x30\x38"; 367 assert_eq!(payloads[0].config_hash(), Some(expected_config_hash)); 368 assert_eq!(payloads[0].authority_hash(), &[0x55; 32]); 369 assert_eq!(payloads[0].config_desc().component_name(), Some("component_name")); 370 assert_eq!(payloads[0].config_desc().component_version(), None); 371 assert!(!payloads[0].config_desc().resettable()); 372 assert_eq!(payloads[0].config_desc().security_version(), None); 373 assert_eq!(payloads[0].config_desc().extensions(), []); 374 } 375 ChainForm::Degenerate(d) => panic!("Parsed chain is not proper: {:?}", d), 376 } 377 assert_eq!(uds_certs.len(), 0); 378 } else { 379 panic!("Parsed CSR was not V3: {:?}", csr); 380 } 381 } 382 383 #[test] from_cbor_valid_v3_with_degenerate_chain() -> anyhow::Result<()>384 fn from_cbor_valid_v3_with_degenerate_chain() -> anyhow::Result<()> { 385 let cbor = fs::read("testdata/csr/v3_csr_degenerate_chain.cbor")?; 386 let session = Session { options: Options::vsr16() }; 387 Csr::from_cbor(&session, cbor.as_slice())?; 388 Ok(()) 389 } 390 391 #[test] from_cbor_valid_v3_avf_with_rkpvm_chain() -> anyhow::Result<()>392 fn from_cbor_valid_v3_avf_with_rkpvm_chain() -> anyhow::Result<()> { 393 let input = fs::read("testdata/csr/v3_csr_avf.cbor")?; 394 let mut session = Session::default(); 395 session.set_allow_any_mode(true); 396 session.set_rkp_instance(RkpInstance::Avf); 397 let csr = Csr::from_cbor(&session, input.as_slice())?; 398 let Csr::V3 { dice_chain, csr_payload, .. } = csr else { 399 panic!("Parsed CSR was not V3: {:?}", csr); 400 }; 401 assert_eq!(csr_payload.device_info.security_level, DeviceInfoSecurityLevel::Avf); 402 let ChainForm::Proper(proper_chain) = dice_chain else { 403 panic!("Parsed chain is not proper: {:?}", dice_chain); 404 }; 405 let expected_len = 7; 406 assert_eq!(proper_chain.payloads().len(), expected_len); 407 assert!(proper_chain.payloads()[expected_len - 1].has_rkpvm_marker()); 408 assert!(proper_chain.payloads()[expected_len - 2].has_rkpvm_marker()); 409 Ok(()) 410 } 411 412 #[test] from_empty_string()413 fn from_empty_string() { 414 let err = Csr::from_base64_cbor(&Session::default(), &"").unwrap_err(); 415 assert!(err.to_string().contains("invalid CBOR")); 416 } 417 418 #[test] from_garbage()419 fn from_garbage() { 420 let err = Csr::from_base64_cbor(&Session::default(), &"cnViYmlzaAo=").unwrap_err(); 421 assert!(err.to_string().contains("invalid CBOR")); 422 } 423 424 #[test] from_invalid_base64()425 fn from_invalid_base64() { 426 let err = Csr::from_base64_cbor(&Session::default(), &"not base64").unwrap_err(); 427 assert!(err.to_string().contains("invalid base64")); 428 } 429 430 const VALID_UDS_CHAIN: &[&str] = &[ 431 "-----BEGIN CERTIFICATE-----\n\ 432 MIICKjCCAbCgAwIBAgIUPFTOIhGtj7sELYJk5HicdV8r/x8wCgYIKoZIzj0EAwMw\n\ 433 RzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ8wDQYDVQQKDAZHb29nbGUxGjAY\n\ 434 BgNVBAMMEVRFU1QgSU5URVJNRURJQVRFMCAXDTI0MTExNDIwMTcwOFoYDzIxMjQx\n\ 435 MDIxMjAxNzA4WjA/MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExDzANBgNVBAoM\n\ 436 Bkdvb2dsZTESMBAGA1UEAwwJVEVTVCBMRUFGMHYwEAYHKoZIzj0CAQYFK4EEACID\n\ 437 YgAEry9HebgpyEnmimjtgs1KN5akdUx6cAEKVwkj0ZkYIW9V+YeRa4ap4yWvh8ZG\n\ 438 U1GA0Eu26z7YQZbPuJ8LnyW0cXj3UGpXgP8EWyftWdz9EX6WpzdO7fuxtxeC/X2l\n\ 439 ZuFIo2MwYTAdBgNVHQ4EFgQUWl8nH6cOAU3IrNZ2kqOzq3JUlukwHwYDVR0jBBgw\n\ 440 FoAUjtSEVIqjzE6pGwlgEHPRn5o9a0YwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B\n\ 441 Af8EBAMCB4AwCgYIKoZIzj0EAwMDaAAwZQIxAMmpFFiMRVnZHZSBCqjWQfA0lqaT\n\ 442 HiusLqIEcAobDy80/mzO2yO6exNjoXkMB17COwIwD1YmiMkaqnnJkan9CNTnBXZB\n\ 443 WNlU9CCE10ohcVfjssl7YVcnna70Rc1UH4DhjSj6\n\ 444 -----END CERTIFICATE-----", 445 "-----BEGIN CERTIFICATE-----\n\ 446 MIICLjCCAbOgAwIBAgIUKIsXCCFZRvz0BYboKmgZjyGArAEwCgYIKoZIzj0EAwMw\n\ 447 PzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ8wDQYDVQQKDAZHb29nbGUxEjAQ\n\ 448 BgNVBAMMCVRFU1QgUk9PVDAgFw0yNDExMTQyMDE1MzVaGA8yMTI0MTAyMTIwMTUz\n\ 449 NVowRzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ8wDQYDVQQKDAZHb29nbGUx\n\ 450 GjAYBgNVBAMMEVRFU1QgSU5URVJNRURJQVRFMHYwEAYHKoZIzj0CAQYFK4EEACID\n\ 451 YgAEFYWPvG5PCQBBXFi/xY1F3MRqDXHkmqdTErc3wlBakVQmCjiklrEalZhMAr5Q\n\ 452 0MYje5/l/ZbN+bvurD5ZsOyWRSrzTkzoUMQszB4fSoJtBp3grcEfd+/tQlC1DZO0\n\ 453 wTROo2YwZDAdBgNVHQ4EFgQUjtSEVIqjzE6pGwlgEHPRn5o9a0YwHwYDVR0jBBgw\n\ 454 FoAUwQ91rFNLmFq9YMlG1bqk7OvWk44wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNV\n\ 455 HQ8BAf8EBAMCAgQwCgYIKoZIzj0EAwMDaQAwZgIxAMWXmsh6d8YSkP1+wR9eMDCe\n\ 456 9G0EFAPOn+BiKfthnnboRUEr8BuIt3w9SkEDCdWfcAIxAMJ99xkGf3bcdykao4jh\n\ 457 bgG844IvDSx11EwzQV/kcteHOut93YO0D83CgkDc2C4dNA==\n\ 458 -----END CERTIFICATE-----", 459 "-----BEGIN CERTIFICATE-----\n\ 460 MIICJTCCAaugAwIBAgIUUo4NdEcdRuQdrm5Trm5x+qvx2LEwCgYIKoZIzj0EAwMw\n\ 461 PzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ8wDQYDVQQKDAZHb29nbGUxEjAQ\n\ 462 BgNVBAMMCVRFU1QgUk9PVDAgFw0yNDExMTQyMDEzNDRaGA8yMTI0MTAyMTIwMTM0\n\ 463 NFowPzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ8wDQYDVQQKDAZHb29nbGUx\n\ 464 EjAQBgNVBAMMCVRFU1QgUk9PVDB2MBAGByqGSM49AgEGBSuBBAAiA2IABOGIoNBS\n\ 465 sVs+mTjZpqOyoWTEOIvIIhuFfi49eqleyKTnekgXyXcJfqppsbqYcgPKaTbJmhU/\n\ 466 iuOjaSIUlyf5tjJ7bIOAngopcH6u+Qky/a2Q///eOIl7U9WhEMnSYwZ7rqNmMGQw\n\ 467 HQYDVR0OBBYEFMEPdaxTS5havWDJRtW6pOzr1pOOMB8GA1UdIwQYMBaAFMEPdaxT\n\ 468 S5havWDJRtW6pOzr1pOOMA4GA1UdDwEB/wQEAwICBDASBgNVHRMBAf8ECDAGAQH/\n\ 469 AgEBMAoGCCqGSM49BAMDA2gAMGUCMDa2TefBEmKLebf6KziawLXeQRhqb4wcMgtE\n\ 470 RUZ7JOojBC6CqN7xqPMIo2Pp9Pn6iwIxANlSkus723tk6OdeG33A++HwZ9KIXzU4\n\ 471 cJUsEeE4pQ5exYACy2Nd+LVtmerw8ZF6xg==\n\ 472 -----END CERTIFICATE-----", 473 ]; 474 475 const INVALID_UDS_ROOT: &str = "-----BEGIN CERTIFICATE-----\n\ 476 MIICJTCCAaugAwIBAgIUUo4NdEcdRuQdrm5Trm5x+qvx2LEwCgYIKoZIzj0EAwMw\n\ 477 PzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ8wDQYDVQQKDAZHb29nbGUxEjAQ\n\ 478 BgNVBAMMCVRFU1QgUk9PVDAgFw0yNDExMTQyMDEzNDRaGA8yMTI0MTAyMTIwMTM0\n\ 479 NFowPzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ8wDQYDVQQKDAZHb29nbGUx\n\ 480 EjAQBgNVBAMMCVRFU1QgUk9PVDB2MBAGByqGSM49AgEGBSuBBAAiA2IABOGIoNBS\n\ 481 sVs+mTjZpqOyoWTEOIvIIhuFfi49eqleyKTnekgXyXcJfqppsbqYcgPKaTbJmhU/\n\ 482 iuOjaSIUlyf5tjJ7bIOAngopcH6u+Qky/a2Q///eOIl7U9WhEMnSYwZ7rqNmMGQw\n\ 483 HQYDVR0OBBYEFMEPdaxTS5havWDJRtW6pOzr1pOOMB8GA1UdIwQYMBaAFMEPdaxT\n\ 484 S5havWDJRtW6pOzr1pOOMA4GA1UdDwEB/wQEAwICBDASBgNVHRMBAf8ECDAGAQH/\n\ 485 AgEBMAoGCCqGSM49BAMDA2gAMGUCMDa2TefBEmKLebf6KziawLXeQRhqb4wcMgtE\n\ 486 RUZ7JOojBC6CqN7xqPMIo2Pp9Pn6iwIxANlSkus723tk6OdeG33A++HwZ9KIXzU4\n\ 487 cJUsEeE4pQ5exYACy2Ndthisisaproblem==\n\ 488 -----END CERTIFICATE-----"; 489 490 #[test] verify_a_valid_cert_chain()491 fn verify_a_valid_cert_chain() { 492 let leaf = X509::from_pem(VALID_UDS_CHAIN[0].as_bytes()).unwrap(); 493 let intermediate = X509::from_pem(VALID_UDS_CHAIN[1].as_bytes()).unwrap(); 494 let root = X509::from_pem(VALID_UDS_CHAIN[2].as_bytes()).unwrap(); 495 let certs = vec![root, intermediate, leaf]; 496 let signer = "Test Signer".to_string(); 497 let result = Csr::validate_uds_cert_path(&signer, &certs); 498 assert!(result.is_ok()); 499 } 500 501 #[test] make_sure_root_signature_is_checked()502 fn make_sure_root_signature_is_checked() { 503 let leaf = X509::from_pem(VALID_UDS_CHAIN[0].as_bytes()).unwrap(); 504 let intermediate = X509::from_pem(VALID_UDS_CHAIN[1].as_bytes()).unwrap(); 505 let valid_root = X509::from_pem(VALID_UDS_CHAIN[2].as_bytes()).unwrap(); 506 let invalid_root = X509::from_pem(INVALID_UDS_ROOT.as_bytes()).unwrap(); 507 508 let valid_root_public_key = valid_root.public_key().unwrap(); 509 let invalid_root_public_key = invalid_root.public_key().unwrap(); 510 assert!(invalid_root_public_key.public_eq(&valid_root_public_key)); 511 512 let certs = vec![invalid_root.clone(), intermediate.clone(), leaf.clone()]; 513 let signer = "Test Signer".to_string(); 514 let error = Csr::validate_uds_cert_path(&signer, &certs).unwrap_err(); 515 assert!(error.to_string().contains("certificate signature failure")); 516 517 let mut intermediates = Stack::new().unwrap(); 518 intermediates.push(intermediate).unwrap(); 519 520 let mut builder = X509StoreBuilder::new().unwrap(); 521 builder.add_cert(invalid_root).unwrap(); 522 let store = builder.build(); 523 524 let mut context = X509StoreContext::new().unwrap(); 525 assert!(context.init(&store, &leaf, &intermediates, |c| c.verify_cert()).unwrap()); 526 } 527 } 528 529 #[cfg(test)] 530 pub(crate) mod testutil { 531 use crate::publickey::PublicKey; 532 use crate::rkp::{ 533 DeviceInfo, DeviceInfoBootloaderState, DeviceInfoSecurityLevel, DeviceInfoVbState, 534 DeviceInfoVersion, 535 }; 536 use openssl::pkey::PKey; 537 538 // Parse the given PEM-encoded public key into a PublicKey object, panicking on failure. parse_pem_public_key_or_panic(pem: &str) -> PublicKey539 pub fn parse_pem_public_key_or_panic(pem: &str) -> PublicKey { 540 PKey::public_key_from_pem(pem.as_bytes()).unwrap().try_into().unwrap() 541 } 542 543 // The test data uses mostly common DeviceInfo fields test_device_info(version: DeviceInfoVersion) -> DeviceInfo544 pub fn test_device_info(version: DeviceInfoVersion) -> DeviceInfo { 545 DeviceInfo { 546 version, 547 brand: "Google".to_string(), 548 manufacturer: "Google".to_string(), 549 product: "pixel".to_string(), 550 model: "model".to_string(), 551 device: "device".to_string(), 552 vb_state: DeviceInfoVbState::Green, 553 bootloader_state: DeviceInfoBootloaderState::Locked, 554 vbmeta_digest: b"\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff".to_vec(), 555 os_version: Some("12".to_string()), 556 system_patch_level: 20221025, 557 boot_patch_level: 20221026, 558 vendor_patch_level: 20221027, 559 security_level: DeviceInfoSecurityLevel::Tee, 560 fused: 1, 561 } 562 } 563 } 564