use std::{collections::HashMap, io::Read};
use asn1_rs::FromDer as _;
use serde::{Deserialize, Serialize};
use x509_parser::prelude::X509Certificate;
const OID_CN: &str = "2.5.4.3";
const OID_O: &str = "2.5.4.10";
const OID_OU: &str = "2.5.4.11";
const OID_C: &str = "2.5.4.6";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DistinguishedName {
#[serde(rename = "CN")]
pub common_name: String,
#[serde(rename = "O")]
pub organization: String,
#[serde(rename = "OU", default)]
pub organizational_unit: String,
#[serde(rename = "C")]
pub country: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Assurance {
#[serde(rename = "maxAssuranceLevel")]
pub max_assurance_level: u32,
#[serde(rename = "attestationMethods", default)]
pub attestation_methods: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Product {
#[serde(rename = "productType")]
pub product_type: String,
#[serde(rename = "DN")]
pub dn: DistinguishedName,
#[serde(rename = "minVersion", default)]
pub min_version: String,
pub assurance: Assurance,
}
pub type MediaTypeMap = HashMap<String, Vec<String>>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Containers {
#[serde(default)]
pub generate: MediaTypeMap,
#[serde(default)]
pub validate: MediaTypeMap,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConformanceDates {
pub creation: String,
pub conformance: String,
#[serde(rename = "earliestPublicDisclosure")]
pub earliest_public_disclosure: String,
#[serde(rename = "lastModification")]
pub last_modification: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConformingProduct {
#[serde(rename = "recordId")]
pub record_id: String,
pub applicant: String,
pub status: String,
pub product: Product,
#[serde(rename = "specVersion")]
pub spec_version: Vec<String>,
#[serde(rename = "conformanceProgramVersion")]
pub conformance_program_version: String,
pub containers: Containers,
pub dates: ConformanceDates,
}
#[allow(dead_code)]
pub fn read_conforming_products(reader: impl Read) -> serde_json::Result<Vec<ConformingProduct>> {
serde_json::from_reader(reader)
}
#[allow(dead_code)]
pub fn find_conforming_product_for_cert<'a>(
cert_der: &[u8],
products: &'a [ConformingProduct],
) -> Option<&'a ConformingProduct> {
let (_, cert) = X509Certificate::from_der(cert_der).ok()?;
let mut cn = String::new();
let mut o = String::new();
let mut ou = String::new();
let mut c = String::new();
for rdn in cert.subject().iter() {
for attr in rdn.iter() {
let Ok(val) = attr.as_str() else { continue };
match attr.attr_type().to_string().as_str() {
OID_CN => cn = val.to_string(),
OID_O => o = val.to_string(),
OID_OU => ou = val.to_string(),
OID_C => c = val.to_string(),
_ => {}
}
}
}
products.iter().find(|p| {
let dn = &p.product.dn;
p.status == "conformant"
&& dn.common_name == cn
&& dn.organization == o
&& (dn.organizational_unit.is_empty() || dn.organizational_unit == ou)
&& (dn.country.is_empty() || dn.country == c)
})
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
fn make_test_cert_der(cn: &str, o: &str, ou: &str, c: &str) -> Vec<u8> {
use chrono::Utc;
use ed25519_dalek::{Signer as _, SigningKey};
use rasn::types::{Any, BitString, ObjectIdentifier, PrintableString, SetOf};
use rasn_pkix::{
AlgorithmIdentifier, AttributeTypeAndValue, Certificate, Name,
RelativeDistinguishedName, SubjectPublicKeyInfo, TbsCertificate, Time, Validity,
Version,
};
let key = SigningKey::from_bytes(&[42u8; 32]);
let vk = key.verifying_key();
let ed_oid = ObjectIdentifier::new(vec![1u32, 3, 101, 112]).unwrap();
let alg = AlgorithmIdentifier {
algorithm: ed_oid,
parameters: None,
};
let spki = SubjectPublicKeyInfo {
algorithm: alg.clone(),
subject_public_key: BitString::from_slice(vk.as_bytes()),
};
let make_rdn = |oid_parts: &[u32], val: &str| -> RelativeDistinguishedName {
let ps = PrintableString::try_from(val.to_string()).unwrap();
let v = rasn::der::encode(&ps).unwrap();
let attr = AttributeTypeAndValue {
r#type: ObjectIdentifier::new(oid_parts.to_vec()).unwrap(),
value: Any::new(v),
};
let mut set = SetOf::new();
set.insert(attr);
RelativeDistinguishedName::from(set)
};
let mut rdns = vec![
make_rdn(&[2, 5, 4, 3], cn), make_rdn(&[2, 5, 4, 10], o), ];
if !ou.is_empty() {
rdns.push(make_rdn(&[2, 5, 4, 11], ou)); }
if !c.is_empty() {
rdns.push(make_rdn(&[2, 5, 4, 6], c)); }
let subject = Name::RdnSequence(rdns);
let now = Utc::now();
let validity = Validity {
not_before: Time::Utc(now - chrono::Duration::days(1)),
not_after: Time::Utc(now + chrono::Duration::days(365)),
};
let tbs = TbsCertificate {
version: Version::V3,
serial_number: rasn::types::Integer::from(1i64),
signature: alg.clone(),
issuer: subject.clone(),
validity,
subject,
subject_public_key_info: spki,
issuer_unique_id: None,
subject_unique_id: None,
extensions: None,
};
let tbs_der = rasn::der::encode(&tbs).unwrap();
let sig = key.sign(&tbs_der);
let cert = Certificate {
tbs_certificate: tbs,
signature_algorithm: alg,
signature_value: BitString::from_slice(sig.to_bytes().as_slice()),
};
rasn::der::encode(&cert).unwrap()
}
const SAMPLE: &str = r#"[
{
"recordId": "0198a1b2-c3d4-7e5f-a6b7-c8d9e0f1a2b3",
"applicant": "My Company Inc",
"status": "conformant",
"product": {
"productType": "generatorProduct",
"DN": { "CN": "MyApplication", "O": "My Company Inc", "OU": "", "C": "US" },
"minVersion": "68566598",
"assurance": {
"maxAssuranceLevel": 2,
"attestationMethods": ["Android_KeyAttestation"]
}
},
"specVersion": ["2.2"],
"conformanceProgramVersion": "0.1",
"containers": {
"generate": { "image": ["image/jpeg"] },
"validate": {}
},
"dates": {
"creation": "2025-06-26",
"conformance": "2025-06-27",
"earliestPublicDisclosure": "2025-08-21",
"lastModification": "2025-06-27"
}
},
{
"recordId": "0199c4d5-e6f7-7a8b-9c0d-e1f2a3b4c5d6",
"applicant": "My Company Inc",
"status": "conformant",
"product": {
"productType": "generatorProduct",
"DN": { "CN": "MyService", "O": "My Company Inc", "OU": "MyOrgId", "C": "US" },
"minVersion": "",
"assurance": { "maxAssuranceLevel": 1 }
},
"specVersion": ["2.2"],
"conformanceProgramVersion": "0.1",
"containers": {
"generate": { "image": ["image/jpeg", "image/png"], "video": ["video/mp4"], "audio": ["audio/wav"] },
"validate": { "image": ["image/jpeg", "image/png"], "video": ["video/mp4"], "audio": ["audio/wav"] }
},
"dates": {
"creation": "2025-07-18",
"conformance": "2025-07-22",
"earliestPublicDisclosure": "2025-08-21",
"lastModification": "2025-07-22"
}
}
]"#;
#[test]
fn parses_sample_list() {
let products = read_conforming_products(SAMPLE.as_bytes()).unwrap();
assert_eq!(products.len(), 2);
let first = &products[0];
assert_eq!(first.record_id, "0198a1b2-c3d4-7e5f-a6b7-c8d9e0f1a2b3");
assert_eq!(first.applicant, "My Company Inc");
assert_eq!(first.status, "conformant");
assert_eq!(first.product.product_type, "generatorProduct");
assert_eq!(first.product.dn.common_name, "MyApplication");
assert_eq!(first.product.assurance.max_assurance_level, 2);
assert_eq!(
first.product.assurance.attestation_methods,
["Android_KeyAttestation"]
);
assert_eq!(first.spec_version, ["2.2"]);
assert_eq!(
first.containers.generate.get("image").unwrap(),
&["image/jpeg"]
);
assert!(first.containers.validate.is_empty());
let second = &products[1];
assert_eq!(second.product.assurance.attestation_methods.len(), 0);
assert_eq!(
second.containers.generate.get("video").unwrap(),
&["video/mp4"]
);
}
#[test]
fn find_match_cn_o_only_when_ou_c_wildcarded() {
let cert = make_test_cert_der("MyApplication", "My Company Inc", "", "US");
let products: Vec<ConformingProduct> = serde_json::from_str(SAMPLE).unwrap();
let found = find_conforming_product_for_cert(&cert, &products);
assert!(found.is_some());
assert_eq!(
found.unwrap().record_id,
"0198a1b2-c3d4-7e5f-a6b7-c8d9e0f1a2b3"
);
}
#[test]
fn find_match_with_all_four_dn_fields() {
let cert = make_test_cert_der("MyService", "My Company Inc", "MyOrgId", "US");
let products: Vec<ConformingProduct> = serde_json::from_str(SAMPLE).unwrap();
let found = find_conforming_product_for_cert(&cert, &products);
assert!(found.is_some());
assert_eq!(
found.unwrap().record_id,
"0199c4d5-e6f7-7a8b-9c0d-e1f2a3b4c5d6"
);
}
#[test]
fn no_match_wrong_cn() {
let cert = make_test_cert_der("Unknown Product", "My Company Inc", "", "US");
let products: Vec<ConformingProduct> = serde_json::from_str(SAMPLE).unwrap();
assert!(find_conforming_product_for_cert(&cert, &products).is_none());
}
#[test]
fn no_match_wrong_organization() {
let cert = make_test_cert_der("MyApplication", "Other Corp", "", "US");
let products: Vec<ConformingProduct> = serde_json::from_str(SAMPLE).unwrap();
assert!(find_conforming_product_for_cert(&cert, &products).is_none());
}
#[test]
fn no_match_when_required_ou_missing_from_cert() {
let cert = make_test_cert_der(
"MyService",
"My Company Inc",
"", "US",
);
let products: Vec<ConformingProduct> = serde_json::from_str(SAMPLE).unwrap();
assert!(find_conforming_product_for_cert(&cert, &products).is_none());
}
#[test]
fn no_match_wrong_country() {
let cert = make_test_cert_der("MyApplication", "My Company Inc", "", "GB");
let products: Vec<ConformingProduct> = serde_json::from_str(SAMPLE).unwrap();
assert!(find_conforming_product_for_cert(&cert, &products).is_none());
}
#[test]
fn no_match_when_status_not_conformant() {
let cert = make_test_cert_der("MyApplication", "My Company Inc", "", "US");
let json = SAMPLE.replace(
r#""status": "conformant",
"product": {
"productType": "generatorProduct",
"DN": { "CN": "MyApplication""#,
r#""status": "revoked",
"product": {
"productType": "generatorProduct",
"DN": { "CN": "MyApplication""#,
);
let products: Vec<ConformingProduct> = serde_json::from_str(&json).unwrap();
assert!(find_conforming_product_for_cert(&cert, &products).is_none());
}
#[test]
fn returns_none_for_invalid_der() {
let products: Vec<ConformingProduct> = serde_json::from_str(SAMPLE).unwrap();
assert!(find_conforming_product_for_cert(b"not a cert", &products).is_none());
}
#[test]
fn returns_none_for_empty_product_list() {
let cert = make_test_cert_der("MyApplication", "My Company Inc", "", "US");
assert!(find_conforming_product_for_cert(&cert, &[]).is_none());
}
}