fix(attestation): reject oversized challenges and rewrite cert DER encoding

DuckDetector flagged two issues:
1. Oversized challenge accepted — 256-byte attestation challenge should
   return INVALID_INPUT_LENGTH (-21) like real KeyMint. Added early check
   in handleGenerateKey before any path decision.
2. Issuer/subject chain mismatch — rcgen's HashMap loses DN attribute
   ordering and converts PrintableString to UTF8String, producing
   different DER bytes. Replaced rcgen with manual DER assembly that
   injects raw keybox issuer_dn_der bytes directly.

Verified on device: TX_ID 315 rejects 256-byte challenge, TX_ID 501
generates valid 4-cert chain with correct issuer linkage.
This commit is contained in:
Enginex0
2026-03-09 21:52:06 +01:00
parent c11465d660
commit ba0628c687
6 changed files with 1649 additions and 187 deletions
@@ -12,6 +12,17 @@ data class KeyIdentifier(val uid: Int, val alias: String)
/** A collection of utility functions to support binder interception. */ /** A collection of utility functions to support binder interception. */
object InterceptorUtils { object InterceptorUtils {
private const val EX_SERVICE_SPECIFIC = -8
fun createErrorReply(errorCode: Int): BinderInterceptor.TransactionResult.OverrideReply {
val parcel = Parcel.obtain().apply {
writeInt(EX_SERVICE_SPECIFIC)
writeInt(errorCode)
writeString(null)
}
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
}
/** /**
* Uses reflection to get the integer transaction code for a given method name from a Stub * Uses reflection to get the integer transaction code for a given method name from a Stub
* class. This is necessary for older Android versions where codes are not public constants. * class. This is necessary for older Android versions where codes are not public constants.
@@ -18,6 +18,7 @@ import java.security.spec.PKCS8EncodedKeySpec
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import org.matrix.TEESimulator.attestation.AttestationBuilder import org.matrix.TEESimulator.attestation.AttestationBuilder
import org.matrix.TEESimulator.attestation.AttestationConstants
import org.matrix.TEESimulator.attestation.AttestationPatcher import org.matrix.TEESimulator.attestation.AttestationPatcher
import org.matrix.TEESimulator.attestation.KeyMintAttestation import org.matrix.TEESimulator.attestation.KeyMintAttestation
import org.matrix.TEESimulator.config.ConfigurationManager import org.matrix.TEESimulator.config.ConfigurationManager
@@ -246,6 +247,13 @@ class KeyMintSecurityLevelInterceptor(
) )
val params = data.createTypedArray(KeyParameter.CREATOR)!! val params = data.createTypedArray(KeyParameter.CREATOR)!!
val parsedParams = KeyMintAttestation(params) val parsedParams = KeyMintAttestation(params)
val challenge = parsedParams.attestationChallenge
if (challenge != null && challenge.size > AttestationConstants.CHALLENGE_LENGTH_LIMIT) {
SystemLogger.warning("[TX_ID: $txId] Rejecting oversized attestation challenge: ${challenge.size} bytes (max ${AttestationConstants.CHALLENGE_LENGTH_LIMIT})")
return InterceptorUtils.createErrorReply(KEYMINT_INVALID_INPUT_LENGTH)
}
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
val isAttestKeyRequest = parsedParams.isAttestKey() val isAttestKeyRequest = parsedParams.isAttestKey()
@@ -506,6 +514,7 @@ class KeyMintSecurityLevelInterceptor(
// Maximum alias length to prevent binder buffer exhaustion (Issue #109) // Maximum alias length to prevent binder buffer exhaustion (Issue #109)
// Binder buffer is ~1MB; 256KB provides 4x safety margin for transaction overhead // Binder buffer is ~1MB; 256KB provides 4x safety margin for transaction overhead
private const val MAX_ALIAS_LENGTH = 256 * 1024 private const val MAX_ALIAS_LENGTH = 256 * 1024
private const val KEYMINT_INVALID_INPUT_LENGTH = -21
private const val MAX_CONCURRENT_HW_KEYGEN_PER_UID = 2 private const val MAX_CONCURRENT_HW_KEYGEN_PER_UID = 2
// Sliding window: max hardware keygen permits per UID within the burst window // Sliding window: max hardware keygen permits per UID within the burst window
private const val MAX_HW_KEYGEN_PER_WINDOW = 2 private const val MAX_HW_KEYGEN_PER_WINDOW = 2
+1166
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -13,7 +13,6 @@ ring = "0.17.14"
rsa = { version = "0.9", features = ["sha2"] } rsa = { version = "0.9", features = ["sha2"] }
pkcs8 = { version = "0.10", features = ["alloc"] } pkcs8 = { version = "0.10", features = ["alloc"] }
rand = "0.8" rand = "0.8"
rcgen = { version = "0.13.2", default-features = false, features = ["ring"] }
der = { version = "0.7.10", features = ["alloc", "oid"] } der = { version = "0.7.10", features = ["alloc", "oid"] }
const-oid = "0.9.6" const-oid = "0.9.6"
x509-cert = { version = "0.2.5", features = ["pem"] } x509-cert = { version = "0.2.5", features = ["pem"] }
+455 -171
View File
@@ -1,37 +1,29 @@
use crate::error::{CertGenError, Result}; use crate::error::{CertGenError, Result};
use crate::keybox::ParsedKeybox; use crate::keybox::ParsedKeybox;
use crate::types::{CertGenParams, GeneratedKeyPair}; use crate::types::{Algorithm, CertGenParams, GeneratedKeyPair};
use rcgen::{
BasicConstraints, CertificateParams, CustomExtension, DistinguishedName, DnType, IsCa,
KeyPair, KeyUsagePurpose, SerialNumber,
};
use time::OffsetDateTime; use time::OffsetDateTime;
const ATTESTATION_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 11129, 2, 1, 17]; const ATTESTATION_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 11129, 2, 1, 17];
// Signature algorithm OIDs
const OID_SHA256_WITH_ECDSA: &[u64] = &[1, 2, 840, 10045, 4, 3, 2];
const OID_SHA384_WITH_ECDSA: &[u64] = &[1, 2, 840, 10045, 4, 3, 3];
const OID_SHA256_WITH_RSA: &[u64] = &[1, 2, 840, 113549, 1, 1, 11];
// Extension OIDs
const OID_KEY_USAGE: &[u64] = &[2, 5, 29, 15];
pub fn build_certificate_chain( pub fn build_certificate_chain(
key_pair: &GeneratedKeyPair, key_pair: &GeneratedKeyPair,
attestation_ext_der: &[u8], attestation_ext_der: &[u8],
keybox: &ParsedKeybox, keybox: &ParsedKeybox,
params: &CertGenParams, params: &CertGenParams,
) -> Result<Vec<Vec<u8>>> { ) -> Result<Vec<Vec<u8>>> {
let issuer_key = KeyPair::try_from(keybox.signing_key_der.as_slice()) let leaf_der = build_leaf_cert(key_pair, attestation_ext_der, keybox, params)?;
.map_err(|e| CertGenError::CertBuildFailed(format!("keybox key parse: {e}")))?;
let issuer_cert = build_issuer_cert(&issuer_key, &keybox.issuer_dn_der)?;
let subject_key = KeyPair::try_from(key_pair.private_key_pkcs8.as_slice())
.map_err(|e| CertGenError::CertBuildFailed(format!("subject key parse: {e}")))?;
let leaf_params = build_leaf_params(attestation_ext_der, keybox, params)?;
let leaf_cert = leaf_params
.signed_by(&subject_key, &issuer_cert, &issuer_key)
.map_err(|e| CertGenError::CertBuildFailed(format!("signing: {e}")))?;
let mut chain = Vec::with_capacity(1 + keybox.cert_chain_ders.len()); let mut chain = Vec::with_capacity(1 + keybox.cert_chain_ders.len());
chain.push(leaf_cert.der().to_vec()); chain.push(leaf_der);
for cert_der in &keybox.cert_chain_ders { for cert_der in &keybox.cert_chain_ders {
chain.push(cert_der.clone()); chain.push(cert_der.clone());
} }
@@ -39,201 +31,493 @@ pub fn build_certificate_chain(
Ok(chain) Ok(chain)
} }
fn build_issuer_cert( fn build_leaf_cert(
issuer_key: &KeyPair, key_pair: &GeneratedKeyPair,
issuer_dn_der: &[u8],
) -> Result<rcgen::Certificate> {
let mut issuer_params = CertificateParams::default();
issuer_params.distinguished_name = parse_dn_from_der(issuer_dn_der)?;
issuer_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
// Suppress AKI/SKI generation — we only need this cert as a signing vehicle
issuer_params.key_identifier_method = rcgen::KeyIdMethod::PreSpecified(vec![]);
issuer_params
.self_signed(issuer_key)
.map_err(|e| CertGenError::CertBuildFailed(format!("issuer self-sign: {e}")))
}
fn build_leaf_params(
attestation_ext_der: &[u8], attestation_ext_der: &[u8],
keybox: &ParsedKeybox, keybox: &ParsedKeybox,
params: &CertGenParams, params: &CertGenParams,
) -> Result<CertificateParams> { ) -> Result<Vec<u8>> {
let mut cp = CertificateParams::default(); let spki_der = extract_spki_from_pkcs8(&key_pair.private_key_pkcs8)?;
let sig_alg_der = signature_algorithm_for_signing_key(&keybox.signing_key_der, params.algorithm)?;
// Subject DN
cp.distinguished_name = if let Some(ref subject_der) = params.cert_subject {
parse_dn_from_der(subject_der)?
} else {
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, "Android KeyStore Key");
dn
};
// Serial number // Serial number
cp.serial_number = if let Some(ref serial_bytes) = params.cert_serial { let serial_bytes = if let Some(ref serial) = params.cert_serial {
Some(SerialNumber::from(serial_bytes.clone())) serial.clone()
} else { } else {
Some(SerialNumber::from(vec![1u8])) vec![1u8]
}; };
// Validity period // Subject DN
cp.not_before = timestamp_to_datetime(params.cert_not_before)?; let subject_dn_der = if let Some(ref subject) = params.cert_subject {
cp.not_after = if params.cert_not_after == -1 { subject.clone()
// Fall back to keybox leaf cert's notAfter, or +1 year } else {
encode_simple_cn_dn("Android KeyStore Key")
};
// Validity
let not_before = timestamp_to_datetime(params.cert_not_before)?;
let not_after = if params.cert_not_after == -1 {
OffsetDateTime::from_unix_timestamp(keybox.leaf_not_after) OffsetDateTime::from_unix_timestamp(keybox.leaf_not_after)
.unwrap_or_else(|_| OffsetDateTime::now_utc() + time::Duration::days(365)) .unwrap_or_else(|_| OffsetDateTime::now_utc() + time::Duration::days(365))
} else { } else {
timestamp_to_datetime(params.cert_not_after)? timestamp_to_datetime(params.cert_not_after)?
}; };
// rcgen 0.13.2: IsCa::NoCa (the default) emits neither BasicConstraints nor SKI // Extensions
// extension. This matches real Android attestation leaf certs which include neither. let extensions_der = build_extensions(attestation_ext_der, &params.purposes)?;
// No explicit suppression needed — NoCa is a no-op in the extension writer.
// KeyUsage from purposes // TBS Certificate
cp.key_usages = map_key_usages(&params.purposes); let version_der = encode_der_explicit_tag(0, &encode_der_integer(&[2]));
let serial_der = encode_der_integer(&serial_bytes);
let validity_der = encode_validity(&not_before, &not_after);
let extensions_tagged = encode_der_explicit_tag(3, &extensions_der);
let tbs_der = encode_der_sequence(&[
&version_der,
&serial_der,
&sig_alg_der,
&keybox.issuer_dn_der, // RAW bytes — no re-encoding
&validity_der,
&subject_dn_der,
&spki_der,
&extensions_tagged,
]);
// Sign the TBS
let signature_bytes = sign_tbs(&tbs_der, &keybox.signing_key_der, params.algorithm)?;
let signature_bit_string = encode_der_bit_string(&signature_bytes);
// Final certificate: SEQUENCE { TBS, sigAlgorithm, signature }
let cert_der = encode_der_sequence(&[
&tbs_der,
&sig_alg_der,
&signature_bit_string,
]);
Ok(cert_der)
}
fn sign_tbs(tbs_der: &[u8], signing_key_der: &[u8], algorithm: Algorithm) -> Result<Vec<u8>> {
match algorithm {
Algorithm::Ec => sign_tbs_ec(tbs_der, signing_key_der),
Algorithm::Rsa => sign_tbs_rsa(tbs_der, signing_key_der),
}
}
fn sign_tbs_ec(tbs_der: &[u8], signing_key_der: &[u8]) -> Result<Vec<u8>> {
// Determine EC curve from the signing key's PKCS8 AlgorithmIdentifier
let alg = detect_ec_signing_algorithm(signing_key_der)?;
let key_pair = ring::signature::EcdsaKeyPair::from_pkcs8(alg, signing_key_der, &ring::rand::SystemRandom::new())
.map_err(|e| CertGenError::SigningFailed(format!("EC key parse: {e}")))?;
let rng = ring::rand::SystemRandom::new();
let sig = key_pair.sign(&rng, tbs_der)
.map_err(|e| CertGenError::SigningFailed(format!("EC sign: {e}")))?;
Ok(sig.as_ref().to_vec())
}
fn detect_ec_signing_algorithm(pkcs8_der: &[u8]) -> Result<&'static ring::signature::EcdsaSigningAlgorithm> {
use der::Decode;
let info = pkcs8::PrivateKeyInfo::from_der(pkcs8_der)
.map_err(|e| CertGenError::SigningFailed(format!("PKCS8 parse: {e}")))?;
let params_oid = info.algorithm.parameters_oid()
.map_err(|e| CertGenError::SigningFailed(format!("EC curve OID: {e}")))?;
let p256_oid: const_oid::ObjectIdentifier = "1.2.840.10045.3.1.7".parse()
.map_err(|_| CertGenError::SigningFailed("OID parse".into()))?;
let p384_oid: const_oid::ObjectIdentifier = "1.3.132.0.34".parse()
.map_err(|_| CertGenError::SigningFailed("OID parse".into()))?;
if params_oid == p256_oid {
Ok(&ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING)
} else if params_oid == p384_oid {
Ok(&ring::signature::ECDSA_P384_SHA384_ASN1_SIGNING)
} else {
Err(CertGenError::SigningFailed(format!("unsupported EC curve OID: {params_oid}")))
}
}
fn sign_tbs_rsa(tbs_der: &[u8], signing_key_der: &[u8]) -> Result<Vec<u8>> {
use rsa::pkcs8::DecodePrivateKey;
use rsa::signature::{SignatureEncoding, SignerMut};
use rsa::pkcs1v15::SigningKey;
use rsa::sha2::Sha256;
let private_key = rsa::RsaPrivateKey::from_pkcs8_der(signing_key_der)
.map_err(|e| CertGenError::SigningFailed(format!("RSA key parse: {e}")))?;
let mut signing_key = SigningKey::<Sha256>::new(private_key);
let signature = signing_key.sign(tbs_der);
Ok(signature.to_vec())
}
fn signature_algorithm_for_signing_key(signing_key_der: &[u8], algorithm: Algorithm) -> Result<Vec<u8>> {
match algorithm {
Algorithm::Ec => {
let ring_alg = detect_ec_signing_algorithm(signing_key_der)?;
// Determine OID from the algorithm used
let oid = if std::ptr::eq(ring_alg, &ring::signature::ECDSA_P384_SHA384_ASN1_SIGNING) {
OID_SHA384_WITH_ECDSA
} else {
OID_SHA256_WITH_ECDSA
};
let oid_der = encode_der_oid(oid);
Ok(encode_der_sequence(&[&oid_der]))
}
Algorithm::Rsa => {
let oid_der = encode_der_oid(OID_SHA256_WITH_RSA);
let null_der = vec![0x05, 0x00];
Ok(encode_der_sequence(&[&oid_der, &null_der]))
}
}
}
fn extract_spki_from_pkcs8(pkcs8_der: &[u8]) -> Result<Vec<u8>> {
use der::Decode;
let info = pkcs8::PrivateKeyInfo::from_der(pkcs8_der)
.map_err(|e| CertGenError::CertBuildFailed(format!("PKCS8 parse for SPKI: {e}")))?;
// Reconstruct SPKI from AlgorithmIdentifier + public key
// For EC: derive public key from private key via ring
// For RSA: derive from rsa crate
let alg_id_oid = info.algorithm.oid;
let ec_oid: const_oid::ObjectIdentifier = "1.2.840.10045.2.1".parse()
.map_err(|_| CertGenError::CertBuildFailed("OID parse".into()))?;
if alg_id_oid == ec_oid {
extract_ec_spki(pkcs8_der, &info)
} else {
extract_rsa_spki(pkcs8_der)
}
}
fn extract_ec_spki(pkcs8_der: &[u8], info: &pkcs8::PrivateKeyInfo) -> Result<Vec<u8>> {
use ring::signature::KeyPair as _;
let params_oid = info.algorithm.parameters_oid()
.map_err(|e| CertGenError::CertBuildFailed(format!("EC curve OID: {e}")))?;
let p256_oid: const_oid::ObjectIdentifier = "1.2.840.10045.3.1.7".parse()
.map_err(|_| CertGenError::CertBuildFailed("OID parse".into()))?;
let p384_oid: const_oid::ObjectIdentifier = "1.3.132.0.34".parse()
.map_err(|_| CertGenError::CertBuildFailed("OID parse".into()))?;
let (ring_alg, curve_oid_der): (&ring::signature::EcdsaSigningAlgorithm, Vec<u8>) = if params_oid == p256_oid {
(&ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING, encode_der_oid(&[1, 2, 840, 10045, 3, 1, 7]))
} else if params_oid == p384_oid {
(&ring::signature::ECDSA_P384_SHA384_ASN1_SIGNING, encode_der_oid(&[1, 3, 132, 0, 34]))
} else {
return Err(CertGenError::CertBuildFailed(format!("unsupported EC curve: {params_oid}")));
};
let kp = ring::signature::EcdsaKeyPair::from_pkcs8(
ring_alg,
pkcs8_der,
&ring::rand::SystemRandom::new(),
).map_err(|e| CertGenError::CertBuildFailed(format!("EC key parse: {e}")))?;
let ec_kp = kp.public_key().as_ref().to_vec();
// SPKI = SEQUENCE { AlgorithmIdentifier, BIT STRING (public key) }
// AlgorithmIdentifier = SEQUENCE { ecPublicKey OID, curve OID }
let ec_oid_der = encode_der_oid(&[1, 2, 840, 10045, 2, 1]);
let alg_id = encode_der_sequence(&[&ec_oid_der, &curve_oid_der]);
let pub_key_bits = encode_der_bit_string(&ec_kp);
Ok(encode_der_sequence(&[&alg_id, &pub_key_bits]))
}
fn extract_rsa_spki(pkcs8_der: &[u8]) -> Result<Vec<u8>> {
use rsa::pkcs8::DecodePrivateKey;
let private_key = rsa::RsaPrivateKey::from_pkcs8_der(pkcs8_der)
.map_err(|e| CertGenError::CertBuildFailed(format!("RSA key parse: {e}")))?;
let public_key = rsa::RsaPublicKey::from(&private_key);
// Encode RSA public key as DER: SEQUENCE { n INTEGER, e INTEGER }
use rsa::traits::PublicKeyParts;
let n_bytes = public_key.n().to_bytes_be();
let e_bytes = public_key.e().to_bytes_be();
let rsa_pub_der = encode_der_sequence(&[
&encode_der_integer(&n_bytes),
&encode_der_integer(&e_bytes),
]);
// SPKI = SEQUENCE { AlgorithmIdentifier, BIT STRING (DER-encoded RSAPublicKey) }
let rsa_oid_der = encode_der_oid(&[1, 2, 840, 113549, 1, 1, 1]);
let null_der = vec![0x05, 0x00];
let alg_id = encode_der_sequence(&[&rsa_oid_der, &null_der]);
let pub_key_bits = encode_der_bit_string(&rsa_pub_der);
Ok(encode_der_sequence(&[&alg_id, &pub_key_bits]))
}
fn build_extensions(attestation_ext_der: &[u8], purposes: &[i32]) -> Result<Vec<u8>> {
let mut extensions: Vec<Vec<u8>> = Vec::new();
// KeyUsage extension (critical)
let ku_byte = map_key_usage_byte(purposes);
if ku_byte != 0 {
let ku_ext = build_key_usage_extension(ku_byte);
extensions.push(ku_ext);
}
// Attestation extension (non-critical) // Attestation extension (non-critical)
let mut attest_ext = CustomExtension::from_oid_content(ATTESTATION_OID, attestation_ext_der.to_vec()); let attest_ext = build_extension(&encode_der_oid(ATTESTATION_OID), false, attestation_ext_der);
attest_ext.set_criticality(false); extensions.push(attest_ext);
cp.custom_extensions.push(attest_ext);
Ok(cp) Ok(encode_der_sequence_of(&extensions))
} }
/// Maps KeyPurpose values to X.509 KeyUsage bits per KeyCreationResult.aidl spec. fn build_extension(oid_der: &[u8], critical: bool, value_der: &[u8]) -> Vec<u8> {
/// Only SIGN, DECRYPT, WRAP_KEY, AGREE_KEY, and ATTEST_KEY produce KeyUsage bits. let value_octet_string = encode_der_octet_string(value_der);
/// ENCRYPT and VERIFY are intentionally excluded (matches Kotlin CertificateGenerator). if critical {
fn map_key_usages(purposes: &[i32]) -> Vec<KeyUsagePurpose> { let critical_der = encode_der_boolean(true);
let mut usages = Vec::new(); encode_der_sequence(&[oid_der, &critical_der, &value_octet_string])
} else {
encode_der_sequence(&[oid_der, &value_octet_string])
}
}
fn build_key_usage_extension(ku_byte: u8) -> Vec<u8> {
// DER BIT STRING: minimal encoding requires trimming trailing zero bits
let unused_bits = ku_byte.trailing_zeros().min(7) as u8;
// BIT STRING = tag (0x03) + length(2) + unused_bits + byte
let bit_string = vec![0x03, 0x02, unused_bits, ku_byte];
let oid_der = encode_der_oid(OID_KEY_USAGE);
let value_octet_string = encode_der_octet_string(&bit_string);
let critical_der = encode_der_boolean(true);
encode_der_sequence(&[&oid_der, &critical_der, &value_octet_string])
}
// KeyUsage BIT STRING byte layout (RFC 5280):
// byte[0] bit 7 = digitalSignature (0x80)
// byte[0] bit 6 = nonRepudiation (0x40)
// byte[0] bit 5 = keyEncipherment (0x20)
// byte[0] bit 4 = dataEncipherment (0x10)
// byte[0] bit 3 = keyAgreement (0x08)
// byte[0] bit 2 = keyCertSign (0x04)
// byte[0] bit 1 = cRLSign (0x02)
// byte[0] bit 0 = encipherOnly (0x01)
// byte[1] bit 7 = decipherOnly (0x80)
fn map_key_usage_byte(purposes: &[i32]) -> u8 {
let mut bits: u8 = 0;
for &purpose in purposes { for &purpose in purposes {
match purpose { match purpose {
2 => { 2 => bits |= 0x80, // SIGN -> digitalSignature
// SIGN -> digitalSignature 1 => bits |= 0x10, // DECRYPT -> dataEncipherment
if !usages.contains(&KeyUsagePurpose::DigitalSignature) { 5 => bits |= 0x20, // WRAP_KEY -> keyEncipherment
usages.push(KeyUsagePurpose::DigitalSignature); 6 => bits |= 0x08, // AGREE_KEY -> keyAgreement
} 7 => bits |= 0x04, // ATTEST_KEY -> keyCertSign
}
1 => {
// DECRYPT -> dataEncipherment
if !usages.contains(&KeyUsagePurpose::DataEncipherment) {
usages.push(KeyUsagePurpose::DataEncipherment);
}
}
5 => {
// WRAP_KEY -> keyEncipherment
if !usages.contains(&KeyUsagePurpose::KeyEncipherment) {
usages.push(KeyUsagePurpose::KeyEncipherment);
}
}
6 => {
// AGREE_KEY -> keyAgreement
if !usages.contains(&KeyUsagePurpose::KeyAgreement) {
usages.push(KeyUsagePurpose::KeyAgreement);
}
}
7 => {
// ATTEST_KEY -> keyCertSign
if !usages.contains(&KeyUsagePurpose::KeyCertSign) {
usages.push(KeyUsagePurpose::KeyCertSign);
}
}
_ => {} _ => {}
} }
} }
usages bits
}
fn encode_validity(not_before: &OffsetDateTime, not_after: &OffsetDateTime) -> Vec<u8> {
let nb = encode_time(not_before);
let na = encode_time(not_after);
encode_der_sequence(&[&nb, &na])
}
fn encode_time(dt: &OffsetDateTime) -> Vec<u8> {
let year = dt.year();
if (1950..2050).contains(&year) {
encode_utctime(dt)
} else {
encode_gentime(dt)
}
}
fn encode_utctime(dt: &OffsetDateTime) -> Vec<u8> {
// UTCTime: YYMMDDHHMMSSZ
let year = dt.year() % 100;
let s = format!(
"{:02}{:02}{:02}{:02}{:02}{:02}Z",
year, dt.month() as u8, dt.day(), dt.hour(), dt.minute(), dt.second()
);
let mut out = Vec::with_capacity(2 + s.len());
out.push(0x17); // UTCTime tag
out.extend_from_slice(&encode_der_length_bytes(s.len()));
out.extend_from_slice(s.as_bytes());
out
}
fn encode_gentime(dt: &OffsetDateTime) -> Vec<u8> {
// GeneralizedTime: YYYYMMDDHHMMSSZ
let s = format!(
"{:04}{:02}{:02}{:02}{:02}{:02}Z",
dt.year(), dt.month() as u8, dt.day(), dt.hour(), dt.minute(), dt.second()
);
let mut out = Vec::with_capacity(2 + s.len());
out.push(0x18); // GeneralizedTime tag
out.extend_from_slice(&encode_der_length_bytes(s.len()));
out.extend_from_slice(s.as_bytes());
out
}
fn encode_simple_cn_dn(cn: &str) -> Vec<u8> {
// Name = SEQUENCE OF RelativeDistinguishedName
// RDN = SET OF AttributeTypeAndValue
// ATV = SEQUENCE { OID, UTF8String }
let cn_oid = encode_der_oid(&[2, 5, 4, 3]);
let cn_value = encode_der_utf8string(cn);
let atv = encode_der_sequence(&[&cn_oid, &cn_value]);
let rdn = encode_der_set(&[&atv]);
encode_der_sequence(&[&rdn])
} }
fn timestamp_to_datetime(ts: i64) -> Result<OffsetDateTime> { fn timestamp_to_datetime(ts: i64) -> Result<OffsetDateTime> {
if ts == -1 { if ts == -1 {
return Ok(OffsetDateTime::now_utc()); return Ok(OffsetDateTime::now_utc());
} }
// Params use milliseconds for validity timestamps
OffsetDateTime::from_unix_timestamp(ts / 1000) OffsetDateTime::from_unix_timestamp(ts / 1000)
.map_err(|e| CertGenError::CertBuildFailed(format!("invalid timestamp {ts}: {e}"))) .map_err(|e| CertGenError::CertBuildFailed(format!("invalid timestamp {ts}: {e}")))
} }
// Parse a DER-encoded X.500 Name into rcgen DistinguishedName. // ---------------------------------------------------------------------------
// We only extract the CN (most common for Android keystore certs). // DER encoding primitives
// If parsing fails, fall back to empty DN. // ---------------------------------------------------------------------------
fn parse_dn_from_der(der: &[u8]) -> Result<DistinguishedName> {
use x509_cert::name::Name;
use der::{Decode, Encode};
let name = Name::from_der(der) fn encode_der_length_bytes(len: usize) -> Vec<u8> {
.map_err(|e| CertGenError::CertBuildFailed(format!("DN parse: {e}")))?; if len < 0x80 {
vec![len as u8]
let mut dn = DistinguishedName::new(); } else if len <= 0xFF {
vec![0x81, len as u8]
for rdn in name.0.iter() { } else if len <= 0xFFFF {
for atv in rdn.0.iter() { vec![0x82, (len >> 8) as u8, len as u8]
let oid_str = atv.oid.to_string(); } else if len <= 0xFF_FFFF {
// Map common OIDs to rcgen DnType vec![0x83, (len >> 16) as u8, (len >> 8) as u8, len as u8]
let dn_type = match oid_str.as_str() {
"2.5.4.3" => DnType::CommonName,
"2.5.4.6" => DnType::CountryName,
"2.5.4.7" => DnType::LocalityName,
"2.5.4.8" => DnType::StateOrProvinceName,
"2.5.4.10" => DnType::OrganizationName,
"2.5.4.11" => DnType::OrganizationalUnitName,
other => DnType::CustomDnType(
other.split('.').filter_map(|s| s.parse().ok()).collect(),
),
};
// Extract the string value from the AttributeValue (ANY type)
// The value is DER-encoded; try to read it as UTF8String or PrintableString
let value_bytes = atv.value.to_der()
.map_err(|e| CertGenError::CertBuildFailed(format!("DN value encode: {e}")))?;
let value_str = extract_string_from_der_any(&value_bytes);
dn.push(dn_type, value_str);
}
}
Ok(dn)
}
// Extract a string from a DER-encoded ASN.1 string type (UTF8String, PrintableString, etc.)
fn extract_string_from_der_any(der: &[u8]) -> String {
if der.len() < 2 {
return String::new();
}
// Tag byte at [0], length at [1..], then content
let tag = der[0];
let (content_len, header_len) = if der[1] < 0x80 {
(der[1] as usize, 2)
} else { } else {
let num = (der[1] & 0x7f) as usize; vec![0x84, (len >> 24) as u8, (len >> 16) as u8, (len >> 8) as u8, len as u8]
if num == 0 || 2 + num > der.len() {
return String::new();
} }
let mut len = 0usize;
for i in 0..num {
len = (len << 8) | der[2 + i] as usize;
} }
(len, 2 + num)
};
let end = header_len + content_len; fn encode_der_tag_length_value(tag: u8, content: &[u8]) -> Vec<u8> {
if end > der.len() { let mut out = Vec::with_capacity(1 + 4 + content.len());
return String::new(); out.push(tag);
out.extend_from_slice(&encode_der_length_bytes(content.len()));
out.extend_from_slice(content);
out
} }
let content = &der[header_len..end];
match tag { fn encode_der_sequence(items: &[&[u8]]) -> Vec<u8> {
0x0C | 0x13 | 0x16 | 0x1A => { let total: usize = items.iter().map(|i| i.len()).sum();
// UTF8String (0x0C), PrintableString (0x13), IA5String (0x16), VisibleString (0x1A) let mut content = Vec::with_capacity(total);
String::from_utf8_lossy(content).into_owned() for item in items {
content.extend_from_slice(item);
} }
0x1E => { encode_der_tag_length_value(0x30, &content)
// BMPString (UTF-16BE)
let chars: Vec<u16> = content
.chunks_exact(2)
.map(|c| u16::from_be_bytes([c[0], c[1]]))
.collect();
String::from_utf16_lossy(&chars)
} }
_ => String::from_utf8_lossy(content).into_owned(),
fn encode_der_sequence_of(items: &[Vec<u8>]) -> Vec<u8> {
let total: usize = items.iter().map(|i| i.len()).sum();
let mut content = Vec::with_capacity(total);
for item in items {
content.extend_from_slice(item);
}
encode_der_tag_length_value(0x30, &content)
}
fn encode_der_set(items: &[&[u8]]) -> Vec<u8> {
let total: usize = items.iter().map(|i| i.len()).sum();
let mut content = Vec::with_capacity(total);
for item in items {
content.extend_from_slice(item);
}
encode_der_tag_length_value(0x31, &content)
}
fn encode_der_explicit_tag(tag_num: u8, content: &[u8]) -> Vec<u8> {
encode_der_tag_length_value(0xA0 | tag_num, content)
}
fn encode_der_integer(value: &[u8]) -> Vec<u8> {
// DER INTEGER must have minimal encoding and leading 0x00 if high bit set
if value.is_empty() {
return encode_der_tag_length_value(0x02, &[0x00]);
}
// Strip leading zeros (but keep at least one byte)
let mut start = 0;
while start < value.len() - 1 && value[start] == 0 {
start += 1;
}
let trimmed = &value[start..];
// Add leading 0x00 if high bit is set (positive integer)
if trimmed[0] & 0x80 != 0 {
let mut padded = Vec::with_capacity(1 + trimmed.len());
padded.push(0x00);
padded.extend_from_slice(trimmed);
encode_der_tag_length_value(0x02, &padded)
} else {
encode_der_tag_length_value(0x02, trimmed)
} }
} }
fn encode_der_bit_string(bits: &[u8]) -> Vec<u8> {
// BIT STRING: tag 0x03, length, unused_bits (0), content
let mut content = Vec::with_capacity(1 + bits.len());
content.push(0x00); // 0 unused bits
content.extend_from_slice(bits);
encode_der_tag_length_value(0x03, &content)
}
fn encode_der_octet_string(content: &[u8]) -> Vec<u8> {
encode_der_tag_length_value(0x04, content)
}
fn encode_der_utf8string(s: &str) -> Vec<u8> {
encode_der_tag_length_value(0x0C, s.as_bytes())
}
fn encode_der_boolean(val: bool) -> Vec<u8> {
encode_der_tag_length_value(0x01, &[if val { 0xFF } else { 0x00 }])
}
fn encode_der_oid(components: &[u64]) -> Vec<u8> {
if components.len() < 2 {
return encode_der_tag_length_value(0x06, &[]);
}
let mut content = Vec::new();
// First two components encoded as 40 * c[0] + c[1]
content.push((components[0] * 40 + components[1]) as u8);
for &c in &components[2..] {
encode_oid_subidentifier(&mut content, c);
}
encode_der_tag_length_value(0x06, &content)
}
fn encode_oid_subidentifier(buf: &mut Vec<u8>, mut value: u64) {
if value == 0 {
buf.push(0);
return;
}
// Encode in base-128 with continuation bits
let mut bytes = Vec::new();
while value > 0 {
bytes.push((value & 0x7F) as u8);
value >>= 7;
}
bytes.reverse();
// Set high bit on all but the last byte
for i in 0..bytes.len() - 1 {
bytes[i] |= 0x80;
}
buf.extend_from_slice(&bytes);
}
-7
View File
@@ -11,7 +11,6 @@ pub enum CertGenError {
KeyboxParseFailed(String), KeyboxParseFailed(String),
AttestationBuildFailed(String), AttestationBuildFailed(String),
DerError(der::Error), DerError(der::Error),
RcgenError(rcgen::Error),
EmptyKeyboxChain, EmptyKeyboxChain,
ChallengeTooLong(usize), ChallengeTooLong(usize),
InvalidParameter(String), InvalidParameter(String),
@@ -31,7 +30,6 @@ impl fmt::Display for CertGenError {
Self::KeyboxParseFailed(msg) => write!(f, "keybox parse failed: {}", msg), Self::KeyboxParseFailed(msg) => write!(f, "keybox parse failed: {}", msg),
Self::AttestationBuildFailed(msg) => write!(f, "attestation build failed: {}", msg), Self::AttestationBuildFailed(msg) => write!(f, "attestation build failed: {}", msg),
Self::DerError(e) => write!(f, "DER error: {}", e), Self::DerError(e) => write!(f, "DER error: {}", e),
Self::RcgenError(e) => write!(f, "rcgen error: {}", e),
Self::EmptyKeyboxChain => write!(f, "keybox certificate chain is empty"), Self::EmptyKeyboxChain => write!(f, "keybox certificate chain is empty"),
Self::ChallengeTooLong(len) => write!(f, "attestation challenge too long: {} bytes (max 128)", len), Self::ChallengeTooLong(len) => write!(f, "attestation challenge too long: {} bytes (max 128)", len),
Self::InvalidParameter(msg) => write!(f, "invalid parameter: {}", msg), Self::InvalidParameter(msg) => write!(f, "invalid parameter: {}", msg),
@@ -73,10 +71,5 @@ impl From<rsa::Error> for CertGenError {
} }
} }
impl From<rcgen::Error> for CertGenError {
fn from(e: rcgen::Error) -> Self {
Self::RcgenError(e)
}
}
pub type Result<T> = std::result::Result<T, CertGenError>; pub type Result<T> = std::result::Result<T, CertGenError>;