Skip to content
Open
511 changes: 283 additions & 228 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion attestation-gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ thiserror.workspace = true
tokio = { version = "1.50.0", features = ["full"] }
tower-http = { version = "0.6.8", features = ["compression-br", "trace", "timeout"] }
tracing = { version = "0.1.44", features = ["log"] }
tracing-subscriber = { version = "0.3.22", default-features = false, features = ["fmt", "json"] }
tracing-subscriber = { version = "0.3.22", default-features = false, features = ["fmt", "json", "env-filter"] }
uuid = { version = "1.21.0", default-features = false, features = ["v4"] }
x509-parser = "0.18.1"

Expand Down
347 changes: 326 additions & 21 deletions attestation-gateway/src/android/android_attestation_service.rs

Large diffs are not rendered by default.

70 changes: 63 additions & 7 deletions attestation-gateway/src/android/android_ca_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ pub enum AndroidCaRegistryError {
#[derive(Debug, Clone)]
pub struct AndroidCaRegistry {
public_keys: Vec<Vec<u8>>,
/// Subset of `public_keys` belonging to roots that issue keys exclusively
/// through Remote Key Provisioning. Chains rooted here cannot have been
/// produced from a leaked legacy keybox.
rkp_public_keys: Vec<Vec<u8>>,
/// DER-encoded trusted root certificates. Used to seed the OpenSSL X509
/// trust store during chain verification so the trust anchor cannot be
/// influenced by what the client sends.
Expand All @@ -28,20 +32,61 @@ pub struct AndroidCaRegistry {

impl AndroidCaRegistry {
pub fn from_default_pem() -> Result<Self, AndroidCaRegistryError> {
Self::from_pem(&[
include_bytes!("attestation_root_ca1.pem").to_vec(),
include_bytes!("attestation_root_ca2.pem").to_vec(),
])
// ca1: Legacy RSA root (factory-provisioned keyboxes, vulnerable to leaks).
let legacy_pems = vec![include_bytes!("attestation_root_ca1.pem").to_vec()];

// ca2: Google "Key Attestation CA1" P-384 root (RKP-provisioned).
// Devices that went through Remote Key Provisioning chain to this root.
// Keybox bypass is impossible for chains rooted here because there is
// no factory-provisioned batch key -- keys are provisioned per-device.
let mut rkp_pems = vec![include_bytes!("attestation_root_ca2.pem").to_vec()];

if let Ok(extra) = std::fs::read("attestation_root_rkp_extra.pem") {
rkp_pems.push(extra);
}

Self::from_pem_with_rkp(&legacy_pems, &rkp_pems)
}

pub fn from_pem(pem_certs: &[Vec<u8>]) -> Result<Self, AndroidCaRegistryError> {
let ca_certs = pem_certs
fn from_pem_with_rkp(
legacy_pems: &[Vec<u8>],
rkp_pems: &[Vec<u8>],
) -> Result<Self, AndroidCaRegistryError> {
let all_pems: Vec<Vec<u8>> = legacy_pems.iter().chain(rkp_pems.iter()).cloned().collect();
let all_certs = all_pems
.iter()
.map(|pem| X509::from_pem(pem))
.collect::<Result<Vec<X509>, openssl::error::ErrorStack>>()
.map_err(|_| AndroidCaRegistryError::PemParsing)?;

Self::from_x509(&ca_certs)
let all_der = all_certs
.iter()
.map(|cert| cert.to_der())
.collect::<Result<Vec<Vec<u8>>, openssl::error::ErrorStack>>()
.map_err(|_| AndroidCaRegistryError::DerEncoding)?;

let all_public_keys = all_der
.iter()
.map(|der| {
let (_, cert) = X509Certificate::from_der(der)?;
Ok(Vec::from(cert.public_key().subject_public_key.data.clone()))
})
.collect::<Result<Vec<Vec<u8>>, X509Error>>()
.map_err(|_| AndroidCaRegistryError::DerParsing)?;

let rkp_count = rkp_pems.len();
let legacy_count = legacy_pems.len();
let rkp_public_keys = all_public_keys[legacy_count..legacy_count + rkp_count].to_vec();

Ok(Self {
public_keys: all_public_keys,
rkp_public_keys,
trusted_root_certs_der: all_der,
})
}

pub fn from_pem(pem_certs: &[Vec<u8>]) -> Result<Self, AndroidCaRegistryError> {
Self::from_pem_with_rkp(pem_certs, &[])
}

pub fn from_x509(x509_certs: &[X509]) -> Result<Self, AndroidCaRegistryError> {
Expand All @@ -66,6 +111,7 @@ impl AndroidCaRegistry {

Ok(Self {
public_keys: ca_public_keys,
rkp_public_keys: vec![],
trusted_root_certs_der: der_certs.to_vec(),
})
}
Expand All @@ -77,6 +123,16 @@ impl AndroidCaRegistry {
.any(|key| key.as_slice() == public_key)
}

/// Returns `true` when the root public key belongs to a Remote Key
/// Provisioning root, meaning the device went through RKP and the key
/// cannot have been produced from a leaked legacy keybox.
#[must_use]
pub fn is_rkp_root(&self, public_key: &[u8]) -> bool {
self.rkp_public_keys
.iter()
.any(|key| key.as_slice() == public_key)
}

/// DER-encoded trusted root certificates. Pass these into the OpenSSL
/// `X509Store` so chain verification anchors only on roots we explicitly
/// trust, regardless of what the client supplies as the last cert.
Expand Down
96 changes: 96 additions & 0 deletions attestation-gateway/src/android/device_certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,102 @@ impl DeviceCertificate {
pub fn package_names(&self) -> &[String] {
&self.key_description.package_names
}

pub fn purpose(&self) -> &[u64] {
&self.key_description.purpose
}

pub fn verified_boot_key(&self) -> Option<&[u8]> {
self.key_description.verified_boot_key.as_deref()
}

pub fn verified_boot_hash(&self) -> Option<&[u8]> {
self.key_description.verified_boot_hash.as_deref()
}

pub const fn creation_date_time(&self) -> Option<u64> {
self.key_description.creation_date_time
}

pub fn unique_id(&self) -> Option<&[u8]> {
self.key_description.unique_id.as_deref()
}

pub fn attestation_id_brand(&self) -> Option<&[u8]> {
self.key_description.attestation_id_brand.as_deref()
}

pub fn attestation_id_device(&self) -> Option<&[u8]> {
self.key_description.attestation_id_device.as_deref()
}

pub fn attestation_id_product(&self) -> Option<&[u8]> {
self.key_description.attestation_id_product.as_deref()
}

pub fn attestation_id_serial(&self) -> Option<&[u8]> {
self.key_description.attestation_id_serial.as_deref()
}

pub fn attestation_id_imei(&self) -> Option<&[u8]> {
self.key_description.attestation_id_imei.as_deref()
}

pub fn attestation_id_meid(&self) -> Option<&[u8]> {
self.key_description.attestation_id_meid.as_deref()
}

pub fn attestation_id_manufacturer(&self) -> Option<&[u8]> {
self.key_description.attestation_id_manufacturer.as_deref()
}

pub fn attestation_id_model(&self) -> Option<&[u8]> {
self.key_description.attestation_id_model.as_deref()
}

pub fn attestation_id_second_imei(&self) -> Option<&[u8]> {
self.key_description.attestation_id_second_imei.as_deref()
}

pub const fn device_unique_attestation(&self) -> bool {
self.key_description.device_unique_attestation
}

pub fn module_hash(&self) -> Option<&[u8]> {
self.key_description.module_hash.as_deref()
}

pub const fn attestation_version(&self) -> u64 {
self.key_description.attestation_version
}

pub const fn vendor_patch_level(&self) -> Option<u64> {
self.key_description.vendor_patch_level
}

pub const fn boot_patch_level(&self) -> Option<u64> {
self.key_description.boot_patch_level
}

pub const fn os_version(&self) -> Option<u64> {
self.key_description.os_version
}

pub const fn usage_count_limit(&self) -> Option<u64> {
self.key_description.usage_count_limit
}

pub const fn algorithm(&self) -> Option<u64> {
self.key_description.algorithm
}

pub const fn key_size(&self) -> Option<u64> {
self.key_description.key_size
}

pub const fn ec_curve(&self) -> Option<u64> {
self.key_description.ec_curve
}
}

impl DeviceCertificateError {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,25 @@ pub struct KeyDescription1<'a> {
pub _keymaster_version: u64,
pub keymaster_security_level: asn1::Enumerated,
pub attestation_challenge: &'a [u8],
pub _unique_id: &'a [u8],
pub unique_id: &'a [u8],
pub software_enforced: AuthorizationList<'a>,
pub hardware_enforced: AuthorizationList<'a>,
}

#[derive(asn1::Asn1Read)]
pub struct AuthorizationList<'a> {
#[explicit(1)]
pub _purpose: Option<asn1::SetOf<'a, u64>>,
pub purpose: Option<asn1::SetOf<'a, u64>>,
#[explicit(2)]
pub _algorithm: Option<u64>,
pub algorithm: Option<u64>,
#[explicit(3)]
pub _key_size: Option<u64>,
pub key_size: Option<u64>,
#[explicit(5)]
pub _digest: Option<UnorderedSetOfU64>,
#[explicit(6)]
pub _padding: Option<asn1::SetOf<'a, u64>>,
#[explicit(10)]
pub _ec_curve: Option<u64>,
pub ec_curve: Option<u64>,
#[explicit(200)]
pub _rsa_public_exponent: Option<u64>,
#[explicit(400)]
Expand All @@ -48,23 +48,23 @@ pub struct AuthorizationList<'a> {
#[explicit(600)]
pub _all_applications: Option<asn1::Null>,
#[explicit(701)]
pub _creation_date_time: Option<u64>,
pub creation_date_time: Option<u64>,
#[explicit(702)]
pub origin: Option<u64>,
#[explicit(703)]
pub _rollback_resistant: Option<asn1::Null>,
#[explicit(704)]
pub root_of_trust: Option<RootOfTrust<'a>>,
#[explicit(705)]
pub _os_version: Option<u64>,
pub os_version: Option<u64>,
#[explicit(706)]
pub os_patch_level: Option<u32>,
}

/// Root of trust for attestation schema versions 1 and 2 (no `verified_boot_hash`).
#[derive(asn1::Asn1Read, Debug)]
pub struct RootOfTrust<'a> {
pub _verified_boot_key: &'a [u8],
pub verified_boot_key: &'a [u8],
pub device_locked: bool,
pub verified_boot_state: asn1::Enumerated,
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub struct KeyDescription100<'a> {
pub _key_mint_version: u64,
pub key_mint_security_level: asn1::Enumerated,
pub attestation_challenge: &'a [u8],
pub _unique_id: &'a [u8],
pub unique_id: &'a [u8],
pub software_enforced: AuthorizationList<'a>,
pub hardware_enforced: AuthorizationList<'a>,
}
Expand All @@ -29,11 +29,11 @@ impl<'a> KeyDescription100<'a> {
#[derive(asn1::Asn1Read)]
pub struct AuthorizationList<'a> {
#[explicit(1)]
pub _purpose: Option<asn1::SetOf<'a, u64>>,
pub purpose: Option<asn1::SetOf<'a, u64>>,
#[explicit(2)]
pub _algorithm: Option<u64>,
pub algorithm: Option<u64>,
#[explicit(3)]
pub _key_size: Option<u64>,
pub key_size: Option<u64>,
#[explicit(5)]
pub _digest: Option<UnorderedSetOfU64>,
#[explicit(6)]
Expand All @@ -43,7 +43,7 @@ pub struct AuthorizationList<'a> {
#[explicit(8)]
pub _min_mac_length: Option<u64>,
#[explicit(10)]
pub _ec_curve: Option<u64>,
pub ec_curve: Option<u64>,
#[explicit(200)]
pub _rsa_public_exponent: Option<u64>,
#[explicit(203)]
Expand All @@ -59,7 +59,7 @@ pub struct AuthorizationList<'a> {
#[explicit(402)]
pub _usage_expire_date_time: Option<u64>,
#[explicit(405)]
pub _usage_count_limit: Option<u64>,
pub usage_count_limit: Option<u64>,
#[explicit(502)]
pub _user_secure_id: Option<u64>,
#[explicit(503)]
Expand All @@ -77,39 +77,39 @@ pub struct AuthorizationList<'a> {
#[explicit(509)]
pub _unlocked_device_req: Option<asn1::Null>,
#[explicit(701)]
pub _creation_date_time: Option<u64>,
pub creation_date_time: Option<u64>,
#[explicit(702)]
pub origin: Option<u64>,
#[explicit(704)]
pub root_of_trust: Option<RootOfTrust<'a>>,
#[explicit(705)]
pub _os_version: Option<u64>,
pub os_version: Option<u64>,
#[explicit(706)]
pub os_patch_level: Option<u32>,
#[explicit(709)]
pub attestation_application_id: Option<&'a [u8]>,
#[explicit(710)]
pub _attestation_id_brand: Option<&'a [u8]>,
pub attestation_id_brand: Option<&'a [u8]>,
#[explicit(711)]
pub _attestation_id_device: Option<&'a [u8]>,
pub attestation_id_device: Option<&'a [u8]>,
#[explicit(712)]
pub _attestation_id_product: Option<&'a [u8]>,
pub attestation_id_product: Option<&'a [u8]>,
#[explicit(713)]
pub _attestation_id_serial: Option<&'a [u8]>,
pub attestation_id_serial: Option<&'a [u8]>,
#[explicit(714)]
pub _attestation_id_imei: Option<&'a [u8]>,
pub attestation_id_imei: Option<&'a [u8]>,
#[explicit(715)]
pub _attestation_id_meid: Option<&'a [u8]>,
pub attestation_id_meid: Option<&'a [u8]>,
#[explicit(716)]
pub _attestation_id_manufacturer: Option<&'a [u8]>,
pub attestation_id_manufacturer: Option<&'a [u8]>,
#[explicit(717)]
pub _attestation_id_model: Option<&'a [u8]>,
pub attestation_id_model: Option<&'a [u8]>,
#[explicit(718)]
pub _vendor_patch_level: Option<u64>,
pub vendor_patch_level: Option<u64>,
#[explicit(719)]
pub _boot_patch_level: Option<u64>,
pub boot_patch_level: Option<u64>,
#[explicit(720)]
pub _device_unique_attestation: Option<asn1::Null>,
pub device_unique_attestation: Option<asn1::Null>,
}

/// Contents of authorization tag `709` (`attestation_application_id`). See
Expand All @@ -129,8 +129,8 @@ pub struct AttestationPackageInfo<'a> {

#[derive(asn1::Asn1Read, Debug)]
pub struct RootOfTrust<'a> {
pub _verified_boot_key: &'a [u8],
pub verified_boot_key: &'a [u8],
pub device_locked: bool,
pub verified_boot_state: asn1::Enumerated,
pub _verified_boot_hash: &'a [u8],
pub verified_boot_hash: &'a [u8],
}
Loading