@motebit/crypto-android-keystore
v1.1.17
Published
Apache-2.0 verifier for Android Hardware-Backed Keystore Attestation hardware-attestation credentials — offline X.509 chain verification against pinned Google Hardware Attestation roots (RSA-4096 + ECDSA P-384), plus ASN.1 extraction of the KeyDescription
Maintainers
Readme
@motebit/crypto-android-keystore
Offline Apache-2.0 verifier for Android Hardware-Backed Keystore Attestation hardware-attestation credentials.
npm i @motebit/crypto-android-keystorePlugs into @motebit/crypto's HardwareAttestationVerifiers dispatcher as the androidKeystore verifier — called when a credential declares platform: "android_keystore" (Android devices with KeyMaster 3+ / KeyMint 1+ — every modern Android device since Android 7).
Usage
import { verify } from "@motebit/crypto";
import { androidKeystoreVerifier } from "@motebit/crypto-android-keystore";
const result = await verify(credential, {
hardwareAttestation: {
androidKeystore: androidKeystoreVerifier({
// Bytes of the registered Android package's `attestationApplicationId`,
// captured at registration time. Must byte-equal what the leaf
// attestation extension reports.
expectedAttestationApplicationId,
}),
},
});What it verifies
- Cert chain to a pinned Google Hardware Attestation root. Two roots ship pinned: the legacy RSA-4096 root (for factory-provisioned devices) and the modern ECDSA P-384 root (for RKP-provisioned devices). Verifiers MUST pin both — Google rotated from RSA to ECDSA between Feb–Apr 2026, so a verifier pinning only one drops half its install base.
- The Android Key Attestation extension (OID
1.3.6.1.4.1.11129.2.1.17) on the leaf —attestationVersion ≥ 3(Keymaster 3 / Android 7+),attestationSecurityLevel ≥ TRUSTED_ENVIRONMENT(rejects software-only fallback),hardwareEnforced.rootOfTrust.verifiedBootStatein caller's allowlist (default[VERIFIED]),hardwareEnforced.attestationApplicationIdbyte-equals the registered package binding. - Optional revocation snapshot. Caller-supplied snapshot keyed by lowercase-hex serial number, mirroring Google's published shape at
https://android.googleapis.com/attestation/status. Defaults to empty (no revocation enforcement). The verifier never fetches at runtime —@motebit/verifyships an embedded snapshot at release time. - Identity binding. The leaf's
attestationChallengemust byte-equalSHA-256(canonicalJson({ attested_at, device_id, identity_public_key, motebit_id, platform: "android_keystore", version: "1" }))— the same body the Kotlinexpo-android-keystoremint path composes. A malicious client that substitutes any other body fails here.
Why pinned
A verifier that dynamically fetched Google's attestation roots has no sovereign story. The pinned roots are the self-attesting contract — third parties audit DEFAULT_ANDROID_KEYSTORE_TRUST_ANCHORS and know which trust anchors this library accepts. Source of truth: roots.json in android/keyattestation, Google's canonical Kotlin reference verifier.
Lower-level primitives
Beyond androidKeystoreVerifier, the package exports the parser + constants + canonical literals for advanced consumers:
verifyAndroidKeystoreAttestation(...)— bare-metal entry: takes the parsedKeyDescription+ caller-supplied roots and returns the structured verification result.androidKeystoreVerifieris a thin curry over this.parseKeyDescription(derBytes)— walk the AOSPKeyDescriptionASN.1 extension into a typed structure (attestationVersion,attestationSecurityLevel,hardwareEnforced, etc.).SECURITY_LEVEL_SOFTWARE,SECURITY_LEVEL_TRUSTED_ENVIRONMENT,SECURITY_LEVEL_STRONG_BOX— the canonicalattestationSecurityLevelenum values per AOSP. Use these to constrain the accepted floor.VERIFIED_BOOT_STATE_VERIFIED,VERIFIED_BOOT_STATE_SELF_SIGNED,VERIFIED_BOOT_STATE_UNVERIFIED,VERIFIED_BOOT_STATE_FAILED— the four canonicalverifiedBootStatevalues; populateallowedVerifiedBootStatesfrom this set.ANDROID_KEYSTORE_PLATFORM— the canonical platform-string constant ("android_keystore").ANDROID_KEY_ATTESTATION_OID(1.3.6.1.4.1.11129.2.1.17) — the X.509 extension OID the leaf carries.GOOGLE_ANDROID_KEYSTORE_ROOT_RSA_PEM,GOOGLE_ANDROID_KEYSTORE_ROOT_ECDSA_PEM— the two pinned Google attestation roots (RSA-4096 + ECDSA P-384). Both ship by default; overrideable viaHardwareVerifierBundleConfig.androidKeystoreRootPemsin@motebit/verify.EMPTY_REVOCATION_SNAPSHOT— typed empty snapshot for callers that don't yet wire a revocation list. Replace with a real snapshot at release time per@motebit/verify's embedding pipeline.
Why a hand-rolled DER walker
The KeyDescription ASN.1 structure has ~50 optional context-tagged fields in AuthorizationList, two of which carry policy-relevant material ([704] rootOfTrust and [709] attestationApplicationId). A schema-driven parser would have to declare all 50 fields just to skip past the ones we ignore. Walking the DER directly costs ~150 lines and stays scoped to exactly what verification needs — same trade-off @motebit/crypto-tpm made for TPMS_ATTEST parsing.
Privacy posture
Closer to FIDO Yubico-batch than to TPM EK. The leaf X.509 subject is the fixed string CN=Android Keystore Key — not device-identifying. The optional ID-attestation family (attestationIdSerial, attestationIdImei, etc.) only fires when the caller invokes setDevicePropertiesAttestationIncluded(true); motebit does not. Default setAttestationChallenge() produces batch-shareable chains with the device-identifying material confined to (a) the caller-controlled challenge and (b) verifiedBootKey (boot-image identity, not user identity).
Related
@motebit/crypto— dispatcher (pure permissive-floor; zero deps)@motebit/crypto-appattest— iOS sibling@motebit/crypto-tpm— Windows / Linux TPM sibling@motebit/crypto-webauthn— browser sibling@motebit/verify— canonical CLI bundling all four leaves with motebit defaults
License
Apache-2.0 — see LICENSE and NOTICE.
"Motebit" is a trademark. The Apache License grants rights to this software, not to any Motebit trademarks, logos, or branding. You may not use Motebit branding in a way that suggests endorsement or affiliation without written permission.
