Merge "libfdt: Add FdtNode::descendants()" into main
diff --git a/libs/apkverify/Android.bp b/libs/apkverify/Android.bp
index 1c18d2d..4c5a622 100644
--- a/libs/apkverify/Android.bp
+++ b/libs/apkverify/Android.bp
@@ -48,10 +48,12 @@
test_suites: ["general-tests"],
rustlibs: [
"libandroid_logger",
+ "libanyhow",
"libapkverify",
"libapkzip",
"libbyteorder",
"liblog_rust",
+ "libopenssl",
"libzip",
],
data: ["tests/data/*"],
diff --git a/libs/apkverify/src/lib.rs b/libs/apkverify/src/lib.rs
index 6af8122..1f3c74f 100644
--- a/libs/apkverify/src/lib.rs
+++ b/libs/apkverify/src/lib.rs
@@ -26,5 +26,5 @@
mod v4;
pub use algorithms::{HashAlgorithm, SignatureAlgorithmID};
-pub use v3::{get_public_key_der, verify};
+pub use v3::{extract_signed_data, verify, SignedData};
pub use v4::{get_apk_digest, V4Signature};
diff --git a/libs/apkverify/src/v3.rs b/libs/apkverify/src/v3.rs
index 8a8ad73..88644c7 100644
--- a/libs/apkverify/src/v3.rs
+++ b/libs/apkverify/src/v3.rs
@@ -44,13 +44,9 @@
public_key: PKey<pkey::Public>,
}
-impl Signer {
- fn sdk_range(&self) -> RangeInclusive<u32> {
- self.min_sdk..=self.max_sdk
- }
-}
-
-struct SignedData {
+/// Contains the signed data part of an APK v3 signature.
+#[derive(Debug)]
+pub struct SignedData {
digests: LengthPrefixed<Vec<LengthPrefixed<Digest>>>,
certificates: LengthPrefixed<Vec<LengthPrefixed<X509Certificate>>>,
min_sdk: u32,
@@ -59,20 +55,6 @@
additional_attributes: LengthPrefixed<Vec<LengthPrefixed<AdditionalAttributes>>>,
}
-impl SignedData {
- fn sdk_range(&self) -> RangeInclusive<u32> {
- self.min_sdk..=self.max_sdk
- }
-
- fn find_digest_by_algorithm(&self, algorithm_id: SignatureAlgorithmID) -> Result<&Digest> {
- Ok(self
- .digests
- .iter()
- .find(|&dig| dig.signature_algorithm_id == Some(algorithm_id))
- .context(format!("Digest not found for algorithm: {:?}", algorithm_id))?)
- }
-}
-
#[derive(Debug)]
pub(crate) struct Signature {
/// Option is used here to allow us to ignore unsupported algorithm.
@@ -80,6 +62,7 @@
signature: LengthPrefixed<Bytes>,
}
+#[derive(Debug)]
struct Digest {
signature_algorithm_id: Option<SignatureAlgorithmID>,
digest: LengthPrefixed<Bytes>,
@@ -88,19 +71,19 @@
type X509Certificate = Bytes;
type AdditionalAttributes = Bytes;
-/// Verifies APK Signature Scheme v3 signatures of the provided APK and returns the public key
-/// associated with the signer in DER format.
-pub fn verify<P: AsRef<Path>>(apk_path: P, current_sdk: u32) -> Result<Box<[u8]>> {
+/// Verifies APK Signature Scheme v3 signatures of the provided APK and returns the SignedData from
+/// the signature.
+pub fn verify<P: AsRef<Path>>(apk_path: P, current_sdk: u32) -> Result<SignedData> {
let apk = File::open(apk_path.as_ref())?;
let (signer, mut sections) = extract_signer_and_apk_sections(apk, current_sdk)?;
signer.verify(&mut sections)
}
-/// Gets the public key (in DER format) that was used to sign the given APK/APEX file
-pub fn get_public_key_der<P: AsRef<Path>>(apk_path: P, current_sdk: u32) -> Result<Box<[u8]>> {
+/// Extracts the SignedData from the signature of the given APK. (The signature is not verified.)
+pub fn extract_signed_data<P: AsRef<Path>>(apk_path: P, current_sdk: u32) -> Result<SignedData> {
let apk = File::open(apk_path.as_ref())?;
let (signer, _) = extract_signer_and_apk_sections(apk, current_sdk)?;
- Ok(signer.public_key.public_key_to_der()?.into_boxed_slice())
+ signer.parse_signed_data()
}
pub(crate) fn extract_signer_and_apk_sections<R: Read + Seek>(
@@ -123,6 +106,10 @@
}
impl Signer {
+ fn sdk_range(&self) -> RangeInclusive<u32> {
+ self.min_sdk..=self.max_sdk
+ }
+
/// Selects the signature that has the strongest supported `SignatureAlgorithmID`.
/// The strongest signature is used in both v3 verification and v4 apk digest computation.
pub(crate) fn strongest_signature(&self) -> Result<&Signature> {
@@ -143,27 +130,33 @@
Ok(digest.digest.as_ref().to_vec().into_boxed_slice())
}
- /// Verifies the strongest signature from signatures against signed data using public key.
- /// Returns the verified signed data.
- fn verify_signature(&self, strongest: &Signature) -> Result<SignedData> {
- let mut verifier = strongest
+ /// Verifies a signature over the signed data using the public key.
+ fn verify_signature(&self, signature: &Signature) -> Result<()> {
+ let mut verifier = signature
.signature_algorithm_id
.context("Unsupported algorithm")?
.new_verifier(&self.public_key)?;
verifier.update(&self.signed_data)?;
- ensure!(verifier.verify(&strongest.signature)?, "Signature is invalid.");
- // It is now safe to parse signed data.
+ ensure!(verifier.verify(&signature.signature)?, "Signature is invalid.");
+ Ok(())
+ }
+
+ /// Returns the signed data, converted from bytes.
+ fn parse_signed_data(&self) -> Result<SignedData> {
self.signed_data.slice(..).read()
}
/// The steps in this method implements APK Signature Scheme v3 verification step 3.
- fn verify<R: Read + Seek>(&self, sections: &mut ApkSections<R>) -> Result<Box<[u8]>> {
+ fn verify<R: Read + Seek>(&self, sections: &mut ApkSections<R>) -> Result<SignedData> {
// 1. Choose the strongest supported signature algorithm ID from signatures.
let strongest = self.strongest_signature()?;
// 2. Verify the corresponding signature from signatures against signed data using public
// key.
- let verified_signed_data = self.verify_signature(strongest)?;
+ self.verify_signature(strongest)?;
+
+ // It is now safe to parse signed data.
+ let verified_signed_data = self.parse_signed_data()?;
// 3. Verify the min and max SDK versions in the signed data match those specified for the
// signer.
@@ -199,8 +192,7 @@
// 7. Verify that public key of the first certificate of certificates is identical to public
// key.
- let cert = verified_signed_data.certificates.first().context("No certificates listed")?;
- let cert = X509::from_der(cert.as_ref())?;
+ let cert = X509::from_der(verified_signed_data.first_certificate_der()?)?;
ensure!(
cert.public_key()?.public_eq(&self.public_key),
"Public key mismatch between certificate and signature record"
@@ -209,7 +201,29 @@
// TODO(b/245914104)
// 8. If the proof-of-rotation attribute exists for the signer verify that the
// struct is valid and this signer is the last certificate in the list.
- Ok(self.public_key.public_key_to_der()?.into_boxed_slice())
+
+ Ok(verified_signed_data)
+ }
+}
+
+impl SignedData {
+ /// Returns the first X.509 certificate in the signed data, encoded in DER form. (All other
+ /// certificates are ignored for v3; this certificate describes the public key that was actually
+ /// used to sign the APK.)
+ pub fn first_certificate_der(&self) -> Result<&[u8]> {
+ Ok(self.certificates.first().context("No certificates listed")?)
+ }
+
+ fn sdk_range(&self) -> RangeInclusive<u32> {
+ self.min_sdk..=self.max_sdk
+ }
+
+ fn find_digest_by_algorithm(&self, algorithm_id: SignatureAlgorithmID) -> Result<&Digest> {
+ Ok(self
+ .digests
+ .iter()
+ .find(|&dig| dig.signature_algorithm_id == Some(algorithm_id))
+ .context(format!("Digest not found for algorithm: {:?}", algorithm_id))?)
}
}
diff --git a/libs/apkverify/tests/apkverify_test.rs b/libs/apkverify/tests/apkverify_test.rs
index 0d8e020..441b708 100644
--- a/libs/apkverify/tests/apkverify_test.rs
+++ b/libs/apkverify/tests/apkverify_test.rs
@@ -14,12 +14,14 @@
* limitations under the License.
*/
+use anyhow::Result;
use apkverify::{
- get_apk_digest, get_public_key_der, testing::assert_contains, verify, SignatureAlgorithmID,
+ extract_signed_data, get_apk_digest, testing::assert_contains, verify, SignatureAlgorithmID,
};
use apkzip::zip_sections;
use byteorder::{LittleEndian, ReadBytesExt};
use log::info;
+use openssl::x509::X509;
use std::fmt::Write;
use std::io::{Seek, SeekFrom};
use std::{fs, matches, path::Path};
@@ -286,22 +288,30 @@
/// * public key extracted from apk without verification
/// * expected public key from the corresponding .der file
fn validate_apk_public_key<P: AsRef<Path>>(apk_path: P) {
- let public_key_from_verification = verify(&apk_path, SDK_INT);
- let public_key_from_verification =
- public_key_from_verification.expect("Error in verification result");
+ let signed_data_from_verification =
+ verify(&apk_path, SDK_INT).expect("Error in verification result");
+ let cert_from_verification = signed_data_from_verification.first_certificate_der().unwrap();
+ let public_key_from_verification = public_key_der_from_cert(cert_from_verification).unwrap();
let expected_public_key_path = format!("{}.der", apk_path.as_ref().to_str().unwrap());
assert_bytes_eq_to_data_in_file(&public_key_from_verification, expected_public_key_path);
- let public_key_from_apk = get_public_key_der(&apk_path, SDK_INT);
- let public_key_from_apk =
- public_key_from_apk.expect("Error when extracting public key from apk");
+ let signed_data_from_apk = extract_signed_data(&apk_path, SDK_INT)
+ .expect("Error when extracting signed data from apk");
+ let cert_from_apk = signed_data_from_apk.first_certificate_der().unwrap();
+ // If the two certficiates are byte for byte identical (which they should be), then so are
+ // the public keys embedded in them.
assert_eq!(
- public_key_from_verification, public_key_from_apk,
- "Public key extracted directly from apk does not match the public key from verification."
+ cert_from_verification, cert_from_apk,
+ "Certificate extracted directly from apk does not match the certificate from verification."
);
}
+fn public_key_der_from_cert(cert_der: &[u8]) -> Result<Vec<u8>> {
+ let cert = X509::from_der(cert_der)?;
+ Ok(cert.public_key()?.public_key_to_der()?)
+}
+
/// Validates that the following apk_digest are equal:
/// * apk_digest directly extracted from apk without computation
/// * computed apk_digest
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index e6ddfb9..0cf7013 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -30,17 +30,17 @@
payload_metadata: &PayloadMetadata,
) -> Result<OwnedDiceArtifacts> {
let subcomponents = build_subcomponent_list(instance_data);
- let config_descriptor = format_payload_config_descriptor(payload_metadata, &subcomponents)
+ let config_descriptor = format_payload_config_descriptor(payload_metadata, subcomponents)
.context("Building config descriptor")?;
// Calculate compound digests of code and authorities
let mut code_hash_ctx = Sha512::new();
let mut authority_hash_ctx = Sha512::new();
code_hash_ctx.update(instance_data.apk_data.root_hash.as_ref());
- authority_hash_ctx.update(instance_data.apk_data.pubkey.as_ref());
+ authority_hash_ctx.update(instance_data.apk_data.cert_hash.as_ref());
for extra_apk in &instance_data.extra_apks_data {
code_hash_ctx.update(extra_apk.root_hash.as_ref());
- authority_hash_ctx.update(extra_apk.pubkey.as_ref());
+ authority_hash_ctx.update(extra_apk.cert_hash.as_ref());
}
for apex in &instance_data.apex_data {
code_hash_ctx.update(apex.root_digest.as_ref());
@@ -57,42 +57,40 @@
dice.derive(code_hash, &config_descriptor, authority_hash, debuggable, hidden)
}
-struct Subcomponent<'a> {
+struct Subcomponent {
name: String,
version: u64,
- code_hash: &'a [u8],
- authority_hash: Box<[u8]>,
+ code_hash: Vec<u8>,
+ authority_hash: Vec<u8>,
}
-impl<'a> Subcomponent<'a> {
- fn to_value(&self) -> Result<Value> {
+impl Subcomponent {
+ fn into_value(self) -> Result<Value> {
Ok(cbor!({
1 => self.name,
2 => self.version,
- 3 => self.code_hash,
- 4 => self.authority_hash
+ 3 => Value::Bytes(self.code_hash),
+ 4 => Value::Bytes(self.authority_hash),
})?)
}
- fn for_apk(apk: &'a ApkData) -> Self {
+ fn for_apk(apk: &ApkData) -> Self {
Self {
name: format!("apk:{}", apk.package_name),
version: apk.version_code,
- code_hash: &apk.root_hash,
- authority_hash:
- // TODO(b/305925597): Hash the certificate not the pubkey
- Box::new(sha512(&apk.pubkey)),
+ code_hash: apk.root_hash.clone(),
+ authority_hash: apk.cert_hash.clone(),
}
}
- fn for_apex(apex: &'a ApexData) -> Self {
+ fn for_apex(apex: &ApexData) -> Self {
// Note that this is only reachable if the dice_changes flag is on, in which case
// the manifest data will always be present.
Self {
name: format!("apex:{}", apex.manifest_name.as_ref().unwrap()),
version: apex.manifest_version.unwrap() as u64,
- code_hash: &apex.root_digest,
- authority_hash: Box::new(sha512(&apex.public_key)),
+ code_hash: apex.root_digest.clone(),
+ authority_hash: sha512(&apex.public_key).to_vec(),
}
}
}
@@ -113,7 +111,7 @@
// of the format.
fn format_payload_config_descriptor(
payload: &PayloadMetadata,
- subcomponents: &[Subcomponent],
+ subcomponents: Vec<Subcomponent>,
) -> Result<Vec<u8>> {
let mut map = Vec::new();
map.push((cbor!(-70002)?, cbor!("Microdroid payload")?));
@@ -129,7 +127,7 @@
if !subcomponents.is_empty() {
let values =
- subcomponents.iter().map(Subcomponent::to_value).collect::<Result<Vec<_>>>()?;
+ subcomponents.into_iter().map(Subcomponent::into_value).collect::<Result<Vec<_>>>()?;
map.push((cbor!(-71002)?, cbor!(values)?));
}
@@ -141,7 +139,7 @@
use super::*;
use microdroid_metadata::PayloadConfig;
- const NO_SUBCOMPONENTS: [Subcomponent; 0] = [];
+ const NO_SUBCOMPONENTS: Vec<Subcomponent> = Vec::new();
fn assert_eq_bytes(expected: &[u8], actual: &[u8]) {
assert_eq!(
@@ -157,7 +155,7 @@
fn payload_metadata_with_path_formats_correctly() -> Result<()> {
let payload_metadata = PayloadMetadata::ConfigPath("/config_path".to_string());
let config_descriptor =
- format_payload_config_descriptor(&payload_metadata, &NO_SUBCOMPONENTS)?;
+ format_payload_config_descriptor(&payload_metadata, NO_SUBCOMPONENTS)?;
static EXPECTED_CONFIG_DESCRIPTOR: &[u8] = &[
0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72,
0x6f, 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01,
@@ -176,7 +174,7 @@
};
let payload_metadata = PayloadMetadata::Config(payload_config);
let config_descriptor =
- format_payload_config_descriptor(&payload_metadata, &NO_SUBCOMPONENTS)?;
+ format_payload_config_descriptor(&payload_metadata, NO_SUBCOMPONENTS)?;
static EXPECTED_CONFIG_DESCRIPTOR: &[u8] = &[
0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72,
0x6f, 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01,
@@ -190,31 +188,30 @@
#[test]
fn payload_metadata_with_subcomponents_formats_correctly() -> Result<()> {
let payload_metadata = PayloadMetadata::ConfigPath("/config_path".to_string());
- let subcomponents = [
+ let subcomponents = vec![
Subcomponent {
name: "apk1".to_string(),
version: 1,
- code_hash: &[42u8],
- authority_hash: Box::new([17u8]),
+ code_hash: vec![42, 43],
+ authority_hash: vec![17],
},
Subcomponent {
name: "apk2".to_string(),
version: 0x1000_0000_0001,
- code_hash: &[43u8],
- authority_hash: Box::new([19u8]),
+ code_hash: vec![43],
+ authority_hash: vec![19, 20],
},
];
- let config_descriptor =
- format_payload_config_descriptor(&payload_metadata, &subcomponents)?;
+ let config_descriptor = format_payload_config_descriptor(&payload_metadata, subcomponents)?;
// Verified using cbor.me.
static EXPECTED_CONFIG_DESCRIPTOR: &[u8] = &[
0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72,
0x6f, 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01,
0x15, 0x57, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x61, 0x74,
0x68, 0x3a, 0x00, 0x01, 0x15, 0x59, 0x82, 0xa4, 0x01, 0x64, 0x61, 0x70, 0x6b, 0x31,
- 0x02, 0x01, 0x03, 0x81, 0x18, 0x2a, 0x04, 0x81, 0x11, 0xa4, 0x01, 0x64, 0x61, 0x70,
- 0x6b, 0x32, 0x02, 0x1b, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x81,
- 0x18, 0x2b, 0x04, 0x81, 0x13,
+ 0x02, 0x01, 0x03, 0x42, 0x2a, 0x2b, 0x04, 0x41, 0x11, 0xa4, 0x01, 0x64, 0x61, 0x70,
+ 0x6b, 0x32, 0x02, 0x1b, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x41,
+ 0x2b, 0x04, 0x42, 0x13, 0x14,
];
assert_eq_bytes(EXPECTED_CONFIG_DESCRIPTOR, &config_descriptor);
Ok(())
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index b0fc03d..7a9f0e0 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -287,20 +287,18 @@
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ApkData {
- pub root_hash: Box<RootHash>,
- pub pubkey: Box<[u8]>,
+ pub root_hash: Vec<u8>,
+ pub cert_hash: Vec<u8>,
pub package_name: String,
pub version_code: u64,
}
impl ApkData {
pub fn root_hash_eq(&self, root_hash: &[u8]) -> bool {
- self.root_hash.as_ref() == root_hash
+ self.root_hash == root_hash
}
}
-pub type RootHash = [u8];
-
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ApexData {
pub name: String,
diff --git a/microdroid_manager/src/verify.rs b/microdroid_manager/src/verify.rs
index e63530b..445c1ae 100644
--- a/microdroid_manager/src/verify.rs
+++ b/microdroid_manager/src/verify.rs
@@ -12,16 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::instance::{ApexData, ApkData, MicrodroidData, RootHash};
+use crate::instance::{ApexData, ApkData, MicrodroidData};
use crate::payload::{get_apex_data_from_payload, to_metadata};
use crate::{is_strict_boot, is_verified_boot, MicrodroidError};
use anyhow::{anyhow, ensure, Context, Result};
use apkmanifest::get_manifest_info;
-use apkverify::{get_public_key_der, verify, V4Signature};
+use apkverify::{extract_signed_data, verify, V4Signature};
use glob::glob;
use itertools::sorted;
use log::{info, warn};
use microdroid_metadata::{write_metadata, Metadata};
+use openssl::sha::sha512;
use rand::Fill;
use rustutils::system_properties;
use std::fs::OpenOptions;
@@ -190,10 +191,10 @@
fn get_data_from_apk(
apk_path: &str,
- root_hash: Box<RootHash>,
+ root_hash: Box<[u8]>,
root_hash_trustful: bool,
) -> Result<ApkData> {
- let pubkey = get_public_key_from_apk(apk_path, root_hash_trustful)?;
+ let cert_hash = get_cert_hash_from_apk(apk_path, root_hash_trustful)?.to_vec();
// Read package name etc from the APK manifest. In the unlikely event that they aren't present
// we use the default values. We simply put these values in the DICE node for the payload, and
// users of that can decide how to handle blank information - there's no reason for us
@@ -203,8 +204,8 @@
.unwrap_or_default();
Ok(ApkData {
- root_hash,
- pubkey,
+ root_hash: root_hash.into(),
+ cert_hash,
package_name: manifest_info.package,
version_code: manifest_info.version_code,
})
@@ -233,21 +234,22 @@
Ok(())
}
-fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<RootHash>> {
+fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<[u8]>> {
Ok(V4Signature::from_idsig_path(idsig_path)?.hashing_info.raw_root_hash)
}
-fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
+fn get_cert_hash_from_apk(apk: &str, root_hash_trustful: bool) -> Result<[u8; 64]> {
let current_sdk = get_current_sdk()?;
- if !root_hash_trustful {
+ let signed_data = if !root_hash_trustful {
verify(apk, current_sdk).context(MicrodroidError::PayloadVerificationFailed(format!(
"failed to verify {}",
apk
)))
} else {
- get_public_key_der(apk, current_sdk)
- }
+ extract_signed_data(apk, current_sdk)
+ }?;
+ Ok(sha512(signed_data.first_certificate_der()?))
}
fn get_current_sdk() -> Result<u32> {
@@ -260,7 +262,7 @@
apk: &'a str,
idsig: &'a str,
name: &'a str,
- saved_root_hash: Option<&'a RootHash>,
+ saved_root_hash: Option<&'a [u8]>,
}
fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index 7023b95..4957df2 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -288,6 +288,6 @@
unsafe fn from_raw_range_mut(ptr: usize, range: NonEmptyRange) -> &'a mut [u8] {
// SAFETY: The caller must ensure that the range is valid from ptr.
- unsafe { slice::from_raw_parts_mut((ptr + range.start) as *mut u8, range.end()) }
+ unsafe { slice::from_raw_parts_mut((ptr + range.start) as *mut u8, range.len()) }
}
}
diff --git a/rialto/Android.bp b/rialto/Android.bp
index 90008a9..bbb5e54 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -15,12 +15,12 @@
"libciborium_nostd",
"libcstr",
"libdiced_open_dice_nostd",
- "libdiced_sample_inputs_nostd",
"libhyp",
"libfdtpci",
"liblibfdt",
"liblog_rust_nostd",
"libservice_vm_comm_nostd",
+ "libservice_vm_fake_chain_nostd",
"libservice_vm_requests_nostd",
"libtinyvec_nostd",
"libvirtio_drivers",
@@ -113,12 +113,10 @@
"libciborium",
"libclient_vm_csr",
"libcoset",
- "libcstr",
- "libdiced_open_dice",
- "libdiced_sample_inputs",
"liblibc",
"liblog_rust",
"libservice_vm_comm",
+ "libservice_vm_fake_chain",
"libservice_vm_manager",
"libvmclient",
"libx509_parser",
diff --git a/rialto/src/main.rs b/rialto/src/main.rs
index 0bdc927..1215021 100644
--- a/rialto/src/main.rs
+++ b/rialto/src/main.rs
@@ -38,6 +38,7 @@
use libfdt::FdtError;
use log::{debug, error, info};
use service_vm_comm::{RequestProcessingError, Response, ServiceVmRequest, VmType};
+use service_vm_fake_chain::service_vm;
use service_vm_requests::process_request;
use virtio_drivers::{
device::socket::{VsockAddr, VMADDR_CID_HOST},
@@ -163,9 +164,7 @@
}
// Currently, a sample DICE data is used for non-protected VMs, as these VMs only run
// in tests at the moment.
- // If we intend to run non-protected rialto in production, we should retrieve real
- // DICE chain data instead.
- VmType::NonProtectedVm => Box::new(diced_sample_inputs::make_sample_bcc_and_cdis()?),
+ VmType::NonProtectedVm => Box::new(service_vm::fake_service_vm_dice_artifacts()?),
};
let pci_info = PciInfo::from_fdt(fdt)?;
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 029895f..02a5a28 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -26,16 +26,14 @@
use ciborium::value::Value;
use client_vm_csr::generate_attestation_key_and_csr;
use coset::{CborSerializable, CoseMac0, CoseSign};
-use cstr::cstr;
-use diced_open_dice::{
- retry_bcc_format_config_descriptor, retry_bcc_main_flow, Config, DiceArtifacts,
- DiceConfigValues, DiceMode, InputValues, OwnedDiceArtifacts, HASH_SIZE, HIDDEN_SIZE,
-};
use log::info;
use service_vm_comm::{
ClientVmAttestationParams, Csr, CsrPayload, EcdsaP256KeyPair, GenerateCertificateRequestParams,
Request, RequestProcessingError, Response, VmType,
};
+use service_vm_fake_chain::client_vm::{
+ fake_client_vm_dice_artifacts, fake_sub_components, SubComponent,
+};
use service_vm_manager::ServiceVm;
use std::fs;
use std::fs::File;
@@ -45,7 +43,7 @@
use vmclient::VmInstance;
use x509_parser::{
certificate::X509Certificate,
- der_parser::{der::parse_der, oid, oid::Oid},
+ der_parser::{ber::BerObject, der::parse_der, oid, oid::Oid},
prelude::FromDer,
x509::{AlgorithmIdentifier, SubjectPublicKeyInfo, X509Version},
};
@@ -53,19 +51,6 @@
const UNSIGNED_RIALTO_PATH: &str = "/data/local/tmp/rialto_test/arm64/rialto_unsigned.bin";
const INSTANCE_IMG_PATH: &str = "/data/local/tmp/rialto_test/arm64/instance.img";
const TEST_CERT_CHAIN_PATH: &str = "testdata/rkp_cert_chain.der";
-/// The following data are generated randomly with urandom.
-const CODE_HASH_MICRODROID: [u8; HASH_SIZE] = [
- 0x08, 0x78, 0xc2, 0x5b, 0xe7, 0xea, 0x3d, 0x62, 0x70, 0x22, 0xd9, 0x1c, 0x4f, 0x3c, 0x2e, 0x2f,
- 0x0f, 0x97, 0xa4, 0x6f, 0x6d, 0xd5, 0xe6, 0x4a, 0x6d, 0xbe, 0x34, 0x2e, 0x56, 0x04, 0xaf, 0xef,
- 0x74, 0x3f, 0xec, 0xb8, 0x44, 0x11, 0xf4, 0x2f, 0x05, 0xb2, 0x06, 0xa3, 0x0e, 0x75, 0xb7, 0x40,
- 0x9a, 0x4c, 0x58, 0xab, 0x96, 0xe7, 0x07, 0x97, 0x07, 0x86, 0x5c, 0xa1, 0x42, 0x12, 0xf0, 0x34,
-];
-const AUTHORITY_HASH_MICRODROID: [u8; HASH_SIZE] = [
- 0xc7, 0x97, 0x5b, 0xa9, 0x9e, 0xbf, 0x0b, 0xeb, 0xe7, 0x7f, 0x69, 0x8f, 0x8e, 0xcf, 0x04, 0x7d,
- 0x2c, 0x0f, 0x4d, 0xbe, 0xcb, 0xf5, 0xf1, 0x4c, 0x1d, 0x1c, 0xb7, 0x44, 0xdf, 0xf8, 0x40, 0x90,
- 0x09, 0x65, 0xab, 0x01, 0x34, 0x3e, 0xc2, 0xc4, 0xf7, 0xa2, 0x3a, 0x5c, 0x4e, 0x76, 0x4f, 0x42,
- 0xa8, 0x6c, 0xc9, 0xf1, 0x7b, 0x12, 0x80, 0xa4, 0xef, 0xa2, 0x4d, 0x72, 0xa1, 0x21, 0xe2, 0x47,
-];
#[test]
fn process_requests_in_protected_vm() -> Result<()> {
@@ -148,8 +133,7 @@
0x7d, 0x86, 0x58, 0x79, 0x3a, 0x09, 0xdf, 0x1c, 0xa5, 0x80, 0x80, 0x15, 0x2b, 0x13, 0x17,
0x5c,
];
- let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
- let dice_artifacts = extend_dice_artifacts_with_microdroid_payload(&dice_artifacts)?;
+ let dice_artifacts = fake_client_vm_dice_artifacts()?;
let attestation_data = generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
let cert_chain = fs::read(TEST_CERT_CHAIN_PATH)?;
let (remaining, cert) = X509Certificate::from_der(&cert_chain)?;
@@ -193,30 +177,23 @@
}
}
-fn extend_dice_artifacts_with_microdroid_payload(
- dice_artifacts: &dyn DiceArtifacts,
-) -> Result<OwnedDiceArtifacts> {
- let config_values = DiceConfigValues {
- component_name: Some(cstr!("Microdroid payload")),
- component_version: Some(1),
- resettable: true,
- ..Default::default()
- };
- let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
- let input_values = InputValues::new(
- CODE_HASH_MICRODROID,
- Config::Descriptor(config_descriptor.as_slice()),
- AUTHORITY_HASH_MICRODROID,
- DiceMode::kDiceModeDebug,
- [0u8; HIDDEN_SIZE], // hidden
- );
- retry_bcc_main_flow(
- dice_artifacts.cdi_attest(),
- dice_artifacts.cdi_seal(),
- dice_artifacts.bcc().unwrap(),
- &input_values,
- )
- .context("Failed to run BCC main flow for Microdroid")
+fn check_vm_components(vm_components: &[BerObject]) -> Result<()> {
+ let expected_components = fake_sub_components();
+ assert_eq!(expected_components.len(), vm_components.len());
+ for i in 0..expected_components.len() {
+ check_vm_component(&vm_components[i], &expected_components[i])?;
+ }
+ Ok(())
+}
+
+fn check_vm_component(vm_component: &BerObject, expected_component: &SubComponent) -> Result<()> {
+ let vm_component = vm_component.as_sequence()?;
+ assert_eq!(4, vm_component.len());
+ assert_eq!(expected_component.name, vm_component[0].as_str()?);
+ assert_eq!(expected_component.version, vm_component[1].as_u64()?);
+ assert_eq!(expected_component.code_hash, vm_component[2].as_slice()?);
+ assert_eq!(expected_component.authority_hash, vm_component[3].as_slice()?);
+ Ok(())
}
fn check_certificate_for_client_vm(
@@ -262,13 +239,15 @@
let (remaining, extension) = parse_der(extension.value)?;
assert!(remaining.is_empty());
let attestation_ext = extension.as_sequence()?;
- assert_eq!(2, attestation_ext.len());
+ assert_eq!(3, attestation_ext.len());
assert_eq!(csr_payload.challenge, attestation_ext[0].as_slice()?);
let is_vm_secure = attestation_ext[1].as_bool()?;
assert!(
!is_vm_secure,
"The VM shouldn't be secure as the last payload added in the test is in Debug mode"
);
+ let vm_components = attestation_ext[2].as_sequence()?;
+ check_vm_components(vm_components)?;
// Checks other fields on the certificate
assert_eq!(X509Version::V3, cert.version());
diff --git a/service_vm/comm/src/csr.rs b/service_vm/comm/src/csr.rs
index 2a27f90..f14787f 100644
--- a/service_vm/comm/src/csr.rs
+++ b/service_vm/comm/src/csr.rs
@@ -55,8 +55,8 @@
return Err(CoseError::UnexpectedItem("array", "array with 2 items"));
}
Ok(Self {
- signed_csr_payload: try_as_bytes(arr.remove(1), "signed_csr_payload")?,
- dice_cert_chain: try_as_bytes(arr.remove(0), "dice_cert_chain")?,
+ signed_csr_payload: value_to_bytes(arr.remove(1), "signed_csr_payload")?,
+ dice_cert_chain: value_to_bytes(arr.remove(0), "dice_cert_chain")?,
})
}
}
@@ -94,21 +94,28 @@
return Err(CoseError::UnexpectedItem("array", "array with 2 items"));
}
Ok(Self {
- challenge: try_as_bytes(arr.remove(1), "challenge")?,
- public_key: try_as_bytes(arr.remove(0), "public_key")?,
+ challenge: value_to_bytes(arr.remove(1), "challenge")?,
+ public_key: value_to_bytes(arr.remove(0), "public_key")?,
})
}
}
-/// Reads the provided value `v` as bytes array.
-pub fn try_as_bytes(v: Value, context: &str) -> coset::Result<Vec<u8>> {
- if let Value::Bytes(data) = v {
- Ok(data)
- } else {
- let v_type = cbor_value_type(&v);
- error!("The provided value type '{v_type}' is not of type 'bytes': {context}");
- Err(CoseError::UnexpectedItem(v_type, "bytes"))
- }
+/// Converts the provided value `v` to bytes array.
+pub fn value_to_bytes(v: Value, context: &'static str) -> coset::Result<Vec<u8>> {
+ v.into_bytes().map_err(|e| to_unexpected_item_error(&e, "bstr", context))
+}
+
+/// Builds a `CoseError::UnexpectedItem` error when the provided value `v` is not of the expected
+/// type `expected_type` and logs the error message with the provided `context`.
+pub fn to_unexpected_item_error(
+ v: &Value,
+ expected_type: &'static str,
+ context: &'static str,
+) -> CoseError {
+ let v_type = cbor_value_type(v);
+ assert!(v_type != expected_type);
+ error!("The provided value type '{v_type}' is not of type '{expected_type}': {context}");
+ CoseError::UnexpectedItem(v_type, expected_type)
}
/// Reads the type of the provided value `v`.
diff --git a/service_vm/comm/src/lib.rs b/service_vm/comm/src/lib.rs
index c9de540..343d48e 100644
--- a/service_vm/comm/src/lib.rs
+++ b/service_vm/comm/src/lib.rs
@@ -23,7 +23,7 @@
mod message;
mod vsock;
-pub use csr::{cbor_value_type, try_as_bytes, Csr, CsrPayload};
+pub use csr::{cbor_value_type, to_unexpected_item_error, value_to_bytes, Csr, CsrPayload};
pub use message::{
ClientVmAttestationParams, EcdsaP256KeyPair, GenerateCertificateRequestParams, Request,
RequestProcessingError, Response, ServiceVmRequest,
diff --git a/service_vm/fake_chain/Android.bp b/service_vm/fake_chain/Android.bp
new file mode 100644
index 0000000..ebc185d
--- /dev/null
+++ b/service_vm/fake_chain/Android.bp
@@ -0,0 +1,56 @@
+// Copyright 2023, 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.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libservice_vm_fake_chain_defaults",
+ crate_name: "service_vm_fake_chain",
+ defaults: ["avf_build_flags_rust"],
+ srcs: ["src/lib.rs"],
+ visibility: [
+ "//packages/modules/Virtualization/rialto:__subpackages__",
+ ],
+ rustlibs: [
+ "libcstr",
+ ],
+}
+
+rust_library {
+ name: "libservice_vm_fake_chain",
+ defaults: ["libservice_vm_fake_chain_defaults"],
+ features: [
+ "std",
+ ],
+ rustlibs: [
+ "libciborium",
+ "libcoset",
+ "libdiced_open_dice",
+ "liblog_rust",
+ ],
+}
+
+rust_library_rlib {
+ name: "libservice_vm_fake_chain_nostd",
+ defaults: ["libservice_vm_fake_chain_defaults"],
+ rustlibs: [
+ "libciborium_nostd",
+ "libcoset_nostd",
+ "libdiced_open_dice_nostd",
+ "liblog_rust_nostd",
+ ],
+
+}
diff --git a/service_vm/fake_chain/src/client_vm.rs b/service_vm/fake_chain/src/client_vm.rs
new file mode 100644
index 0000000..eb8654b
--- /dev/null
+++ b/service_vm/fake_chain/src/client_vm.rs
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+//! Provides functions to build fake DICE artifacts for client VM in tests.
+
+use crate::service_vm;
+use alloc::vec;
+use alloc::vec::Vec;
+use ciborium::{cbor, value::Value};
+use core::result;
+use coset::CborSerializable;
+use cstr::cstr;
+use diced_open_dice::{
+ retry_bcc_format_config_descriptor, retry_bcc_main_flow, Config, DiceArtifacts,
+ DiceConfigValues, DiceError, DiceMode, InputValues, OwnedDiceArtifacts, Result, HASH_SIZE,
+ HIDDEN_SIZE,
+};
+use log::error;
+
+type CborResult<T> = result::Result<T, ciborium::value::Error>;
+
+/// All the following data are generated with urandom.
+const CODE_HASH_KERNEL: [u8; HASH_SIZE] = [
+ 0xc8, 0x54, 0x6c, 0xad, 0x9d, 0xe7, 0x25, 0xc7, 0x2b, 0xed, 0x07, 0xe1, 0xe9, 0x1a, 0xb0, 0xd0,
+ 0xa7, 0x7f, 0x43, 0xb9, 0xe4, 0x56, 0x79, 0x0d, 0x7d, 0xd8, 0xc5, 0xdd, 0xad, 0x0d, 0x31, 0x85,
+ 0xaf, 0x94, 0x02, 0xd8, 0x9d, 0x70, 0xab, 0xba, 0xac, 0xc7, 0x12, 0x80, 0xec, 0x7b, 0x9b, 0x65,
+ 0xec, 0x6b, 0xdd, 0x64, 0x94, 0xd0, 0x9a, 0x3a, 0x09, 0xf2, 0x49, 0xdb, 0x60, 0x3c, 0x50, 0x30,
+];
+const CODE_HASH_PAYLOAD: [u8; HASH_SIZE] = [
+ 0x08, 0x78, 0xc2, 0x5b, 0xe7, 0xea, 0x3d, 0x62, 0x70, 0x22, 0xd9, 0x1c, 0x4f, 0x3c, 0x2e, 0x2f,
+ 0x0f, 0x97, 0xa4, 0x6f, 0x6d, 0xd5, 0xe6, 0x4a, 0x6d, 0xbe, 0x34, 0x2e, 0x56, 0x04, 0xaf, 0xef,
+ 0x74, 0x3f, 0xec, 0xb8, 0x44, 0x11, 0xf4, 0x2f, 0x05, 0xb2, 0x06, 0xa3, 0x0e, 0x75, 0xb7, 0x40,
+ 0x9a, 0x4c, 0x58, 0xab, 0x96, 0xe7, 0x07, 0x97, 0x07, 0x86, 0x5c, 0xa1, 0x42, 0x12, 0xf0, 0x34,
+];
+const AUTHORITY_HASH_PAYLOAD: [u8; HASH_SIZE] = [
+ 0xc7, 0x97, 0x5b, 0xa9, 0x9e, 0xbf, 0x0b, 0xeb, 0xe7, 0x7f, 0x69, 0x8f, 0x8e, 0xcf, 0x04, 0x7d,
+ 0x2c, 0x0f, 0x4d, 0xbe, 0xcb, 0xf5, 0xf1, 0x4c, 0x1d, 0x1c, 0xb7, 0x44, 0xdf, 0xf8, 0x40, 0x90,
+ 0x09, 0x65, 0xab, 0x01, 0x34, 0x3e, 0xc2, 0xc4, 0xf7, 0xa2, 0x3a, 0x5c, 0x4e, 0x76, 0x4f, 0x42,
+ 0xa8, 0x6c, 0xc9, 0xf1, 0x7b, 0x12, 0x80, 0xa4, 0xef, 0xa2, 0x4d, 0x72, 0xa1, 0x21, 0xe2, 0x47,
+];
+const APK1_CODE_HASH: &[u8] = &[
+ 0x41, 0x92, 0x0d, 0xd0, 0xf5, 0x60, 0xe3, 0x69, 0x26, 0x7f, 0xb8, 0xbc, 0x12, 0x3a, 0xd1, 0x95,
+ 0x1d, 0xb8, 0x9a, 0x9c, 0x3a, 0x3f, 0x01, 0xbf, 0xa8, 0xd9, 0x6d, 0xe9, 0x90, 0x30, 0x1d, 0x0b,
+];
+const APK1_AUTHORITY_HASH: &[u8] = &[
+ 0xe3, 0xd9, 0x1c, 0xf5, 0x6f, 0xee, 0x73, 0x40, 0x3d, 0x95, 0x59, 0x67, 0xea, 0x5d, 0x01, 0xfd,
+ 0x25, 0x9d, 0x5c, 0x88, 0x94, 0x3a, 0xc6, 0xd7, 0xa9, 0xdc, 0x4c, 0x60, 0x81, 0xbe, 0x2b, 0x74,
+];
+const APEX1_CODE_HASH: &[u8] = &[
+ 0x52, 0x93, 0x2b, 0xb0, 0x8d, 0xec, 0xdf, 0x54, 0x1f, 0x5c, 0x10, 0x9d, 0x17, 0xce, 0x7f, 0xac,
+ 0xb0, 0x2b, 0xe2, 0x99, 0x05, 0x7d, 0xa3, 0x9b, 0xa6, 0x3e, 0xf9, 0x99, 0xa2, 0xea, 0xd4, 0xd9,
+];
+const APEX1_AUTHORITY_HASH: &[u8] = &[
+ 0xd1, 0xfc, 0x3d, 0x5f, 0xa0, 0x5f, 0x02, 0xd0, 0x83, 0x9b, 0x0e, 0x32, 0xc2, 0x27, 0x09, 0x12,
+ 0xcc, 0xfc, 0x42, 0xf6, 0x0d, 0xf4, 0x7d, 0xc8, 0x80, 0x1a, 0x64, 0x25, 0xa7, 0xfa, 0x4a, 0x37,
+];
+
+#[allow(missing_docs)]
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct SubComponent {
+ pub name: String,
+ pub version: u64,
+ pub code_hash: Vec<u8>,
+ pub authority_hash: Vec<u8>,
+}
+
+impl SubComponent {
+ fn to_value(&self) -> CborResult<Value> {
+ Ok(cbor!({
+ 1 => self.name,
+ 2 => self.version,
+ 3 => Value::Bytes(self.code_hash.clone()),
+ 4 => Value::Bytes(self.authority_hash.clone()),
+ })?)
+ }
+}
+
+/// Generates fake DICE artifacts for client VM with a DICE chain up to the certificate
+/// describing the Microdroid payload.
+///
+/// The fake DICE chain has the following nodes:
+/// Root public key -> pvmfw certificate -> Microdroid kernel certificate
+/// -> Microdroid payload certificate
+pub fn fake_client_vm_dice_artifacts() -> Result<OwnedDiceArtifacts> {
+ // Client VM DICE chain has the same prefix as the service VM DICE chain up to
+ // the pvmfw entry.
+ let (cdi_values, dice_chain) = service_vm::fake_dice_artifacts_up_to_pvmfw()?;
+
+ // Adds an entry describing the Microdroid kernel.
+ let config_values = DiceConfigValues {
+ component_name: Some(cstr!("vm_entry")),
+ component_version: Some(12),
+ resettable: true,
+ ..Default::default()
+ };
+ let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
+ // The Microdroid kernel is signed with the same key as the one used for the service VM,
+ // so the authority hash is the same.
+ let authority_hash = service_vm::AUTHORITY_HASH_SERVICE_VM;
+ let input_values = InputValues::new(
+ CODE_HASH_KERNEL,
+ Config::Descriptor(config_descriptor.as_slice()),
+ authority_hash,
+ DiceMode::kDiceModeDebug,
+ [0; HIDDEN_SIZE], // No hidden.
+ );
+ let dice_artifacts = retry_bcc_main_flow(
+ &cdi_values.cdi_attest,
+ &cdi_values.cdi_seal,
+ &dice_chain,
+ &input_values,
+ )
+ .map_err(|e| {
+ error!("Failed to run the Microdroid kernel BCC main flow: {e}");
+ e
+ })?;
+
+ // Adds an entry describing the Microdroid payload.
+ let config_descriptor = fake_microdroid_payload_config_descriptor().map_err(|e| {
+ error!("Failed to generate config descriptor for Microdroid: {e}");
+ DiceError::InvalidInput
+ })?;
+ let input_values = InputValues::new(
+ CODE_HASH_PAYLOAD,
+ Config::Descriptor(config_descriptor.as_slice()),
+ AUTHORITY_HASH_PAYLOAD,
+ DiceMode::kDiceModeDebug,
+ [0u8; HIDDEN_SIZE], // hidden
+ );
+ retry_bcc_main_flow(
+ dice_artifacts.cdi_attest(),
+ dice_artifacts.cdi_seal(),
+ dice_artifacts.bcc().unwrap(),
+ &input_values,
+ )
+ .map_err(|e| {
+ error!("Failed to run the Microdroid payload BCC main flow: {e}");
+ e
+ })
+}
+
+fn fake_microdroid_payload_config_descriptor() -> CborResult<Vec<u8>> {
+ let mut map = Vec::new();
+ map.push((cbor!(-70002)?, cbor!("Microdroid payload")?));
+ map.push((cbor!(-71000)?, cbor!("/config_path")?));
+ let components =
+ fake_sub_components().iter().map(|c| c.to_value()).collect::<CborResult<_>>()?;
+ map.push((cbor!(-71002)?, Value::Array(components)));
+ Ok(Value::Map(map).to_vec().unwrap())
+}
+
+/// Generates a list of fake subcomponents as the Microdroid payload.
+pub fn fake_sub_components() -> Vec<SubComponent> {
+ vec![
+ SubComponent {
+ name: "apk:com.android.apk.apk1".to_string(),
+ version: 1,
+ code_hash: APK1_CODE_HASH.to_vec(),
+ authority_hash: APK1_AUTHORITY_HASH.to_vec(),
+ },
+ SubComponent {
+ name: "apex:com.android.apex.apex1".to_string(),
+ version: 1,
+ code_hash: APEX1_CODE_HASH.to_vec(),
+ authority_hash: APEX1_AUTHORITY_HASH.to_vec(),
+ },
+ ]
+}
diff --git a/service_vm/fake_chain/src/lib.rs b/service_vm/fake_chain/src/lib.rs
new file mode 100644
index 0000000..a5ab828
--- /dev/null
+++ b/service_vm/fake_chain/src/lib.rs
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+//! Provides functions to build a test chain for non-protected rialto and tests.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+extern crate alloc;
+
+// `client_vm` builds DICE artifacts related to Microdroid, which is not relevant
+// to the nostd build used in rialto.
+#[cfg(feature = "std")]
+pub mod client_vm;
+pub mod service_vm;
diff --git a/service_vm/fake_chain/src/service_vm.rs b/service_vm/fake_chain/src/service_vm.rs
new file mode 100644
index 0000000..9bd831d
--- /dev/null
+++ b/service_vm/fake_chain/src/service_vm.rs
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+//! Provides functions to build fake DICE artifacts for non-protected rialto used in
+//! end-to-end tests.
+
+use alloc::vec;
+use alloc::vec::Vec;
+use ciborium::value::Value;
+use coset::{
+ iana::{self, EnumI64},
+ Algorithm, AsCborValue, CborSerializable, CoseKey, KeyOperation, KeyType, Label,
+};
+use cstr::cstr;
+use diced_open_dice::{
+ derive_cdi_private_key_seed, keypair_from_seed, retry_bcc_format_config_descriptor,
+ retry_bcc_main_flow, retry_dice_main_flow, CdiValues, Config, DiceConfigValues, DiceError,
+ DiceMode, InputValues, OwnedDiceArtifacts, Result, CDI_SIZE, HASH_SIZE, HIDDEN_SIZE,
+};
+use log::error;
+
+/// All the following data are generated with urandom.
+const UDS: [u8; CDI_SIZE] = [
+ 0x1d, 0xa5, 0xea, 0x90, 0x47, 0xfc, 0xb5, 0xf6, 0x47, 0x12, 0xd3, 0x65, 0x9c, 0xf2, 0x00, 0xe0,
+ 0x06, 0xf7, 0xe8, 0x9e, 0x2f, 0xd0, 0x94, 0x7f, 0xc9, 0x9a, 0x9d, 0x40, 0xf7, 0xce, 0x13, 0x21,
+];
+const CODE_HASH_PVMFW: [u8; HASH_SIZE] = [
+ 0x16, 0x48, 0xf2, 0x55, 0x53, 0x23, 0xdd, 0x15, 0x2e, 0x83, 0x38, 0xc3, 0x64, 0x38, 0x63, 0x26,
+ 0x0f, 0xcf, 0x5b, 0xd1, 0x3a, 0xd3, 0x40, 0x3e, 0x23, 0xf8, 0x34, 0x4c, 0x6d, 0xa2, 0xbe, 0x25,
+ 0x1c, 0xb0, 0x29, 0xe8, 0xc3, 0xfb, 0xb8, 0x80, 0xdc, 0xb1, 0xd2, 0xb3, 0x91, 0x4d, 0xd3, 0xfb,
+ 0x01, 0x0f, 0xe4, 0xe9, 0x46, 0xa2, 0xc0, 0x26, 0x57, 0x5a, 0xba, 0x30, 0xf7, 0x15, 0x98, 0x14,
+];
+const AUTHORITY_HASH_PVMFW: [u8; HASH_SIZE] = [
+ 0xf9, 0x00, 0x9d, 0xc2, 0x59, 0x09, 0xe0, 0xb6, 0x98, 0xbd, 0xe3, 0x97, 0x4a, 0xcb, 0x3c, 0xe7,
+ 0x6b, 0x24, 0xc3, 0xe4, 0x98, 0xdd, 0xa9, 0x6a, 0x41, 0x59, 0x15, 0xb1, 0x23, 0xe6, 0xc8, 0xdf,
+ 0xfb, 0x52, 0xb4, 0x52, 0xc1, 0xb9, 0x61, 0xdd, 0xbc, 0x5b, 0x37, 0x0e, 0x12, 0x12, 0xb2, 0xfd,
+ 0xc1, 0x09, 0xb0, 0xcf, 0x33, 0x81, 0x4c, 0xc6, 0x29, 0x1b, 0x99, 0xea, 0xae, 0xfd, 0xaa, 0x0d,
+];
+const HIDDEN_PVMFW: [u8; HIDDEN_SIZE] = [
+ 0xa2, 0x01, 0xd0, 0xc0, 0xaa, 0x75, 0x3c, 0x06, 0x43, 0x98, 0x6c, 0xc3, 0x5a, 0xb5, 0x5f, 0x1f,
+ 0x0f, 0x92, 0x44, 0x3b, 0x0e, 0xd4, 0x29, 0x75, 0xe3, 0xdb, 0x36, 0xda, 0xc8, 0x07, 0x97, 0x4d,
+ 0xff, 0xbc, 0x6a, 0xa4, 0x8a, 0xef, 0xc4, 0x7f, 0xf8, 0x61, 0x7d, 0x51, 0x4d, 0x2f, 0xdf, 0x7e,
+ 0x8c, 0x3d, 0xa3, 0xfc, 0x63, 0xd4, 0xd4, 0x74, 0x8a, 0xc4, 0x14, 0x45, 0x83, 0x6b, 0x12, 0x7e,
+];
+const CODE_HASH_SERVICE_VM: [u8; HASH_SIZE] = [
+ 0xa4, 0x0c, 0xcb, 0xc1, 0xbf, 0xfa, 0xcc, 0xfd, 0xeb, 0xf4, 0xfc, 0x43, 0x83, 0x7f, 0x46, 0x8d,
+ 0xd8, 0xd8, 0x14, 0xc1, 0x96, 0x14, 0x1f, 0x6e, 0xb3, 0xa0, 0xd9, 0x56, 0xb3, 0xbf, 0x2f, 0xfa,
+ 0x88, 0x70, 0x11, 0x07, 0x39, 0xa4, 0xd2, 0xa9, 0x6b, 0x18, 0x28, 0xe8, 0x29, 0x20, 0x49, 0x0f,
+ 0xbb, 0x8d, 0x08, 0x8c, 0xc6, 0x54, 0xe9, 0x71, 0xd2, 0x7e, 0xa4, 0xfe, 0x58, 0x7f, 0xd3, 0xc7,
+];
+pub(crate) const AUTHORITY_HASH_SERVICE_VM: [u8; HASH_SIZE] = [
+ 0xb2, 0x69, 0x05, 0x48, 0x56, 0xb5, 0xfa, 0x55, 0x6f, 0xac, 0x56, 0xd9, 0x02, 0x35, 0x2b, 0xaa,
+ 0x4c, 0xba, 0x28, 0xdd, 0x82, 0x3a, 0x86, 0xf5, 0xd4, 0xc2, 0xf1, 0xf9, 0x35, 0x7d, 0xe4, 0x43,
+ 0x13, 0xbf, 0xfe, 0xd3, 0x36, 0xd8, 0x1c, 0x12, 0x78, 0x5c, 0x9c, 0x3e, 0xf6, 0x66, 0xef, 0xab,
+ 0x3d, 0x0f, 0x89, 0xa4, 0x6f, 0xc9, 0x72, 0xee, 0x73, 0x43, 0x02, 0x8a, 0xef, 0xbc, 0x05, 0x98,
+];
+const HIDDEN_SERVICE_VM: [u8; HIDDEN_SIZE] = [
+ 0x5b, 0x3f, 0xc9, 0x6b, 0xe3, 0x95, 0x59, 0x40, 0x5e, 0x64, 0xe5, 0x64, 0x3f, 0xfd, 0x21, 0x09,
+ 0x9d, 0xf3, 0xcd, 0xc7, 0xa4, 0x2a, 0xe2, 0x97, 0xdd, 0xe2, 0x4f, 0xb0, 0x7d, 0x7e, 0xf5, 0x8e,
+ 0xd6, 0x4d, 0x84, 0x25, 0x54, 0x41, 0x3f, 0x8f, 0x78, 0x64, 0x1a, 0x51, 0x27, 0x9d, 0x55, 0x8a,
+ 0xe9, 0x90, 0x35, 0xab, 0x39, 0x80, 0x4b, 0x94, 0x40, 0x84, 0xa2, 0xfd, 0x73, 0xeb, 0x35, 0x7a,
+];
+
+fn ed25519_public_key_to_cbor_value(public_key: &[u8]) -> Result<Value> {
+ let key = CoseKey {
+ kty: KeyType::Assigned(iana::KeyType::OKP),
+ alg: Some(Algorithm::Assigned(iana::Algorithm::EdDSA)),
+ key_ops: vec![KeyOperation::Assigned(iana::KeyOperation::Verify)].into_iter().collect(),
+ params: vec![
+ (
+ Label::Int(iana::Ec2KeyParameter::Crv.to_i64()),
+ iana::EllipticCurve::Ed25519.to_i64().into(),
+ ),
+ (Label::Int(iana::Ec2KeyParameter::X.to_i64()), Value::Bytes(public_key.to_vec())),
+ ],
+ ..Default::default()
+ };
+ key.to_cbor_value().map_err(|e| {
+ error!("Failed to serialize the key to CBOR data: {e}");
+ DiceError::InvalidInput
+ })
+}
+
+/// Generates a fake DICE artifacts with a DICE chain up to the certificate describing pvmfw.
+///
+/// The fake DICE chain has the following nodes:
+/// Root public key -> pvmfw certificate
+pub(crate) fn fake_dice_artifacts_up_to_pvmfw() -> Result<(CdiValues, Vec<u8>)> {
+ let private_key_seed = derive_cdi_private_key_seed(&UDS).map_err(|e| {
+ error!("Failed to derive private key seed: {e}");
+ e
+ })?;
+
+ // Gets the root public key in DICE chain.
+ let (public_key, _) = keypair_from_seed(private_key_seed.as_array()).map_err(|e| {
+ error!("Failed to generate key pair: {e}");
+ e
+ })?;
+ let ed25519_public_key_value = ed25519_public_key_to_cbor_value(&public_key)?;
+
+ // Gets the pvmfw certificate to as the root certificate of DICE chain.
+ let config_values = DiceConfigValues {
+ component_name: Some(cstr!("Protected VM firmware")),
+ component_version: Some(1),
+ resettable: true,
+ ..Default::default()
+ };
+ let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
+ let input_values = InputValues::new(
+ CODE_HASH_PVMFW,
+ Config::Descriptor(config_descriptor.as_slice()),
+ AUTHORITY_HASH_PVMFW,
+ DiceMode::kDiceModeDebug,
+ HIDDEN_PVMFW,
+ );
+ let (cdi_values, cert) = retry_dice_main_flow(&UDS, &UDS, &input_values).map_err(|e| {
+ error!("Failed to run first main flow: {e}");
+ e
+ })?;
+ let dice_chain = Value::Array(vec![
+ ed25519_public_key_value,
+ Value::from_slice(&cert).map_err(|e| {
+ error!("Deserialize root DICE certificate failed: {e}");
+ DiceError::InvalidInput
+ })?,
+ ]);
+ let dice_chain = dice_chain.to_vec().map_err(|e| {
+ error!("Failed to serialize the DICE chain to CBOR data: {e}");
+ DiceError::InvalidInput
+ })?;
+ Ok((cdi_values, dice_chain))
+}
+
+/// Generates fake DICE artifacts for service VM with a DICE chain up to the certificate
+/// describing service VM.
+///
+/// The fake DICE chain has the following nodes:
+/// Root public key -> pvmfw certificate -> service VM certificate
+///
+/// The fake DICE chain is solely used in non-protected rialto for testing
+/// purposes.
+pub fn fake_service_vm_dice_artifacts() -> Result<OwnedDiceArtifacts> {
+ let (cdi_values, dice_chain) = fake_dice_artifacts_up_to_pvmfw()?;
+ let config_values = DiceConfigValues {
+ component_name: Some(cstr!("vm_entry")),
+ component_version: Some(12),
+ resettable: true,
+ ..Default::default()
+ };
+ let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
+ let input_values = InputValues::new(
+ CODE_HASH_SERVICE_VM,
+ Config::Descriptor(config_descriptor.as_slice()),
+ AUTHORITY_HASH_SERVICE_VM,
+ DiceMode::kDiceModeDebug,
+ HIDDEN_SERVICE_VM,
+ );
+ retry_bcc_main_flow(&cdi_values.cdi_attest, &cdi_values.cdi_seal, &dice_chain, &input_values)
+ .map_err(|e| {
+ error!("Failed to run the service VM BCC main flow: {e}");
+ e
+ })
+}
diff --git a/service_vm/requests/src/cert.rs b/service_vm/requests/src/cert.rs
index 2baca2a..73828a7 100644
--- a/service_vm/requests/src/cert.rs
+++ b/service_vm/requests/src/cert.rs
@@ -14,9 +14,11 @@
//! Generation of certificates and attestation extensions.
+use crate::dice::SubComponent;
use alloc::vec;
+use alloc::vec::Vec;
use der::{
- asn1::{BitStringRef, ObjectIdentifier, UIntRef},
+ asn1::{BitStringRef, ObjectIdentifier, UIntRef, Utf8StringRef},
oid::AssociatedOid,
Decode, Sequence,
};
@@ -42,15 +44,17 @@
/// ```asn1
/// AttestationDescription ::= SEQUENCE {
/// attestationChallenge OCTET_STRING,
+/// isVmSecure BOOLEAN,
+/// vmComponents SEQUENCE OF VmComponent,
/// }
/// ```
-/// TODO(b/312448064): Add VM root of trust and payload information to the extension.
#[derive(Debug, Clone, Sequence)]
pub(crate) struct AttestationExtension<'a> {
#[asn1(type = "OCTET STRING")]
attestation_challenge: &'a [u8],
/// Indicates whether the VM is operating under a secure configuration.
is_vm_secure: bool,
+ vm_components: Vec<VmComponent<'a>>,
}
impl<'a> AssociatedOid for AttestationExtension<'a> {
@@ -58,8 +62,43 @@
}
impl<'a> AttestationExtension<'a> {
- pub(crate) fn new(attestation_challenge: &'a [u8], is_vm_secure: bool) -> Self {
- Self { attestation_challenge, is_vm_secure }
+ pub(crate) fn new(
+ attestation_challenge: &'a [u8],
+ is_vm_secure: bool,
+ vm_components: Vec<VmComponent<'a>>,
+ ) -> Self {
+ Self { attestation_challenge, is_vm_secure, vm_components }
+ }
+}
+
+/// VM component information
+///
+/// ```asn1
+/// VmComponent ::= SEQUENCE {
+/// name UTF8String,
+/// securityVersion INTEGER,
+/// codeHash OCTET STRING,
+/// authorityHash OCTET STRING,
+/// }
+/// ```
+#[derive(Debug, Clone, Sequence)]
+pub(crate) struct VmComponent<'a> {
+ name: Utf8StringRef<'a>,
+ version: u64,
+ #[asn1(type = "OCTET STRING")]
+ code_hash: &'a [u8],
+ #[asn1(type = "OCTET STRING")]
+ authority_hash: &'a [u8],
+}
+
+impl<'a> VmComponent<'a> {
+ pub(crate) fn new(sub_component: &'a SubComponent) -> der::Result<Self> {
+ Ok(Self {
+ name: Utf8StringRef::new(&sub_component.name)?,
+ version: sub_component.version,
+ code_hash: &sub_component.code_hash,
+ authority_hash: &sub_component.authority_hash,
+ })
}
}
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index 4e87136..cfdac2d 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -76,9 +76,16 @@
rand_bytes(&mut serial_number)?;
let subject = Name::encode_from_string("CN=Android Protected Virtual Machine Key")?;
let rkp_cert = Certificate::from_der(¶ms.remotely_provisioned_cert)?;
+ let vm_components =
+ if let Some(components) = client_vm_dice_chain.microdroid_payload_components() {
+ components.iter().map(cert::VmComponent::new).collect::<der::Result<Vec<_>>>()?
+ } else {
+ Vec::new()
+ };
let attestation_ext = cert::AttestationExtension::new(
&csr_payload.challenge,
client_vm_dice_chain.all_entries_are_secure(),
+ vm_components,
)
.to_vec()?;
let tbs_cert = cert::build_tbs_certificate(
diff --git a/service_vm/requests/src/dice.rs b/service_vm/requests/src/dice.rs
index c220af6..15cfbc9 100644
--- a/service_vm/requests/src/dice.rs
+++ b/service_vm/requests/src/dice.rs
@@ -14,8 +14,9 @@
//! This module contains functions related to DICE.
+use alloc::string::String;
use alloc::vec::Vec;
-use ciborium::value::Value;
+use ciborium::value::{Integer, Value};
use core::cell::OnceCell;
use core::result;
use coset::{
@@ -23,7 +24,9 @@
};
use diced_open_dice::{DiceMode, HASH_SIZE};
use log::error;
-use service_vm_comm::{cbor_value_type, try_as_bytes, RequestProcessingError};
+use service_vm_comm::{
+ cbor_value_type, to_unexpected_item_error, value_to_bytes, RequestProcessingError,
+};
type Result<T> = result::Result<T, RequestProcessingError>;
@@ -33,6 +36,17 @@
const MODE: i64 = -4670551;
const SUBJECT_PUBLIC_KEY: i64 = -4670552;
+const CONFIG_DESC_COMPONENT_NAME: i64 = -70002;
+const CONFIG_DESC_SUB_COMPONENTS: i64 = -71002;
+
+const SUB_COMPONENT_NAME: i64 = 1;
+const SUB_COMPONENT_VERSION: i64 = 2;
+const SUB_COMPONENT_CODE_HASH: i64 = 3;
+const SUB_COMPONENT_AUTHORITY_HASH: i64 = 4;
+
+const MICRODROID_KERNEL_COMPONENT_NAME: &str = "vm_entry";
+const MICRODROID_PAYLOAD_COMPONENT_NAME: &str = "Microdroid payload";
+
/// Represents a partially decoded `DiceCertChain` from the client VM.
/// The whole chain is defined as following:
///
@@ -83,7 +97,43 @@
payloads.len() >= 3,
"The client VM DICE chain must contain at least three DiceChainEntryPayloads"
);
- Ok(Self { payloads })
+ let chain = Self { payloads };
+ chain.validate_microdroid_components_names()?;
+ Ok(chain)
+ }
+
+ fn validate_microdroid_components_names(&self) -> Result<()> {
+ let microdroid_kernel_name = &self.microdroid_kernel().config_descriptor.component_name;
+ if MICRODROID_KERNEL_COMPONENT_NAME != microdroid_kernel_name {
+ error!(
+ "The second to last entry in the client VM DICE chain must describe the \
+ Microdroid kernel. Got {}",
+ microdroid_kernel_name
+ );
+ return Err(RequestProcessingError::InvalidDiceChain);
+ }
+ let microdroid_payload_name = &self.microdroid_payload().config_descriptor.component_name;
+ if MICRODROID_PAYLOAD_COMPONENT_NAME != microdroid_payload_name {
+ error!(
+ "The last entry in the client VM DICE chain must describe the Microdroid \
+ payload. Got {}",
+ microdroid_payload_name
+ );
+ return Err(RequestProcessingError::InvalidDiceChain);
+ }
+ Ok(())
+ }
+
+ fn microdroid_kernel(&self) -> &DiceChainEntryPayload {
+ &self.payloads[self.payloads.len() - 2]
+ }
+
+ fn microdroid_payload(&self) -> &DiceChainEntryPayload {
+ &self.payloads[self.payloads.len() - 1]
+ }
+
+ pub(crate) fn microdroid_payload_components(&self) -> Option<&Vec<SubComponent>> {
+ self.microdroid_payload().config_descriptor.sub_components.as_ref()
}
/// Returns true if all payloads in the DICE chain are in normal mode.
@@ -101,9 +151,9 @@
service_vm_dice_chain: &[u8],
) -> Result<Vec<Value>> {
let client_vm_dice_chain =
- try_as_value_array(Value::from_slice(client_vm_dice_chain)?, "client_vm_dice_chain")?;
+ value_to_array(Value::from_slice(client_vm_dice_chain)?, "client_vm_dice_chain")?;
let service_vm_dice_chain =
- try_as_value_array(Value::from_slice(service_vm_dice_chain)?, "service_vm_dice_chain")?;
+ value_to_array(Value::from_slice(service_vm_dice_chain)?, "service_vm_dice_chain")?;
if service_vm_dice_chain.len() < 3 {
// The service VM's DICE chain must contain the root key and at least two other entries
// that describe:
@@ -161,9 +211,7 @@
code_hash: [u8; HASH_SIZE],
#[allow(dead_code)]
authority_hash: [u8; HASH_SIZE],
- /// TODO(b/313815907): Parse the config descriptor and read Apk/Apexes info in it.
- #[allow(dead_code)]
- config_descriptor: Vec<u8>,
+ config_descriptor: ConfigDescriptor,
}
impl DiceChainEntryPayload {
@@ -180,10 +228,7 @@
error!("No payload found in the DICE chain entry");
RequestProcessingError::InvalidDiceChain
})?;
- let payload = Value::from_slice(&payload)?;
- let Value::Map(entries) = payload else {
- return Err(CoseError::UnexpectedItem(cbor_value_type(&payload), "map").into());
- };
+ let entries = value_to_map(Value::from_slice(&payload)?, "DiceChainEntryPayload")?;
build_payload(entries)
}
}
@@ -191,41 +236,191 @@
fn build_payload(entries: Vec<(Value, Value)>) -> Result<DiceChainEntryPayload> {
let mut builder = PayloadBuilder::default();
for (key, value) in entries.into_iter() {
- let Some(Ok(key)) = key.as_integer().map(i64::try_from) else {
- error!("Invalid key found in the DICE chain entry: {:?}", key);
- return Err(RequestProcessingError::InvalidDiceChain);
- };
+ let key: i64 = value_to_num(key, "DiceChainEntryPayload key")?;
match key {
SUBJECT_PUBLIC_KEY => {
- let subject_public_key = try_as_bytes(value, "subject_public_key")?;
+ let subject_public_key = value_to_bytes(value, "subject_public_key")?;
let subject_public_key = CoseKey::from_slice(&subject_public_key)?.try_into()?;
builder.subject_public_key(subject_public_key)?;
}
MODE => builder.mode(to_mode(value)?)?,
- CODE_HASH => builder.code_hash(try_as_byte_array(value, "code_hash")?)?,
- AUTHORITY_HASH => {
- builder.authority_hash(try_as_byte_array(value, "authority_hash")?)?
+ CODE_HASH => {
+ let code_hash = value_to_byte_array(value, "DiceChainEntryPayload code_hash")?;
+ builder.code_hash(code_hash)?;
}
- CONFIG_DESC => builder.config_descriptor(try_as_bytes(value, "config_descriptor")?)?,
+ AUTHORITY_HASH => {
+ let authority_hash =
+ value_to_byte_array(value, "DiceChainEntryPayload authority_hash")?;
+ builder.authority_hash(authority_hash)?;
+ }
+ CONFIG_DESC => {
+ let config_descriptor = value_to_bytes(value, "config_descriptor")?;
+ let config_descriptor = ConfigDescriptor::from_slice(&config_descriptor)?;
+ builder.config_descriptor(config_descriptor)?;
+ }
_ => {}
}
}
builder.build()
}
-fn try_as_value_array(v: Value, context: &str) -> coset::Result<Vec<Value>> {
- if let Value::Array(data) = v {
- Ok(data)
- } else {
- let v_type = cbor_value_type(&v);
- error!("The provided value type '{v_type}' is not of type 'bytes': {context}");
- Err(CoseError::UnexpectedItem(v_type, "array"))
+/// Represents a partially decoded `ConfigurationDescriptor`.
+///
+/// The whole `ConfigurationDescriptor` is defined in:
+///
+/// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
+/// generateCertificateRequestV2.cddl
+#[derive(Debug, Clone)]
+pub(crate) struct ConfigDescriptor {
+ component_name: String,
+ sub_components: Option<Vec<SubComponent>>,
+}
+
+impl ConfigDescriptor {
+ fn from_slice(data: &[u8]) -> Result<Self> {
+ let value = Value::from_slice(data)?;
+ let entries = value_to_map(value, "ConfigDescriptor")?;
+ let mut builder = ConfigDescriptorBuilder::default();
+ for (key, value) in entries.into_iter() {
+ let key: i64 = value_to_num(key, "ConfigDescriptor key")?;
+ match key {
+ CONFIG_DESC_COMPONENT_NAME => {
+ let name = value_to_text(value, "ConfigDescriptor component_name")?;
+ builder.component_name(name)?;
+ }
+ CONFIG_DESC_SUB_COMPONENTS => {
+ let sub_components = value_to_array(value, "ConfigDescriptor sub_components")?;
+ let sub_components = sub_components
+ .into_iter()
+ .map(SubComponent::try_from)
+ .collect::<Result<Vec<_>>>()?;
+ builder.sub_components(sub_components)?
+ }
+ _ => {}
+ }
+ }
+ builder.build()
}
}
-fn try_as_byte_array<const N: usize>(v: Value, context: &str) -> Result<[u8; N]> {
- let data = try_as_bytes(v, context)?;
- data.try_into().map_err(|e| {
+#[derive(Debug, Clone, Default)]
+struct ConfigDescriptorBuilder {
+ component_name: OnceCell<String>,
+ sub_components: OnceCell<Vec<SubComponent>>,
+}
+
+impl ConfigDescriptorBuilder {
+ fn component_name(&mut self, component_name: String) -> Result<()> {
+ set_once(&self.component_name, component_name, "ConfigDescriptor component_name")
+ }
+
+ fn sub_components(&mut self, sub_components: Vec<SubComponent>) -> Result<()> {
+ set_once(&self.sub_components, sub_components, "ConfigDescriptor sub_components")
+ }
+
+ fn build(mut self) -> Result<ConfigDescriptor> {
+ let component_name =
+ take_value(&mut self.component_name, "ConfigDescriptor component_name")?;
+ let sub_components = self.sub_components.take();
+ Ok(ConfigDescriptor { component_name, sub_components })
+ }
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct SubComponent {
+ pub(crate) name: String,
+ pub(crate) version: u64,
+ pub(crate) code_hash: Vec<u8>,
+ pub(crate) authority_hash: Vec<u8>,
+}
+
+impl TryFrom<Value> for SubComponent {
+ type Error = RequestProcessingError;
+
+ fn try_from(value: Value) -> Result<Self> {
+ let entries = value_to_map(value, "SubComponent")?;
+ let mut builder = SubComponentBuilder::default();
+ for (key, value) in entries.into_iter() {
+ let key: i64 = value_to_num(key, "SubComponent key")?;
+ match key {
+ SUB_COMPONENT_NAME => {
+ builder.name(value_to_text(value, "SubComponent component_name")?)?
+ }
+ SUB_COMPONENT_VERSION => {
+ builder.version(value_to_num(value, "SubComponent version")?)?
+ }
+ SUB_COMPONENT_CODE_HASH => {
+ builder.code_hash(value_to_bytes(value, "SubComponent code_hash")?)?
+ }
+ SUB_COMPONENT_AUTHORITY_HASH => {
+ builder.authority_hash(value_to_bytes(value, "SubComponent authority_hash")?)?
+ }
+ k => {
+ error!("Unknown key in SubComponent: {}", k);
+ return Err(RequestProcessingError::InvalidDiceChain);
+ }
+ }
+ }
+ builder.build()
+ }
+}
+
+#[derive(Debug, Clone, Default)]
+struct SubComponentBuilder {
+ name: OnceCell<String>,
+ version: OnceCell<u64>,
+ code_hash: OnceCell<Vec<u8>>,
+ authority_hash: OnceCell<Vec<u8>>,
+}
+
+impl SubComponentBuilder {
+ fn name(&mut self, name: String) -> Result<()> {
+ set_once(&self.name, name, "SubComponent name")
+ }
+
+ fn version(&mut self, version: u64) -> Result<()> {
+ set_once(&self.version, version, "SubComponent version")
+ }
+
+ fn code_hash(&mut self, code_hash: Vec<u8>) -> Result<()> {
+ set_once(&self.code_hash, code_hash, "SubComponent code_hash")
+ }
+
+ fn authority_hash(&mut self, authority_hash: Vec<u8>) -> Result<()> {
+ set_once(&self.authority_hash, authority_hash, "SubComponent authority_hash")
+ }
+
+ fn build(mut self) -> Result<SubComponent> {
+ let name = take_value(&mut self.name, "SubComponent name")?;
+ let version = take_value(&mut self.version, "SubComponent version")?;
+ let code_hash = take_value(&mut self.code_hash, "SubComponent code_hash")?;
+ let authority_hash = take_value(&mut self.authority_hash, "SubComponent authority_hash")?;
+ Ok(SubComponent { name, version, code_hash, authority_hash })
+ }
+}
+
+fn value_to_array(v: Value, context: &'static str) -> coset::Result<Vec<Value>> {
+ v.into_array().map_err(|e| to_unexpected_item_error(&e, "array", context))
+}
+
+fn value_to_text(v: Value, context: &'static str) -> coset::Result<String> {
+ v.into_text().map_err(|e| to_unexpected_item_error(&e, "tstr", context))
+}
+
+fn value_to_map(v: Value, context: &'static str) -> coset::Result<Vec<(Value, Value)>> {
+ v.into_map().map_err(|e| to_unexpected_item_error(&e, "map", context))
+}
+
+fn value_to_num<T: TryFrom<Integer>>(v: Value, context: &'static str) -> Result<T> {
+ let num = v.into_integer().map_err(|e| to_unexpected_item_error(&e, "int", context))?;
+ num.try_into().map_err(|_| {
+ error!("The provided value '{num:?}' is not a valid number: {context}");
+ RequestProcessingError::InvalidDiceChain
+ })
+}
+
+fn value_to_byte_array<const N: usize>(v: Value, context: &'static str) -> Result<[u8; N]> {
+ value_to_bytes(v, context)?.try_into().map_err(|e| {
error!("The provided value '{context}' is not an array of length {N}: {e:?}");
RequestProcessingError::InternalError
})
@@ -264,7 +459,7 @@
mode: OnceCell<DiceMode>,
code_hash: OnceCell<[u8; HASH_SIZE]>,
authority_hash: OnceCell<[u8; HASH_SIZE]>,
- config_descriptor: OnceCell<Vec<u8>>,
+ config_descriptor: OnceCell<ConfigDescriptor>,
}
fn set_once<T>(field: &OnceCell<T>, value: T, field_name: &str) -> Result<()> {
@@ -298,7 +493,7 @@
set_once(&self.authority_hash, authority_hash, "authority_hash")
}
- fn config_descriptor(&mut self, config_descriptor: Vec<u8>) -> Result<()> {
+ fn config_descriptor(&mut self, config_descriptor: ConfigDescriptor) -> Result<()> {
set_once(&self.config_descriptor, config_descriptor, "config_descriptor")
}