| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| //! Module used to interact with keymint secure storage data. |
| use crate::keybox; |
| use crate::keymaster_attributes; |
| use alloc::{format, string::String, vec::Vec}; |
| use keymaster_attributes::UdsCerts; |
| use kmr_common::{ |
| crypto::{self, KeyMaterial}, |
| km_err, try_to_vec, vec_try, vec_try_with_capacity, |
| wire::keymint, |
| wire::AttestationIdInfo, |
| Error, |
| }; |
| use kmr_ta::device::{ |
| RetrieveAttestationIds, RetrieveCertSigningInfo, SigningAlgorithm, SigningKeyType, |
| }; |
| use log::{error, info}; |
| use protobuf::{self, Message}; |
| use storage::{OpenMode, Port, SecureFile, Session}; |
| |
| #[cfg(feature = "soft_attestation_fallback")] |
| mod software; |
| |
| /// Name of file holding attestation device ID information; matches the `kAttestationIdsFileName` |
| /// value in `secure_storage_manager.cpp` for back-compatibility. |
| const KM_ATTESTATION_ID_FILENAME: &str = "AttestationIds"; |
| |
| /// Filename prefix for files holding attestation keys and certificates; matches the |
| /// `kAttestKeyCertPrefix` value in `secure_storage_manager.cpp` for back-compatibility. |
| const KM_ATTESTATION_KEY_CERT_PREFIX: &str = "AttestKeyCert"; |
| |
| /// Filename for holding UDS Certificate. |
| const KM_RKP_UDS_CERT_FILENAME: &str = "UdsCerts"; |
| |
| /// Maximum size of each attestation certificate |
| const MAX_CERT_SIZE: usize = 2048; |
| /// Maximum number of attestation certificates |
| const MAX_CERT_CHAIN_LEN: usize = 3; |
| |
| /// Maximum number of UDS certificates |
| const MAX_UDS_CERT_CHAIN_LEN: usize = 3; |
| |
| /// Return the filename for the file holding attestation keys and certificates for the specified |
| /// signing algorithm. |
| fn get_key_slot_file_name(algorithm: SigningAlgorithm) -> String { |
| let suffix = match algorithm { |
| SigningAlgorithm::Ec => "ec", |
| SigningAlgorithm::Rsa => "rsa", |
| }; |
| format!("{}.{}", KM_ATTESTATION_KEY_CERT_PREFIX, suffix) |
| } |
| |
| // `session` and `secure_file` are of type `Option` because close() takes self by value, so this was |
| // needed for the `Drop` implementation. The intent though is that they should always be populated |
| // on a OpenSecureFile object; which is `OpenSecureFile::new` behavior. |
| struct OpenSecureFile { |
| session: Option<Session>, |
| secure_file: Option<SecureFile>, |
| } |
| |
| impl OpenSecureFile { |
| /// Opens a secure storage session and creates the requested file |
| fn new(file_name: &str) -> Result<Self, Error> { |
| let mut session = Session::new(Port::TamperProof, true).map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "couldn't create storage session: {:?}", e) |
| })?; |
| let secure_file = session.open_file(file_name, OpenMode::Create).map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "couldn't create file {}: {:?}", file_name, e) |
| })?; |
| Ok(OpenSecureFile { session: Some(session), secure_file: Some(secure_file) }) |
| } |
| |
| /// Writes provided data in the previously opened file |
| fn write_all(&mut self, data: &[u8]) -> Result<(), Error> { |
| // Even though we are handling the case when secure_file and session are None, this is not |
| // expected; if an OpenSecureFile object exists its `secure_file` and `session` elements |
| // shall be populated. |
| let session = |
| self.session.as_mut().ok_or(km_err!(UnknownError, "session shouldn't ever be None"))?; |
| let file = self |
| .secure_file |
| .as_mut() |
| .ok_or(km_err!(UnknownError, "secure_file shouldn't ever be None"))?; |
| session.write_all(file, data).map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "failed to write data; received error: {:?}", e) |
| }) |
| } |
| |
| /// Close the session and file handlers by taking ownership and letting the value be dropped |
| #[cfg(test)] |
| fn close(self) {} |
| } |
| |
| impl Drop for OpenSecureFile { |
| fn drop(&mut self) { |
| // Even though we are handling the case when secure_file and session are None, this is not |
| // expected; if an OpenSecureFile object exists its `secure_file` and `session` elements |
| // shall be populated. |
| if let Some(file) = self.secure_file.take() { |
| file.close(); |
| } |
| if let Some(session) = self.session.take() { |
| session.close(); |
| } |
| } |
| } |
| |
| /// Creates an empty attestation IDs file |
| fn create_attestation_id_file() -> Result<OpenSecureFile, Error> { |
| OpenSecureFile::new(KM_ATTESTATION_ID_FILENAME) |
| } |
| |
| /// Creates and empty attestation key/certificates file for the given algorithm |
| fn create_attestation_key_file(algorithm: SigningAlgorithm) -> Result<OpenSecureFile, Error> { |
| let file_name = get_key_slot_file_name(algorithm); |
| OpenSecureFile::new(&file_name) |
| } |
| |
| fn write_protobuf_to_attestation_key_file( |
| algorithm: SigningAlgorithm, |
| attestation_key_data: keymaster_attributes::AttestationKey, |
| ) -> Result<(), Error> { |
| let serialized_buffer = attestation_key_data.write_to_bytes().map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "couldn't serialize attestationKey: {:?}", e) |
| })?; |
| let mut file = create_attestation_key_file(algorithm)?; |
| file.write_all(&serialized_buffer).map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "failed to provision attestation key file: {:?}", e) |
| })?; |
| Ok(()) |
| } |
| |
| /// Unwraps a keybox wrapped key and uses it to provision the key on the device. |
| pub(crate) fn set_wrapped_attestation_key( |
| algorithm: SigningAlgorithm, |
| key_data: &[u8], |
| ) -> Result<(), Error> { |
| let unwrapped_key = keybox::keybox_unwrap(key_data)?; |
| provision_attestation_key_file(algorithm, &unwrapped_key) |
| } |
| |
| /// Tries to read the file containing the attestation key and certificates and only replaces the key |
| /// section. If the file doesn't exist it will create it and save the provided key. |
| pub(crate) fn provision_attestation_key_file( |
| algorithm: SigningAlgorithm, |
| key_data: &[u8], |
| ) -> Result<(), Error> { |
| let mut attestation_key = read_attestation_key_content(algorithm)?; |
| attestation_key.set_key(try_to_vec(key_data)?); |
| |
| write_protobuf_to_attestation_key_file(algorithm, attestation_key) |
| } |
| |
| /// Creates and empty UDS certificates file |
| fn create_uds_cert_file() -> Result<OpenSecureFile, Error> { |
| OpenSecureFile::new(KM_RKP_UDS_CERT_FILENAME) |
| } |
| |
| /// Delete the existing UDS certificates file |
| fn delete_uds_cert_file() -> Result<(), Error> { |
| let mut session = Session::new(Port::TamperProof, true).map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "failed to connect to storage port: {:?}", e) |
| })?; |
| session.remove(KM_RKP_UDS_CERT_FILENAME).map_err(|e| { |
| km_err!( |
| SecureHwCommunicationFailed, |
| "failed to delete {:?}: {:?}", |
| KM_RKP_UDS_CERT_FILENAME, |
| e |
| ) |
| })?; |
| Ok(()) |
| } |
| |
| fn write_protobuf_to_uds_cert_file(uds_cert_pb: UdsCerts) -> Result<(), Error> { |
| let serialized_buffer = uds_cert_pb.write_to_bytes().map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "couldn't serialize UdsCerts: {:?}", e) |
| })?; |
| |
| let mut file = create_uds_cert_file()?; |
| file.write_all(&serialized_buffer).map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "failed to provision UdsCerts file: {:?}", e) |
| })?; |
| Ok(()) |
| } |
| |
| /// Tries to read the file containing the UdsCerts and append a cert on it. |
| /// If the file doesn't exist it will create it and save the provided key. |
| pub(crate) fn append_uds_cert_chain(cert_data: &[u8]) -> Result<(), Error> { |
| if cert_data.is_empty() || cert_data.len() > MAX_CERT_SIZE { |
| return Err(km_err!( |
| InvalidArgument, |
| "Invalid certificate size: {}, max size {}", |
| cert_data.len(), |
| MAX_CERT_SIZE |
| )); |
| } |
| |
| let mut uds_cert_pb = read_uds_cert_content()?; |
| let cert_chain_len = uds_cert_pb.get_certs().len(); |
| if cert_chain_len >= MAX_UDS_CERT_CHAIN_LEN { |
| return Err(km_err!( |
| InvalidArgument, |
| "cannot accept more certificates, {} already provisioned", |
| cert_chain_len |
| )); |
| } |
| |
| let mut cert = keymaster_attributes::AttestationCert::new(); |
| cert.set_content(try_to_vec(cert_data)?); |
| uds_cert_pb.mut_certs().push(cert); |
| |
| write_protobuf_to_uds_cert_file(uds_cert_pb) |
| } |
| |
| /// Tries to read the file containing the attestation key and certificates and adds a certificate to |
| /// it if there is still space left on the certificate section. If the file doesn't exist it will |
| /// create it and save the provided certificate. |
| pub(crate) fn append_attestation_cert_chain( |
| algorithm: SigningAlgorithm, |
| cert_data: &[u8], |
| ) -> Result<(), Error> { |
| if cert_data.is_empty() { |
| return Err(km_err!(InvalidInputLength, "received a certificate of length 0")); |
| } |
| |
| if cert_data.len() > MAX_CERT_SIZE { |
| return Err(km_err!( |
| InvalidArgument, |
| "certificate is too big. Size: {}, max size {}", |
| cert_data.len(), |
| MAX_CERT_SIZE |
| )); |
| } |
| |
| let mut attestation_key_data = read_attestation_key_content(algorithm)?; |
| let cert_chain_len = attestation_key_data.get_certs().len(); |
| |
| if cert_chain_len >= MAX_CERT_CHAIN_LEN { |
| return Err(km_err!( |
| InvalidArgument, |
| "cannot accept more certificates, {} already provisioned", |
| cert_chain_len |
| )); |
| } |
| |
| let mut cert = keymaster_attributes::AttestationCert::new(); |
| cert.set_content(try_to_vec(cert_data)?); |
| attestation_key_data.mut_certs().push(cert); |
| |
| write_protobuf_to_attestation_key_file(algorithm, attestation_key_data) |
| } |
| |
| /// Tries to read the file containing the UdsCert. It's fine if that file is clear, or will try to |
| /// delete it. After this call, all "read" operation will get a default <UdsCerts> object. |
| pub(crate) fn maybe_delete_uds_cert_chain() -> Result<(), Error> { |
| let mut uds_cert_pb = read_uds_cert_content()?; |
| if uds_cert_pb.get_certs().is_empty() { |
| // No certs found, nothing to delete. |
| return Ok(()); |
| } |
| delete_uds_cert_file()?; |
| uds_cert_pb = read_uds_cert_content()?; |
| let cert_chain_len = uds_cert_pb.get_certs().len(); |
| if cert_chain_len != 0 { |
| return Err(km_err!( |
| UnknownError, |
| "couldn't delete all certificates, found {}", |
| cert_chain_len |
| )); |
| } |
| Ok(()) |
| } |
| |
| /// Tries to read the file containing the attestation key delete only the certificate section. |
| pub(crate) fn clear_attestation_cert_chain(algorithm: SigningAlgorithm) -> Result<(), Error> { |
| let mut attestation_key_data = read_attestation_key_content(algorithm)?; |
| if attestation_key_data.get_certs().is_empty() { |
| // No certs found, nothing to delete. |
| return Ok(()); |
| } |
| attestation_key_data.clear_certs(); |
| write_protobuf_to_attestation_key_file(algorithm, attestation_key_data)?; |
| // Checking that the certificates were indeed deleted |
| let attestation_key_data = read_attestation_key_content(algorithm)?; |
| let cert_chain_len = attestation_key_data.get_certs().len(); |
| if cert_chain_len != 0 { |
| error!("Couldn't delete all certificates, found {}", cert_chain_len); |
| return Err(km_err!( |
| UnknownError, |
| "couldn't delete all certificates, found {}", |
| cert_chain_len |
| )); |
| } |
| Ok(()) |
| } |
| |
| /// Creates a new attestation IDs file and saves the provided data there |
| #[allow(clippy::too_many_arguments)] |
| pub(crate) fn provision_attestation_id_file( |
| brand: &[u8], |
| product: &[u8], |
| device: &[u8], |
| serial: &[u8], |
| imei: &[u8], |
| meid: &[u8], |
| manufacturer: &[u8], |
| model: &[u8], |
| maybe_imei2: Option<&[u8]>, |
| ) -> Result<(), Error> { |
| let mut file = create_attestation_id_file()?; |
| |
| let mut attestation_ids = keymaster_attributes::AttestationIds::new(); |
| |
| if !brand.is_empty() { |
| attestation_ids.set_brand(try_to_vec(brand)?); |
| } |
| if !device.is_empty() { |
| attestation_ids.set_device(try_to_vec(device)?); |
| } |
| if !product.is_empty() { |
| attestation_ids.set_product(try_to_vec(product)?); |
| } |
| if !serial.is_empty() { |
| attestation_ids.set_serial(try_to_vec(serial)?); |
| } |
| if !imei.is_empty() { |
| attestation_ids.set_imei(try_to_vec(imei)?); |
| } |
| if !meid.is_empty() { |
| attestation_ids.set_meid(try_to_vec(meid)?); |
| } |
| if !manufacturer.is_empty() { |
| attestation_ids.set_manufacturer(try_to_vec(manufacturer)?); |
| } |
| if !model.is_empty() { |
| attestation_ids.set_model(try_to_vec(model)?); |
| } |
| match maybe_imei2 { |
| Some(imei2) if !imei2.is_empty() => { |
| attestation_ids.set_second_imei(try_to_vec(imei2)?); |
| } |
| _ => (), |
| } |
| |
| let serialized_buffer = attestation_ids.write_to_bytes().map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "couldn't serialize attestationIds: {:?}", e) |
| })?; |
| |
| file.write_all(&serialized_buffer).map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "failed to provision attestation IDs file: {:?}", e) |
| })?; |
| |
| Ok(()) |
| } |
| |
| /// Delete all attestation IDs from secure storage. |
| pub(crate) fn delete_attestation_ids() -> Result<(), Error> { |
| let mut session = Session::new(Port::TamperProof, true).map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "failed to connect to storage port: {:?}", e) |
| })?; |
| session.remove(KM_ATTESTATION_ID_FILENAME).map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "failed to delete attestation IDs file: {:?}", e) |
| })?; |
| Ok(()) |
| } |
| |
| /// Return the contents of the specified file in secure storage. |
| fn get_file_contents(file_name: &str) -> Result<Option<Vec<u8>>, Error> { |
| let mut session = Session::new(Port::TamperProof, true).map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "failed to connect to storage port: {:?}", e) |
| })?; |
| // Distinguishing between file not found and other errors, so we can match c++ behavior when |
| // retrieving attestation IDs on unprovisioned devices. |
| let file = match session.open_file(file_name, OpenMode::Open) { |
| Ok(file) => file, |
| Err(storage::Error::Code(trusty_sys::Error::NotFound)) => { |
| info!("{file_name:?} NotFound"); |
| return Ok(None); |
| } |
| Err(e) => { |
| return Err(km_err!( |
| SecureHwCommunicationFailed, |
| "failed to open '{}': {:?}", |
| file_name, |
| e |
| )); |
| } |
| }; |
| let size = session.get_size(&file).map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "failed to get size for '{}': {:?}", file_name, e) |
| })?; |
| let mut buffer = vec_try![0; size]?; |
| let content = session.read_all(&file, buffer.as_mut_slice()).map_err(|e| { |
| km_err!(SecureHwCommunicationFailed, "failed to read '{}': {:?}", file_name, e) |
| })?; |
| let total_size = content.len(); |
| buffer.resize(total_size, 0); |
| Ok(Some(buffer)) |
| } |
| |
| /// Retrieve the attestation ID information from secure storage. |
| pub(crate) fn read_attestation_ids() -> Result<AttestationIdInfo, Error> { |
| // Retrieving attestation IDs from file. If the file is not found (device not provisioned) we |
| // will return an empty AttestationIdInfo info to match the c++ code behavior |
| let content = match get_file_contents(KM_ATTESTATION_ID_FILENAME) { |
| Ok(Some(file_contents)) => file_contents, |
| Ok(None) => return Ok(AttestationIdInfo::default()), |
| Err(e) => return Err(e), |
| }; |
| let mut attestation_ids_pb: keymaster_attributes::AttestationIds = |
| Message::parse_from_bytes(content.as_slice()) |
| .map_err(|e| km_err!(UnknownError, "failed to parse attestation IDs proto: {:?}", e))?; |
| |
| let brand = attestation_ids_pb.take_brand(); |
| let device = attestation_ids_pb.take_device(); |
| let product = attestation_ids_pb.take_product(); |
| let serial = attestation_ids_pb.take_serial(); |
| let imei = attestation_ids_pb.take_imei(); |
| let meid = attestation_ids_pb.take_meid(); |
| let manufacturer = attestation_ids_pb.take_manufacturer(); |
| let model = attestation_ids_pb.take_model(); |
| |
| let imei2 = if attestation_ids_pb.has_second_imei() { |
| // A second IMEI has been explicitly provisioned, so use that. |
| attestation_ids_pb.take_second_imei() |
| } else if cfg!(feature = "auto_second_imei") { |
| // No second IMEI has been explicitly provisioned, but dual-SIM devices typically ship with |
| // two sequential IMEIs, so treat (IMEI+1) as the second IMEI. |
| kmr_common::tag::increment_imei(&imei) |
| } else { |
| Vec::new() |
| }; |
| |
| Ok(AttestationIdInfo { brand, device, product, serial, imei, imei2, meid, manufacturer, model }) |
| } |
| |
| /// Retrieve that attestation key information for the specified signing algorithm. |
| /// Returns an empty protobuf when file is not found to match c++ behavior |
| fn read_attestation_key_content( |
| key_type: SigningAlgorithm, |
| ) -> Result<keymaster_attributes::AttestationKey, Error> { |
| let file_name = get_key_slot_file_name(key_type); |
| let pb = match get_file_contents(&file_name)? { |
| Some(content) => Message::parse_from_bytes(content.as_slice()) |
| .map_err(|e| km_err!(UnknownError, "failed to parse attestation key proto: {:?}", e))?, |
| None => keymaster_attributes::AttestationKey::new(), |
| }; |
| Ok(pb) |
| } |
| |
| /// Retrieve that Uds certificate information |
| /// Returns an empty protobuf when file is not found to match c++ behavior |
| fn read_uds_cert_content() -> Result<UdsCerts, Error> { |
| let uds_cert_file_pb = match get_file_contents(KM_RKP_UDS_CERT_FILENAME)? { |
| Some(content) => Message::parse_from_bytes(&content) |
| .map_err(|e| km_err!(UnknownError, "failed to parse UdsCerts proto: {:?}", e))?, |
| None => UdsCerts::new(), |
| }; |
| Ok(uds_cert_file_pb) |
| } |
| |
| /// Retrieve the UdsCerts from the file in secure storage. |
| pub(crate) fn read_uds_cert() -> Result<Vec<keymint::Certificate>, Error> { |
| let mut uds_certs_pb: UdsCerts = read_uds_cert_content()?; |
| |
| let certs = uds_certs_pb.take_certs(); |
| let num_certs = certs.len(); |
| if num_certs == 0 { |
| return Err(km_err!(UnknownError, "UdsCerts file found but it had nothing")); |
| } |
| let mut certificates = vec_try_with_capacity!(num_certs)?; |
| for mut cert in certs { |
| let km_cert_pb = keymint::Certificate { encoded_certificate: cert.take_content() }; |
| certificates.push(km_cert_pb); |
| } |
| Ok(certificates) |
| } |
| |
| /// Tries to parse an EC private key as a SEC1 private EC key. If that fails, it tries to parse it |
| /// as a PKCS#8 encoded key. |
| fn import_ec_key(key_buffer: &[u8]) -> Result<KeyMaterial, Error> { |
| match crypto::ec::import_sec1_private_key(key_buffer) { |
| Ok(key_material) => Ok(key_material), |
| Err(e) => { |
| info!("couldn't parse as sec1 ECPrivateKey, will try as PKCS#8. Error {:?}", e); |
| crypto::ec::import_pkcs8_key(key_buffer) |
| } |
| } |
| } |
| |
| /// Tries to parse an RSA key as a PKCS#1 encoded key. If that fails, it tries to parse it as a |
| /// PKCS#8 one. |
| fn import_rsa_key(key_buffer: &[u8]) -> Result<KeyMaterial, Error> { |
| match crypto::rsa::import_pkcs1_key(key_buffer) { |
| Ok((key_material, _, _)) => Ok(key_material), |
| Err(e) => { |
| info!("couldn't parse PKCS#1 RSA key, will try as PKCS#8. Error {:?}", e); |
| let (key_material, _, _) = crypto::rsa::import_pkcs8_key(key_buffer)?; |
| Ok(key_material) |
| } |
| } |
| } |
| |
| /// Retrieve the specified attestation key from the file in secure storage. |
| pub(crate) fn read_attestation_key(key_type: SigningKeyType) -> Result<KeyMaterial, Error> { |
| let mut attestation_key_pb: keymaster_attributes::AttestationKey = |
| read_attestation_key_content(key_type.algo_hint)?; |
| |
| if !(attestation_key_pb.has_key()) { |
| return Err(km_err!(UnknownError, "attestation Key file found but it had no key")); |
| } |
| let key_buffer = attestation_key_pb.take_key(); |
| |
| match key_type.algo_hint { |
| SigningAlgorithm::Ec => import_ec_key(&key_buffer), |
| SigningAlgorithm::Rsa => import_rsa_key(&key_buffer), |
| } |
| } |
| |
| pub(crate) fn get_cert_chain(key_type: SigningKeyType) -> Result<Vec<keymint::Certificate>, Error> { |
| let mut attestation_certs_pb: keymaster_attributes::AttestationKey = |
| read_attestation_key_content(key_type.algo_hint)?; |
| let certs = attestation_certs_pb.take_certs(); |
| |
| let num_certs = certs.len(); |
| if num_certs == 0 { |
| return Err(km_err!(UnknownError, "attestation Key file found but it had no certs")); |
| } |
| let mut certificates = vec_try_with_capacity!(num_certs)?; |
| for mut cert in certs { |
| let certificate = keymint::Certificate { encoded_certificate: cert.take_content() }; |
| certificates.push(certificate); |
| } |
| Ok(certificates) |
| } |
| |
| /// Implementation of attestation ID retrieval trait based on protobuf-encoded data in a file in |
| /// secure storage. |
| pub struct AttestationIds; |
| |
| impl RetrieveAttestationIds for AttestationIds { |
| fn get(&self) -> Result<AttestationIdInfo, Error> { |
| read_attestation_ids() |
| } |
| |
| /// Destroy all attestation IDs associated with the device. |
| fn destroy_all(&mut self) -> Result<(), Error> { |
| delete_attestation_ids() |
| } |
| } |
| |
| /// Implementation of attestation signing key retrieval trait based on data held in files in secure |
| /// storage. |
| pub struct CertSignInfo; |
| |
| impl RetrieveCertSigningInfo for CertSignInfo { |
| fn signing_key(&self, key_type: SigningKeyType) -> Result<KeyMaterial, Error> { |
| let result = read_attestation_key(key_type); |
| #[cfg(feature = "soft_attestation_fallback")] |
| if let Err(e) = result { |
| info!("failed to read attestation key ({:?}), fall back to test key", e); |
| let fake = software::CertSignInfo::new(); |
| return fake.signing_key(key_type); |
| } |
| result |
| } |
| |
| fn cert_chain(&self, key_type: SigningKeyType) -> Result<Vec<keymint::Certificate>, Error> { |
| let result = get_cert_chain(key_type); |
| #[cfg(feature = "soft_attestation_fallback")] |
| if let Err(e) = result { |
| info!("failed to read attestation chain ({:?}), fall back to test chain", e); |
| let fake = software::CertSignInfo::new(); |
| return fake.cert_chain(key_type); |
| } |
| result |
| } |
| } |
| |
| #[allow(dead_code)] |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use alloc::vec; |
| use core::iter::zip; |
| use kmr_common::wire::AttestationIdInfo; |
| use kmr_ta::device::{SigningAlgorithm, SigningKey, SigningKeyType}; |
| use test::{expect, expect_eq}; |
| |
| // Generated by: |
| // openssl genpkey -out rsakey2.key -outform DER -algorithm RSA -pkeyopt rsa_keygen_bits:2048 |
| // openssl pkcs8 -topk8 -in rsakey2.key -outform der -nocrypt -out rsakey.key |
| const RSA_KEY: &'static [u8] = include_bytes!("rsakey.key"); |
| // Generated by: |
| // openssl ecparam -name prime256v1 -genkey -outform der -out eckey2.key |
| // openssl pkcs8 -topk8 -in eckey2.key -outform der -nocrypt -out eckey.key |
| const EC_KEY: &'static [u8] = include_bytes!("eckey.key"); |
| // Generated by: |
| // openssl genpkey -out rsakey2.key -outform DER -algorithm RSA -pkeyopt rsa_keygen_bits:2048 |
| const RSA_KEY_PKCS1: &'static [u8] = include_bytes!("rsakeyrfc3447.key"); |
| // Generated by: |
| // openssl ecparam -name prime256v1 -genkey -outform der -out eckey2.key |
| const EC_PRIVATE_KEY_SEC1: &'static [u8] = include_bytes!("eckeysec1.key"); |
| |
| fn delete_key_file(algorithm: SigningAlgorithm) { |
| let file_name = get_key_slot_file_name(algorithm); |
| let mut session = |
| Session::new(Port::TamperProof, true).expect("Couldn't connect to storage"); |
| session.remove(&file_name).expect("Couldn't delete attestation file."); |
| } |
| |
| fn check_key_file_exists(algorithm: SigningAlgorithm) -> bool { |
| let mut session = |
| Session::new(Port::TamperProof, true).expect("Couldn't connect to storage"); |
| let file_name = get_key_slot_file_name(algorithm); |
| session.open_file(&file_name, OpenMode::Open).is_ok() |
| } |
| |
| fn check_uds_certs_file_exists() -> bool { |
| let mut session = |
| Session::new(Port::TamperProof, true).expect("Couldn't connect to storage"); |
| session.open_file(KM_RKP_UDS_CERT_FILENAME, OpenMode::Open).is_ok() |
| } |
| |
| fn delete_attestation_id_file() { |
| let mut session = |
| Session::new(Port::TamperProof, true).expect("Couldn't connect to storage"); |
| session.remove(KM_ATTESTATION_ID_FILENAME).expect("Couldn't delete attestation IDs file."); |
| } |
| |
| fn check_attestation_id_file_exists() -> bool { |
| let mut session = |
| Session::new(Port::TamperProof, true).expect("Couldn't connect to storage"); |
| session.open_file(KM_ATTESTATION_ID_FILENAME, OpenMode::Open).is_ok() |
| } |
| |
| fn compare_attestation_ids(lhs: &AttestationIdInfo, rhs: &AttestationIdInfo) { |
| expect_eq!(lhs.brand, rhs.brand, "brand doesn't match"); |
| expect_eq!(lhs.device, rhs.device, "device doesn't match"); |
| expect_eq!(lhs.product, rhs.product, "product doesn't match"); |
| expect_eq!(lhs.serial, rhs.serial, "serial doesn't match"); |
| expect_eq!(lhs.imei, rhs.imei, "imei doesn't match"); |
| expect_eq!(lhs.meid, rhs.meid, "meid doesn't match"); |
| expect_eq!(lhs.manufacturer, rhs.manufacturer, "manufacturer doesn't match"); |
| expect_eq!(lhs.model, rhs.model, "model doesn't match"); |
| expect_eq!(lhs.imei2, rhs.imei2, "imei2 doesn't match"); |
| } |
| |
| fn read_certificates_test(algorithm: SigningAlgorithm) { |
| let mut file = |
| create_attestation_key_file(algorithm).expect("Couldn't create attestation key file"); |
| let mut key_cert = keymaster_attributes::AttestationKey::new(); |
| let certs_data = [[b'a'; 2048], [b'\0'; 2048], [b'c'; 2048]]; |
| |
| let mut certs = protobuf::RepeatedField::<keymaster_attributes::AttestationCert>::new(); |
| |
| for cert_data in certs_data.iter() { |
| let mut cert = keymaster_attributes::AttestationCert::new(); |
| cert.set_content(cert_data.to_vec()); |
| certs.push(cert); |
| } |
| |
| key_cert.set_certs(certs); |
| |
| let serialized_buffer = key_cert.write_to_bytes().expect("Couldn't serialize certs"); |
| |
| file.write_all(&serialized_buffer).unwrap(); |
| file.close(); |
| |
| let key_type = SigningKeyType { which: SigningKey::Batch, algo_hint: algorithm }; |
| let certs = get_cert_chain(key_type).expect("Couldn't get certificates from storage"); |
| delete_key_file(algorithm); |
| |
| expect_eq!(certs.len(), 3, "Didn't recover all certificates"); |
| for (cert, cert_data) in zip(certs.iter(), certs_data.iter()) { |
| expect_eq!(cert.encoded_certificate, cert_data.to_vec(), "Wrong certificate retrieved"); |
| } |
| |
| // Trying now using just a raw protobuf with same data |
| let field_header = [18, 131, 16, 10, 128, 16]; |
| let mut raw_protobuf = Vec::new(); |
| for cert_data in certs_data.iter() { |
| raw_protobuf.extend_from_slice(&field_header); |
| raw_protobuf.extend_from_slice(cert_data); |
| } |
| |
| let mut file = |
| create_attestation_key_file(algorithm).expect("Couldn't create attestation key file"); |
| file.write_all(&raw_protobuf).unwrap(); |
| file.close(); |
| |
| let certs_comp = get_cert_chain(key_type).expect("Couldn't get certificates from storage"); |
| |
| expect_eq!(certs, certs_comp, "Retrieved certificates didn't match"); |
| |
| delete_key_file(algorithm); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn read_ec_rsa_certificates() { |
| read_certificates_test(SigningAlgorithm::Rsa); |
| read_certificates_test(SigningAlgorithm::Ec); |
| } |
| |
| fn get_test_key_data(algorithm: SigningAlgorithm) -> &'static [u8] { |
| match algorithm { |
| SigningAlgorithm::Rsa => RSA_KEY, |
| SigningAlgorithm::Ec => EC_KEY, |
| } |
| } |
| |
| fn get_non_pkcs8_test_key_data(algorithm: SigningAlgorithm) -> &'static [u8] { |
| match algorithm { |
| SigningAlgorithm::Rsa => RSA_KEY_PKCS1, |
| SigningAlgorithm::Ec => EC_PRIVATE_KEY_SEC1, |
| } |
| } |
| |
| fn get_test_attestation_key(algorithm: SigningAlgorithm) -> Result<KeyMaterial, Error> { |
| let key_buffer = get_test_key_data(algorithm); |
| let key = match algorithm { |
| SigningAlgorithm::Ec => crypto::ec::import_pkcs8_key(key_buffer)?, |
| SigningAlgorithm::Rsa => { |
| let (key_material, _key_size, _exponent) = |
| crypto::rsa::import_pkcs8_key(key_buffer)?; |
| key_material |
| } |
| }; |
| Ok(key) |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn multiple_attestation_calls_work() { |
| let ec_test_key = |
| get_test_attestation_key(SigningAlgorithm::Ec).expect("Couldn't get test key"); |
| let rsa_test_key = |
| get_test_attestation_key(SigningAlgorithm::Rsa).expect("Couldn't get test key"); |
| |
| provision_attestation_key_file(SigningAlgorithm::Ec, EC_KEY) |
| .expect("Couldn't provision key"); |
| provision_attestation_key_file(SigningAlgorithm::Rsa, RSA_KEY) |
| .expect("Couldn't provision key"); |
| provision_attestation_key_file(SigningAlgorithm::Rsa, &[0]) |
| .expect("Couldn't provision key"); |
| provision_attestation_key_file(SigningAlgorithm::Ec, &[0, 0]) |
| .expect("Couldn't provision key"); |
| provision_attestation_key_file(SigningAlgorithm::Ec, EC_KEY) |
| .expect("Couldn't provision key"); |
| provision_attestation_key_file(SigningAlgorithm::Rsa, RSA_KEY) |
| .expect("Couldn't provision key"); |
| |
| let key_type = SigningKeyType { which: SigningKey::Batch, algo_hint: SigningAlgorithm::Ec }; |
| let read_ec_test_key = |
| read_attestation_key(key_type).expect("Couldn't read key from storage"); |
| |
| let key_type = |
| SigningKeyType { which: SigningKey::Batch, algo_hint: SigningAlgorithm::Rsa }; |
| let read_rsa_test_key = |
| read_attestation_key(key_type).expect("Couldn't read key from storage"); |
| |
| delete_key_file(SigningAlgorithm::Ec); |
| delete_key_file(SigningAlgorithm::Rsa); |
| |
| expect_eq!(ec_test_key, read_ec_test_key, "Provisioned key doesn't match original one"); |
| expect_eq!(rsa_test_key, read_rsa_test_key, "Provisioned key doesn't match original one"); |
| } |
| |
| fn read_key_test(algorithm: SigningAlgorithm) { |
| let test_key = get_test_key_data(algorithm); |
| |
| provision_attestation_key_file(algorithm, test_key).expect("Couldn't provision key"); |
| |
| let key_type = SigningKeyType { which: SigningKey::Batch, algo_hint: algorithm }; |
| let att_key = read_attestation_key(key_type).expect("Couldn't read key from storage"); |
| |
| delete_key_file(algorithm); |
| |
| // Trying now using just a raw protobuf with same data |
| let key_header = match algorithm { |
| SigningAlgorithm::Rsa => [10, 191, 9], |
| SigningAlgorithm::Ec => [10, 138, 1], |
| }; |
| let mut raw_protobuf = Vec::new(); |
| raw_protobuf.extend_from_slice(&key_header); |
| raw_protobuf.extend_from_slice(&test_key); |
| |
| let mut file = |
| create_attestation_key_file(algorithm).expect("Couldn't create attestation key file"); |
| file.write_all(&raw_protobuf).unwrap(); |
| file.close(); |
| |
| let att_key_comp = read_attestation_key(key_type).expect("Couldn't read key from storage"); |
| |
| expect_eq!(att_key, att_key_comp); |
| |
| delete_key_file(algorithm); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn read_ec_rsa_key() { |
| read_key_test(SigningAlgorithm::Rsa); |
| read_key_test(SigningAlgorithm::Ec); |
| } |
| |
| fn read_non_pkcs8_key_test(algorithm: SigningAlgorithm) { |
| let test_key = get_non_pkcs8_test_key_data(algorithm); |
| |
| provision_attestation_key_file(algorithm, test_key).expect("Couldn't provision key"); |
| |
| let key_type = SigningKeyType { which: SigningKey::Batch, algo_hint: algorithm }; |
| let att_key = read_attestation_key(key_type); |
| expect!(att_key.is_ok(), "Couldn't read a non-pkcs8 key from storage"); |
| |
| delete_key_file(algorithm); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn read_sec1_private_ec_key() { |
| read_non_pkcs8_key_test(SigningAlgorithm::Ec); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn read_pkcs1_rsa_key() { |
| read_non_pkcs8_key_test(SigningAlgorithm::Rsa); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn unprovisioned_keys_certs_reads_produces_error() { |
| if check_key_file_exists(SigningAlgorithm::Ec) { |
| delete_key_file(SigningAlgorithm::Ec); |
| } |
| if check_key_file_exists(SigningAlgorithm::Rsa) { |
| delete_key_file(SigningAlgorithm::Rsa); |
| } |
| let key_type = SigningKeyType { which: SigningKey::Batch, algo_hint: SigningAlgorithm::Ec }; |
| expect!(read_attestation_key(key_type).is_err(), "Shouldn't be able to read a key"); |
| let key_type = |
| SigningKeyType { which: SigningKey::Batch, algo_hint: SigningAlgorithm::Rsa }; |
| expect!(read_attestation_key(key_type).is_err(), "Shouldn't be able to read a key"); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn provision_certs_test() { |
| provision_certs_test_impl(SigningAlgorithm::Ec, true); |
| provision_certs_test_impl(SigningAlgorithm::Rsa, true); |
| provision_certs_test_impl(SigningAlgorithm::Ec, false); |
| provision_certs_test_impl(SigningAlgorithm::Rsa, false); |
| } |
| |
| fn provision_certs_test_impl(algorithm: SigningAlgorithm, key_first: bool) { |
| if check_key_file_exists(algorithm) { |
| delete_key_file(algorithm); |
| } |
| let test_key = get_test_key_data(algorithm); |
| if key_first { |
| provision_attestation_key_file(algorithm, test_key).expect("Couldn't provision key"); |
| } |
| let cert1 = [b'a'; 2048].as_slice(); |
| let cert2 = [b'b'; 2048].as_slice(); |
| let cert3 = [b'c'; 2048].as_slice(); |
| let certs = [cert1, cert2, cert3]; |
| for cert_data in certs.iter() { |
| append_attestation_cert_chain(algorithm, cert_data) |
| .expect("Couldn't provision certificate"); |
| } |
| expect!( |
| append_attestation_cert_chain(algorithm, cert3).is_err(), |
| "Shouldn't be able to add more certificates" |
| ); |
| if !key_first { |
| provision_attestation_key_file(algorithm, test_key).expect("Couldn't provision key"); |
| } |
| let key_type = SigningKeyType { which: SigningKey::Batch, algo_hint: algorithm }; |
| let read_test_key = read_attestation_key(key_type).expect("Couldn't read attestation key"); |
| //Getting test key data on a format that can be compared with the key in storage |
| let test_key = get_test_attestation_key(algorithm).expect("Couldn't get test key"); |
| expect_eq!(test_key, read_test_key, "Test keys didn't match"); |
| |
| let read_certs = get_cert_chain(key_type).expect("Couldn't get certificates from storage"); |
| expect_eq!(read_certs.len(), 3, "Didn't get all certificates back"); |
| for (cert, read_cert) in certs.iter().zip(read_certs.iter()) { |
| expect_eq!(cert, &read_cert.encoded_certificate, "got wrong certificate back"); |
| } |
| delete_key_file(algorithm); |
| } |
| |
| fn clear_certificate_chain_works_when_unprovisioned_impl(algorithm: SigningAlgorithm) { |
| if check_key_file_exists(algorithm) { |
| delete_key_file(algorithm); |
| } |
| clear_attestation_cert_chain(algorithm).expect("couldn't clear certificate chain"); |
| expect!( |
| check_key_file_exists(algorithm) == false, |
| "Shouldn't have created a file if it didn't existed originally" |
| ); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn clear_certificate_chain_works_when_unprovisioned() { |
| clear_certificate_chain_works_when_unprovisioned_impl(SigningAlgorithm::Ec); |
| clear_certificate_chain_works_when_unprovisioned_impl(SigningAlgorithm::Rsa); |
| } |
| |
| fn clear_certificate_chain_works_impl(algorithm: SigningAlgorithm) { |
| if check_key_file_exists(algorithm) { |
| delete_key_file(algorithm); |
| } |
| let test_key = get_test_key_data(algorithm); |
| provision_attestation_key_file(algorithm, test_key).expect("Couldn't provision key"); |
| let cert = [b'a'; 2048].as_slice(); |
| append_attestation_cert_chain(algorithm, cert).expect("Couldn't provision certificate"); |
| |
| let key_type = SigningKeyType { which: SigningKey::Batch, algo_hint: algorithm }; |
| let read_certs = get_cert_chain(key_type).expect("Couldn't get certificates from storage"); |
| expect_eq!(read_certs.len(), 1, "Didn't get all certificates back"); |
| |
| clear_attestation_cert_chain(algorithm).expect("couldn't clear certificate chain"); |
| |
| expect!(get_cert_chain(key_type).is_err(), "Certificates were not deleted"); |
| |
| let read_test_key = read_attestation_key(key_type).expect("Couldn't read attestation key"); |
| //Getting test key data on a format that can be compared with the key in storage |
| let test_key = get_test_attestation_key(algorithm).expect("Couldn't get test key"); |
| expect_eq!(test_key, read_test_key, "Test keys didn't match"); |
| |
| delete_key_file(algorithm); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn clear_certificate_chain_works() { |
| clear_certificate_chain_works_impl(SigningAlgorithm::Ec); |
| clear_certificate_chain_works_impl(SigningAlgorithm::Rsa); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn provision_uds_certs_works() { |
| if check_uds_certs_file_exists() { |
| delete_uds_cert_file().expect("Couldn't delete UdsCerts file"); |
| } |
| let cert1 = [b'a'; 500].as_slice(); |
| let cert2 = [b'b'; 500].as_slice(); |
| let cert3 = [b'c'; 500].as_slice(); |
| let certs = [cert1, cert2, cert3]; |
| for cert_data in certs.iter() { |
| append_uds_cert_chain(cert_data).expect("Couldn't provision UdsCerts"); |
| } |
| expect!( |
| append_uds_cert_chain(cert3).is_err(), |
| "Shouldn't be able to add more certificates" |
| ); |
| |
| let read_uds_certs = read_uds_cert().expect("Couldn't read attestation key"); |
| for i in 0..certs.len() { |
| expect_eq!(certs[i], read_uds_certs[i].encoded_certificate, "Uds Certs didn't match"); |
| } |
| |
| delete_uds_cert_file().expect("Couldn't delete UdsCerts file"); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn clear_uds_certs_works() { |
| if check_uds_certs_file_exists() { |
| delete_uds_cert_file().expect("Couldn't delete UdsCerts file"); |
| } |
| let cert = [b'a'; 500].as_slice(); |
| append_uds_cert_chain(cert).expect("Couldn't provision certificate"); |
| let read_certs = read_uds_cert().expect("Couldn't get UdsCerts from storage"); |
| expect_eq!(read_certs.len(), 1, "Didn't get all certificates back"); |
| |
| maybe_delete_uds_cert_chain().expect("Couldn't delete UdsCerts file"); |
| |
| expect!(read_uds_cert().is_err(), "Certificates were not deleted"); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn get_clear_uds_certs_after_delete_works() { |
| if check_uds_certs_file_exists() { |
| delete_uds_cert_file().expect("Couldn't delete UdsCerts file"); |
| } |
| let cert = [b'a'; 500].as_slice(); |
| append_uds_cert_chain(cert).expect("Couldn't provision certificate"); |
| let read_certs = read_uds_cert().expect("Couldn't get UdsCerts from storage"); |
| expect_eq!(read_certs.len(), 1, "Didn't get all certificates back"); |
| |
| maybe_delete_uds_cert_chain().expect("couldn't delete UdsCerts file"); |
| |
| let expected_clear_uds_cert = |
| read_uds_cert_content().expect("Couldn't get a default UdsCerts objec"); |
| expect_eq!(expected_clear_uds_cert.certs.len(), 0, "Not a clear UdsCerts object"); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn unprovisioned_attestation_ids_do_not_error() { |
| if check_attestation_id_file_exists() { |
| delete_attestation_id_file(); |
| } |
| let attestation_ids = |
| read_attestation_ids().expect("Couldn't read attestation IDs when unprovisioned"); |
| |
| expect_eq!(attestation_ids.brand.len(), 0, "brand should be empty"); |
| expect_eq!(attestation_ids.device.len(), 0, "device should be empty"); |
| expect_eq!(attestation_ids.product.len(), 0, "product should be empty"); |
| expect_eq!(attestation_ids.serial.len(), 0, "serial should be empty"); |
| expect_eq!(attestation_ids.imei.len(), 0, "imei should be empty"); |
| expect_eq!(attestation_ids.meid.len(), 0, "meid should be empty"); |
| expect_eq!(attestation_ids.manufacturer.len(), 0, "manufacturer should be empty"); |
| expect_eq!(attestation_ids.model.len(), 0, "model should be empty"); |
| expect_eq!(attestation_ids.imei2.len(), 0, "imei2 should be empty"); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn single_attestation_id_field() { |
| let mut file = create_attestation_id_file().expect("Couldn't create attestation id file"); |
| |
| let mut attestation_ids = keymaster_attributes::AttestationIds::new(); |
| let brand = b"new brand"; |
| |
| attestation_ids.set_brand(brand.to_vec()); |
| |
| let serialized_buffer = |
| attestation_ids.write_to_bytes().expect("Couldn't serialize attestationIds"); |
| |
| file.write_all(&serialized_buffer).unwrap(); |
| file.close(); |
| |
| let attestation_ids_info = |
| read_attestation_ids().expect("Couldn't read attestation IDs from storage"); |
| |
| delete_attestation_id_file(); |
| expect_eq!( |
| check_attestation_id_file_exists(), |
| false, |
| "Couldn't delete attestation IDs file" |
| ); |
| |
| expect_eq!(attestation_ids_info.brand, brand.to_vec(), "brand doesn't match"); |
| expect_eq!(attestation_ids_info.device.len(), 0, "shouldn't have a device"); |
| expect_eq!(attestation_ids_info.product.len(), 0, "shouldn't have a product"); |
| expect_eq!(attestation_ids_info.serial.len(), 0, "shouldn't have a serial"); |
| expect_eq!(attestation_ids_info.imei.len(), 0, "shouldn't have a imei"); |
| expect_eq!(attestation_ids_info.meid.len(), 0, "shouldn't have a meid"); |
| expect_eq!(attestation_ids_info.manufacturer.len(), 0, "shouldn't have a manufacturer"); |
| expect_eq!(attestation_ids_info.model.len(), 0, "shouldn't have a model"); |
| expect_eq!(attestation_ids_info.imei2.len(), 0, "shouldn't have a model"); |
| |
| // Now using a raw protobuf |
| let raw_protobuf = [10, 9, 110, 101, 119, 32, 98, 114, 97, 110, 100]; |
| |
| let mut file = create_attestation_id_file().expect("Couldn't create attestation id file"); |
| file.write_all(&raw_protobuf).unwrap(); |
| file.close(); |
| |
| let attestation_ids_comp = read_attestation_ids() |
| .expect("Couldn't read comparison set of attestation IDs from storage"); |
| |
| compare_attestation_ids(&attestation_ids_info, &attestation_ids_comp); |
| |
| delete_attestation_id_file(); |
| expect_eq!( |
| check_attestation_id_file_exists(), |
| false, |
| "Couldn't delete attestation IDs file" |
| ); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn test_provision_attestation_id_file() { |
| let brand = b"unknown brand"; |
| let product = b""; |
| let device = b"my brand new device"; |
| let serial = vec![b'9'; 64]; |
| let imei = b" "; |
| let meid = b"\0"; |
| let manufacturer = b"manufacturer #$%%^"; |
| let model = b"working one"; |
| let imei2 = b"0"; |
| |
| assert!(provision_attestation_id_file( |
| brand, |
| product, |
| device, |
| &serial, |
| imei, |
| meid, |
| manufacturer, |
| model, |
| Some(imei2) |
| ) |
| .is_ok()); |
| |
| let attestation_ids_info = |
| read_attestation_ids().expect("Couldn't read attestation IDs from storage"); |
| |
| delete_attestation_id_file(); |
| expect_eq!( |
| check_attestation_id_file_exists(), |
| false, |
| "Couldn't delete attestation IDs file" |
| ); |
| |
| expect_eq!(attestation_ids_info.brand, brand.to_vec(), "brand doesn't match"); |
| expect_eq!(attestation_ids_info.device, device.to_vec(), "device doesn't match"); |
| expect_eq!(attestation_ids_info.product, product.to_vec(), "product doesn't match"); |
| expect_eq!(attestation_ids_info.serial, serial, "serial doesn't match"); |
| expect_eq!(attestation_ids_info.imei, imei.to_vec(), "imei doesn't match"); |
| expect_eq!(attestation_ids_info.meid, meid.to_vec(), "meid doesn't match"); |
| expect_eq!( |
| attestation_ids_info.manufacturer, |
| manufacturer.to_vec(), |
| "manufacturer doesn't match" |
| ); |
| expect_eq!(attestation_ids_info.model, model.to_vec(), "model doesn't match"); |
| expect_eq!(attestation_ids_info.imei2, imei2.to_vec(), "imei2 doesn't match"); |
| } |
| |
| #[test] |
| fn test_provision_attestation_id_file_imei2_none() { |
| let brand = b"unknown brand"; |
| let product = b""; |
| let device = b"my brand new device"; |
| let serial = vec![b'9'; 64]; |
| let imei = b"000000123456782"; |
| let meid = b"\0"; |
| let manufacturer = b"manufacturer #$%%^"; |
| let model = b"working one"; |
| let expected_imei2 = b"123456790"; |
| |
| assert!(provision_attestation_id_file( |
| brand, |
| product, |
| device, |
| &serial, |
| imei, |
| meid, |
| manufacturer, |
| model, |
| None |
| ) |
| .is_ok()); |
| |
| let attestation_ids_info = |
| read_attestation_ids().expect("Couldn't read attestation IDs from storage"); |
| |
| delete_attestation_id_file(); |
| expect_eq!( |
| check_attestation_id_file_exists(), |
| false, |
| "Couldn't delete attestation IDs file" |
| ); |
| |
| expect_eq!(attestation_ids_info.brand, brand.to_vec(), "brand doesn't match"); |
| expect_eq!(attestation_ids_info.device, device.to_vec(), "device doesn't match"); |
| expect_eq!(attestation_ids_info.product, product.to_vec(), "product doesn't match"); |
| expect_eq!(attestation_ids_info.serial, serial, "serial doesn't match"); |
| expect_eq!(attestation_ids_info.imei, imei.to_vec(), "imei doesn't match"); |
| expect_eq!(attestation_ids_info.meid, meid.to_vec(), "meid doesn't match"); |
| expect_eq!( |
| attestation_ids_info.manufacturer, |
| manufacturer.to_vec(), |
| "manufacturer doesn't match" |
| ); |
| expect_eq!(attestation_ids_info.model, model.to_vec(), "model doesn't match"); |
| expect_eq!(attestation_ids_info.imei2, expected_imei2.to_vec(), "imei2 doesn't match"); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn all_attestation_id_fields() { |
| let mut file = create_attestation_id_file().expect("Couldn't create attestation id file"); |
| let mut attestation_ids = keymaster_attributes::AttestationIds::new(); |
| let brand = b"unknown brand"; |
| let device = b"my brand new device"; |
| let product = b""; |
| let serial = vec![b'9'; 64]; |
| let imei = b" "; |
| let meid = b"\0"; |
| let manufacturer = b"manufacturer #$%%^"; |
| let model = b"working one"; |
| let imei2 = b"0"; |
| |
| attestation_ids.set_brand(brand.to_vec()); |
| attestation_ids.set_device(device.to_vec()); |
| attestation_ids.set_product(product.to_vec()); |
| attestation_ids.set_serial(serial.clone()); |
| attestation_ids.set_imei(imei.to_vec()); |
| attestation_ids.set_meid(meid.to_vec()); |
| attestation_ids.set_manufacturer(manufacturer.to_vec()); |
| attestation_ids.set_model(model.to_vec()); |
| attestation_ids.set_second_imei(imei2.to_vec()); |
| |
| let serialized_buffer = |
| attestation_ids.write_to_bytes().expect("Couldn't serialize attestationIds"); |
| |
| file.write_all(&serialized_buffer).unwrap(); |
| file.close(); |
| |
| let attestation_ids_info = |
| read_attestation_ids().expect("Couldn't read attestation IDs from storage"); |
| |
| delete_attestation_id_file(); |
| expect_eq!( |
| check_attestation_id_file_exists(), |
| false, |
| "Couldn't delete attestation IDs file" |
| ); |
| |
| expect_eq!(attestation_ids_info.brand, brand.to_vec(), "brand doesn't match"); |
| expect_eq!(attestation_ids_info.device, device.to_vec(), "device doesn't match"); |
| expect_eq!(attestation_ids_info.product, product.to_vec(), "product doesn't match"); |
| expect_eq!(attestation_ids_info.serial, serial, "serial doesn't match"); |
| expect_eq!(attestation_ids_info.imei, imei.to_vec(), "imei doesn't match"); |
| expect_eq!(attestation_ids_info.meid, meid.to_vec(), "meid doesn't match"); |
| expect_eq!( |
| attestation_ids_info.manufacturer, |
| manufacturer.to_vec(), |
| "manufacturer doesn't match" |
| ); |
| expect_eq!(attestation_ids_info.model, model.to_vec(), "model doesn't match"); |
| expect_eq!(attestation_ids_info.imei2, imei2.to_vec(), "imei2 doesn't match"); |
| |
| // Now trying the same from a raw protobuf |
| let raw_protobuf = [ |
| 10, 13, 117, 110, 107, 110, 111, 119, 110, 32, 98, 114, 97, 110, 100, 18, 19, 109, 121, |
| 32, 98, 114, 97, 110, 100, 32, 110, 101, 119, 32, 100, 101, 118, 105, 99, 101, 26, 0, |
| 34, 64, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, |
| 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, |
| 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, |
| 42, 1, 32, 50, 1, 0, 58, 18, 109, 97, 110, 117, 102, 97, 99, 116, 117, 114, 101, 114, |
| 32, 35, 36, 37, 37, 94, 66, 11, 119, 111, 114, 107, 105, 110, 103, 32, 111, 110, 101, |
| 74, 1, 48, |
| ]; |
| |
| let mut file = create_attestation_id_file().expect("Couldn't create attestation id file"); |
| file.write_all(&raw_protobuf).unwrap(); |
| file.close(); |
| |
| let attestation_ids_comp = read_attestation_ids() |
| .expect("Couldn't read comparison set of attestation IDs from storage"); |
| |
| compare_attestation_ids(&attestation_ids_info, &attestation_ids_comp); |
| |
| delete_attestation_id_file(); |
| expect_eq!( |
| check_attestation_id_file_exists(), |
| false, |
| "Couldn't delete attestation IDs file" |
| ); |
| } |
| |
| // This test should be run manually as it writes to storage. |
| // #[test] |
| fn delete_attestation_ids_file() { |
| let mut file = create_attestation_id_file().expect("Couldn't create attestation id file"); |
| let raw_protobuf = [10, 9, 110, 101, 119, 32, 98, 114, 97, 110, 100]; |
| file.write_all(&raw_protobuf).unwrap(); |
| file.close(); |
| |
| expect!(check_attestation_id_file_exists(), "Couldn't create attestation IDs file"); |
| expect!(delete_attestation_ids().is_ok(), "Couldn't delete attestation IDs file"); |
| expect_eq!( |
| check_attestation_id_file_exists(), |
| false, |
| "Attestation IDs file was not deleted" |
| ); |
| } |
| |
| #[test] |
| fn protobuf_lib_version() { |
| // We are generating the protobuf rust files out of tree because we cannot do it in-tree yet |
| // Because the version of the tool used to autogenerate the files has to match the protobuf |
| // library version, we check it here. |
| expect_eq!("2.27.1", protobuf::VERSION, "autogenerated files version mistmatch"); |
| } |
| } |