1package com.peachpasswords.passkeys23import org.junit.Assert.assertEquals4import org.junit.Assert.assertFalse5import org.junit.Assert.assertTrue6import org.junit.Test789101112class PasskeyCallerOriginTest {1314 @Test fun apk_key_hash_origin_matches_webauthn_l3_format() {15 16 val origin = PasskeyCallerOrigin.apkKeyHashOrigin("cert".toByteArray())17 assertTrue(origin.startsWith("android:apk-key-hash:"))18 val digest = origin.removePrefix("android:apk-key-hash:")19 assertFalse(digest.contains('='))20 assertEquals(43, digest.length) 21 }2223 @Test fun rp_id_matches_origin_subdomain_semantics() {24 assertTrue(PasskeyCallerOrigin.rpIdMatchesOrigin("example.com", "https://example.com"))25 assertTrue(PasskeyCallerOrigin.rpIdMatchesOrigin("example.com", "https://login.example.com"))26 27 assertFalse(PasskeyCallerOrigin.rpIdMatchesOrigin("login.example.com", "https://example.com"))28 29 assertFalse(PasskeyCallerOrigin.rpIdMatchesOrigin("ample.com", "https://example.com"))30 assertFalse(PasskeyCallerOrigin.rpIdMatchesOrigin("example.com", "https://evilexample.com"))31 assertFalse(PasskeyCallerOrigin.rpIdMatchesOrigin("example.com", "https://evil-example.com"))32 }3334 @Test fun rp_id_public_suffixes_are_rejected() {35 36 assertFalse(PasskeyCallerOrigin.rpIdMatchesOrigin("co.uk", "https://bank.co.uk"))37 }3839 @Test fun non_web_origins_never_match_rp_ids() {40 assertFalse(PasskeyCallerOrigin.rpIdMatchesOrigin("example.com", "android:apk-key-hash:AAAA"))41 assertFalse(PasskeyCallerOrigin.rpIdMatchesOrigin("example.com", "not-an-origin"))42 }4344 @Test fun web_callers_pass_rp_id_domain_gate_regardless_of_binding() {45 46 47 assertTrue(PasskeyCallerOrigin.canServeCredential(null, "example.com", "https://example.com"))48 assertTrue(PasskeyCallerOrigin.canServeCredential("https://example.com", "example.com", "https://login.example.com"))49 50 assertFalse(PasskeyCallerOrigin.canServeCredential(null, "example.com", "https://evil.com"))51 }5253 @Test fun native_callers_only_get_their_own_bound_credentials() {54 val appA = "android:apk-key-hash:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"55 val appB = "android:apk-key-hash:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"56 assertTrue(PasskeyCallerOrigin.canServeCredential(appA, "example.com", appA))57 assertFalse(PasskeyCallerOrigin.canServeCredential(appA, "example.com", appB))58 59 assertFalse(PasskeyCallerOrigin.canServeCredential(null, "example.com", appA))60 }6162 @Test fun browser_allowlist_requires_both_package_and_signing_certificate() {63 val fingerprint = PasskeyCallerOrigin.certificateFingerprint("browser-cert".toByteArray())64 val allowlist = """65 {"apps":[{"info":{"package_name":"com.example.browser","signatures":[{"cert_fingerprint_sha256":"$fingerprint"}]}}]}66 """.trimIndent()6768 assertTrue(69 PasskeyCallerOrigin.isAllowlistedBrowser(70 "com.example.browser", setOf(fingerprint.lowercase()), allowlist,71 ),72 )73 assertFalse(74 PasskeyCallerOrigin.isAllowlistedBrowser(75 "com.example.malicious", setOf(fingerprint), allowlist,76 ),77 )78 assertFalse(79 PasskeyCallerOrigin.isAllowlistedBrowser(80 "com.example.browser", setOf("00:11:22"), allowlist,81 ),82 )83 }84}