Last active
December 24, 2025 03:09
-
-
Save Lua12138/81312aeb1d2360fa7ef4b756ec7d9d18 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| object KeyStoreChallenge { | |
| private val TAG = "KeyAttestation" | |
| /** | |
| * How to verify see to | |
| * https://developer.android.com/privacy-and-security/security-key-attestation?hl=zh-cn#key_attestation_ext_schema | |
| */ | |
| fun <T> use(alias: String, challenge: ByteArray, block: (Boolean, List<PEM>) -> T): T { | |
| if (!this.challenge(alias, true, challenge)) { | |
| if (!this.challenge(alias, false, challenge)) { | |
| return block(false, listOf()) | |
| } | |
| } | |
| val (okay, certs) = this.load(alias) | |
| return block(okay, certs) | |
| } | |
| fun load(alias: String): Pair<Boolean, List<PEM>> { | |
| val keyStore = KeyStore.getInstance("AndroidKeyStore") | |
| keyStore.load(null) | |
| val certChain = keyStore.getCertificateChain(alias) | |
| if (certChain == null || certChain.isEmpty()) { | |
| Log.e(TAG, "cannot found certificate chain within $alias") | |
| return Pair(false, listOf()) | |
| } | |
| val certs = mutableListOf<ByteArray>() | |
| certChain.forEach { cert -> | |
| certs.add(cert.encoded) | |
| } | |
| return Pair(certs.isNotEmpty(), certs) | |
| } | |
| fun challenge( | |
| alias: String, | |
| devicePropertiesAttestationIncluded: Boolean, | |
| challenge: ByteArray | |
| ): Boolean { | |
| if (challenge.isNotEmpty() && challenge.size < 16) { | |
| throw RuntimeException("challenge must be gt 16 bytes or be empty(disable challenge)") | |
| } | |
| val now = Date() | |
| // https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#parameterspec-names | |
| val spec = ECGenParameterSpec("secp256r1") | |
| val builder = KeyGenParameterSpec.Builder( | |
| alias, | |
| KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY | |
| ).setAlgorithmParameterSpec(spec) | |
| .setDigests("SHA-256", "SHA-384", "SHA-512") | |
| .setKeyValidityStart(now) | |
| if (Build.VERSION.SDK_INT > 17 && challenge.isNotEmpty()) { | |
| builder.setAttestationChallenge(challenge) | |
| } | |
| if (Build.VERSION.SDK_INT >= 31) { | |
| // need READ_PHONE_STATE permission | |
| builder.setDevicePropertiesAttestationIncluded(devicePropertiesAttestationIncluded) | |
| } | |
| val generator = | |
| KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore") | |
| generator.initialize(builder.build()) | |
| val keyPair = try { | |
| // https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setDevicePropertiesAttestationIncluded(boolean) | |
| // KeyGenerator.generateKey() will throw ProviderException if device properties attestation fails or is not supported. | |
| generator.generateKeyPair() | |
| } catch (_: ProviderException) { | |
| return false | |
| } | |
| Log.d(TAG, "generate public key success: ${keyPair.public.encoded.toPemString()}") | |
| // It should be null if the operator from tee | |
| Log.d(TAG, "generate private key success: ${keyPair.private?.encoded.toPemString()}") | |
| return true | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 依赖项目: https://github.com/android/keyattestation | |
| fun tee_cert_chains(@RequestBody body: String): String { | |
| var description: KeyDescription? = null | |
| val certs = mutableListOf<X509Certificate>() | |
| val x509 = CertificateFactory.getInstance("X.509") | |
| body.byteInputStream().use { input -> | |
| for (certificate in x509.generateCertificates(input)) { | |
| if (certificate is X509Certificate) { | |
| if (certificate.hasAttestationExtension()) { | |
| description = certificate.keyDescription() | |
| } | |
| certs.add(certificate) | |
| } | |
| } | |
| } | |
| val verifier = Verifier( | |
| { GoogleTrustAnchors() }, | |
| { setOf<String>() }, | |
| { Instant.now() } | |
| ) | |
| val result = verifier.verify(certs) | |
| val j = JSONObject() | |
| val applicationId = description?.hardwareEnforced?.attestationApplicationId | |
| ?: description?.softwareEnforced?.attestationApplicationId | |
| val pkg = JSONArray() | |
| val sig = JSONArray() | |
| applicationId?.packages?.forEachIndexed { index, info -> | |
| JSONObject().put("应用包名", info.name) | |
| .put("应用版本", info.version) | |
| .let(pkg::put) | |
| } | |
| applicationId?.signatures?.map { sig.put(it.toByteArray().toHexString()) } | |
| j.put("签名应用", pkg) | |
| j.put("签名信息", sig) | |
| j.put("系统版本", description?.hardwareEnforced?.osVersion) | |
| j.put("系统补丁版本", description?.hardwareEnforced?.osPatchLevel) | |
| j.put("实际签署时间", description?.softwareEnforced?.creationDateTime) | |
| j.put("申请签署时间", description?.softwareEnforced?.activeDateTime) | |
| j.put("证书验证成功", false) | |
| when (result) { | |
| is VerificationResult.Success -> { | |
| j.put("证书验证成功", true) | |
| j.put("公钥", result.publicKey) | |
| j.put("安全等级", result.securityLevel) | |
| j.put("解锁状态", result.verifiedBootState) | |
| with(result.attestedDeviceIds) { | |
| val imei = JSONArray() | |
| imeis.forEach { imei.put(it) } | |
| JSONObject().put("brand", brand) | |
| .put("device", device) | |
| .put("product", product) | |
| .put("serial number", serialNumber) | |
| .put("imei", imei) | |
| .put("meid", meid) | |
| .put("manufacturer", manufacturer) | |
| .put("model", model) | |
| }.let { j.put("设备标识", it) } | |
| j.put("设备信息", result.deviceInformation) | |
| j.put("挑战字符(hex)", result.challenge.toByteArray().toHexString()) | |
| } | |
| is VerificationResult.PathValidationFailure -> { | |
| j.put("错误信息", "证书链无效,不能验证来自Google CA签发") | |
| } | |
| else -> { | |
| j.put("错误信息", result.toString()) | |
| } | |
| } | |
| return j.toString() | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment