npm.io
1.1.22 • Published 4d ago

@motebit/crypto-android-keystore

Licence
Apache-2.0
Version
1.1.22
Deps
3
Size
111 kB
Vulns
0
Weekly
0
Stars
4

@motebit/crypto-android-keystore

Offline Apache-2.0 verifier for Android Hardware-Backed Keystore Attestation hardware-attestation credentials.

npm i @motebit/crypto-android-keystore

Plugs 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

  1. 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.
  2. 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.verifiedBootState in caller's allowlist (default [VERIFIED]), hardwareEnforced.attestationApplicationId byte-equals the registered package binding.
  3. 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/verify ships an embedded snapshot at release time.
  4. Identity binding. The leaf's attestationChallenge must byte-equal SHA-256(canonicalJson({ attested_at, device_id, identity_public_key, motebit_id, platform: "android_keystore", version: "1" })) — the same body the Kotlin expo-android-keystore mint 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 parsed KeyDescription + caller-supplied roots and returns the structured verification result. androidKeystoreVerifier is a thin curry over this.
  • parseKeyDescription(derBytes) — walk the AOSP KeyDescription ASN.1 extension into a typed structure (attestationVersion, attestationSecurityLevel, hardwareEnforced, etc.).
  • SECURITY_LEVEL_SOFTWARE, SECURITY_LEVEL_TRUSTED_ENVIRONMENT, SECURITY_LEVEL_STRONG_BOX — the canonical attestationSecurityLevel enum 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 canonical verifiedBootState values; populate allowedVerifiedBootStates from 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 via HardwareVerifierBundleConfig.androidKeystoreRootPems in @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).

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.