@dynamic-labs-sdk/client 1.8.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{InvalidParamError-Btv8VGSa.native.esm.js → InvalidParamError-BqFQnYCi.native.esm.js} +23 -10
- package/dist/{InvalidParamError-Btv8VGSa.native.esm.js.map → InvalidParamError-BqFQnYCi.native.esm.js.map} +1 -1
- package/dist/{InvalidParamError-DGBih-LJ.cjs → InvalidParamError-CmwKWCyr.cjs} +24 -11
- package/dist/{InvalidParamError-DGBih-LJ.cjs.map → InvalidParamError-CmwKWCyr.cjs.map} +1 -1
- package/dist/{InvalidParamError-BKnC9rcJ.esm.js → InvalidParamError-c1J0Vmze.esm.js} +23 -10
- package/dist/{InvalidParamError-BKnC9rcJ.esm.js.map → InvalidParamError-c1J0Vmze.esm.js.map} +1 -1
- package/dist/{NotWaasWalletAccountError-CRmyVoB3.native.esm.js → NotWaasWalletAccountError-Be0QB89x.native.esm.js} +3 -3
- package/dist/{NotWaasWalletAccountError-CRmyVoB3.native.esm.js.map → NotWaasWalletAccountError-Be0QB89x.native.esm.js.map} +1 -1
- package/dist/{NotWaasWalletAccountError-CHr72wTH.esm.js → NotWaasWalletAccountError-CgwG_c2-.esm.js} +3 -3
- package/dist/{NotWaasWalletAccountError-CHr72wTH.esm.js.map → NotWaasWalletAccountError-CgwG_c2-.esm.js.map} +1 -1
- package/dist/{NotWaasWalletAccountError-i1FozGep.cjs → NotWaasWalletAccountError-DKEpLrGc.cjs} +3 -3
- package/dist/{NotWaasWalletAccountError-i1FozGep.cjs.map → NotWaasWalletAccountError-DKEpLrGc.cjs.map} +1 -1
- package/dist/client/core/createCore/getInitialState.d.ts +1 -1
- package/dist/client/core/createCore/getInitialState.d.ts.map +1 -1
- package/dist/client/createDynamicClient/createDynamicClient.d.ts.map +1 -1
- package/dist/client/types/DynamicClient.d.ts +9 -0
- package/dist/client/types/DynamicClient.d.ts.map +1 -1
- package/dist/core.cjs +5 -5
- package/dist/core.esm.js +5 -5
- package/dist/core.native.esm.js +5 -5
- package/dist/{getNetworkProviderFromNetworkId-CRuh5pgc.esm.js → getNetworkProviderFromNetworkId-CGpE_Y95.esm.js} +3 -3
- package/dist/{getNetworkProviderFromNetworkId-CRuh5pgc.esm.js.map → getNetworkProviderFromNetworkId-CGpE_Y95.esm.js.map} +1 -1
- package/dist/{getNetworkProviderFromNetworkId-Bm41knUq.native.esm.js → getNetworkProviderFromNetworkId-Cke-DBhx.native.esm.js} +4 -4
- package/dist/{getNetworkProviderFromNetworkId-Bm41knUq.native.esm.js.map → getNetworkProviderFromNetworkId-Cke-DBhx.native.esm.js.map} +1 -1
- package/dist/{getNetworkProviderFromNetworkId-BdgI7g5U.cjs → getNetworkProviderFromNetworkId-DXzopnuR.cjs} +4 -4
- package/dist/{getNetworkProviderFromNetworkId-BdgI7g5U.cjs.map → getNetworkProviderFromNetworkId-DXzopnuR.cjs.map} +1 -1
- package/dist/{getSignedSessionId-D2w_IRdt.esm.js → getSignedSessionId-C1uLkDjy.esm.js} +3 -3
- package/dist/{getSignedSessionId-D2w_IRdt.esm.js.map → getSignedSessionId-C1uLkDjy.esm.js.map} +1 -1
- package/dist/{getSignedSessionId-S6CBWkOn.native.esm.js → getSignedSessionId-C7YR-jNH.native.esm.js} +3 -3
- package/dist/{getSignedSessionId-S6CBWkOn.native.esm.js.map → getSignedSessionId-C7YR-jNH.native.esm.js.map} +1 -1
- package/dist/{getSignedSessionId-Cwmt4BkY.cjs → getSignedSessionId-CbWFH_TM.cjs} +3 -3
- package/dist/{getSignedSessionId-Cwmt4BkY.cjs.map → getSignedSessionId-CbWFH_TM.cjs.map} +1 -1
- package/dist/{getVerifiedCredentialForWalletAccount-D33r7Drd.native.esm.js → getVerifiedCredentialForWalletAccount-DAmdvMY5.native.esm.js} +2 -2
- package/dist/{getVerifiedCredentialForWalletAccount-D33r7Drd.native.esm.js.map → getVerifiedCredentialForWalletAccount-DAmdvMY5.native.esm.js.map} +1 -1
- package/dist/{getVerifiedCredentialForWalletAccount-QwfkKHfr.esm.js → getVerifiedCredentialForWalletAccount-DPTHL1pO.esm.js} +2 -2
- package/dist/{getVerifiedCredentialForWalletAccount-QwfkKHfr.esm.js.map → getVerifiedCredentialForWalletAccount-DPTHL1pO.esm.js.map} +1 -1
- package/dist/{getVerifiedCredentialForWalletAccount-CDuP1kaI.cjs → getVerifiedCredentialForWalletAccount-DidFNLGP.cjs} +3 -3
- package/dist/{getVerifiedCredentialForWalletAccount-CDuP1kaI.cjs.map → getVerifiedCredentialForWalletAccount-DidFNLGP.cjs.map} +1 -1
- package/dist/index.cjs +19 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +19 -7
- package/dist/index.esm.js.map +1 -1
- package/dist/index.native.esm.js +19 -7
- package/dist/index.native.esm.js.map +1 -1
- package/dist/{isMfaRequiredForAction-z74isCQ7.cjs → isMfaRequiredForAction-BNipdNF5.cjs} +2 -2
- package/dist/{isMfaRequiredForAction-z74isCQ7.cjs.map → isMfaRequiredForAction-BNipdNF5.cjs.map} +1 -1
- package/dist/{isMfaRequiredForAction-5p3i98-Z.esm.js → isMfaRequiredForAction-DCwLx4Ik.esm.js} +2 -2
- package/dist/{isMfaRequiredForAction-5p3i98-Z.esm.js.map → isMfaRequiredForAction-DCwLx4Ik.esm.js.map} +1 -1
- package/dist/{isMfaRequiredForAction-cmElIVof.native.esm.js → isMfaRequiredForAction-Dx4BPJVg.native.esm.js} +2 -2
- package/dist/{isMfaRequiredForAction-cmElIVof.native.esm.js.map → isMfaRequiredForAction-Dx4BPJVg.native.esm.js.map} +1 -1
- package/dist/modules/initializeClient/initializeClient.d.ts.map +1 -1
- package/dist/modules/initializeClient/state.d.ts +9 -0
- package/dist/modules/initializeClient/state.d.ts.map +1 -1
- package/dist/modules/state/raiseStateEvents/events.d.ts +4 -0
- package/dist/modules/state/raiseStateEvents/events.d.ts.map +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/utils/getNonce/fetchAndStoreNonces/fetchAndStoreNonces.d.ts +4 -0
- package/dist/utils/getNonce/fetchAndStoreNonces/fetchAndStoreNonces.d.ts.map +1 -1
- package/dist/utils/getNonce/state.d.ts +5 -0
- package/dist/utils/getNonce/state.d.ts.map +1 -1
- package/dist/waas.cjs +4 -4
- package/dist/waas.esm.js +3 -3
- package/dist/waas.native.esm.js +3 -3
- package/dist/waasCore.cjs +3 -3
- package/dist/waasCore.esm.js +3 -3
- package/dist/waasCore.native.esm.js +3 -3
- package/package.json +11 -4
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +0 -7
- package/android/gradlew +0 -251
- package/android/gradlew.bat +0 -94
- package/android/settings.gradle +0 -30
- package/android/src/test/java/xyz/dynamic/client/keychain/KeyStoreKeyManagerTest.kt +0 -288
- package/android/src/test/java/xyz/dynamic/client/webview/DynamicWebViewClientTest.kt +0 -196
- package/android/src/test/java/xyz/dynamic/client/webview/DynamicWebViewHostOpenTest.kt +0 -347
- package/android/src/test/java/xyz/dynamic/client/webview/DynamicWebViewHostTest.kt +0 -171
- package/android/src/test/java/xyz/dynamic/client/webview/DynamicWebViewModuleTest.kt +0 -191
- package/react-native.config.cjs +0 -14
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
package xyz.dynamic.client.keychain
|
|
2
|
-
|
|
3
|
-
import android.content.Context
|
|
4
|
-
import android.security.keystore.KeyGenParameterSpec
|
|
5
|
-
import android.security.keystore.KeyProperties
|
|
6
|
-
import android.security.keystore.StrongBoxUnavailableException
|
|
7
|
-
import io.mockk.every
|
|
8
|
-
import io.mockk.mockk
|
|
9
|
-
import io.mockk.mockkStatic
|
|
10
|
-
import io.mockk.slot
|
|
11
|
-
import io.mockk.unmockkAll
|
|
12
|
-
import io.mockk.verify
|
|
13
|
-
import java.security.KeyPair
|
|
14
|
-
import java.security.KeyPairGenerator
|
|
15
|
-
import java.security.KeyStore
|
|
16
|
-
import java.security.Signature
|
|
17
|
-
import java.security.spec.ECGenParameterSpec
|
|
18
|
-
import java.util.Base64
|
|
19
|
-
import org.junit.After
|
|
20
|
-
import org.junit.Assert.assertArrayEquals
|
|
21
|
-
import org.junit.Assert.assertEquals
|
|
22
|
-
import org.junit.Assert.assertFalse
|
|
23
|
-
import org.junit.Assert.assertNull
|
|
24
|
-
import org.junit.Assert.assertThrows
|
|
25
|
-
import org.junit.Assert.assertTrue
|
|
26
|
-
import org.junit.Before
|
|
27
|
-
import org.junit.Test
|
|
28
|
-
import org.junit.runner.RunWith
|
|
29
|
-
import org.robolectric.RobolectricTestRunner
|
|
30
|
-
import org.robolectric.annotation.Config
|
|
31
|
-
|
|
32
|
-
// SECURITY CAVEAT (mirrors SecureEnclaveKeyManagerTests.swift): these tests use
|
|
33
|
-
// MockK to stand in for the AndroidKeyStore JCA provider, which is hardware
|
|
34
|
-
// (TEE/StrongBox) and does not exist under Robolectric. They verify that
|
|
35
|
-
// KeyStoreKeyManager calls the right APIs with the right arguments and handles
|
|
36
|
-
// the returned values correctly — they do NOT verify the KeyStore hardware
|
|
37
|
-
// itself. A manual on-device smoke test of generateKeyPair and sign remains
|
|
38
|
-
// required before release. The base64url and SEC1-extraction logic, by
|
|
39
|
-
// contrast, is exercised for real (android.util.Base64 runs under Robolectric).
|
|
40
|
-
@RunWith(RobolectricTestRunner::class)
|
|
41
|
-
@Config(sdk = [35])
|
|
42
|
-
class KeyStoreKeyManagerTest {
|
|
43
|
-
|
|
44
|
-
private val androidKeyStore = "AndroidKeyStore"
|
|
45
|
-
|
|
46
|
-
// A genuine P-256 keypair generated on the JVM, used as the fixture the
|
|
47
|
-
// mocked AndroidKeyStore "returns". Generated BEFORE any mockkStatic call
|
|
48
|
-
// so the real SunEC provider is still in place.
|
|
49
|
-
private lateinit var realKeyPair: KeyPair
|
|
50
|
-
private lateinit var manager: KeyStoreKeyManager
|
|
51
|
-
|
|
52
|
-
@Before
|
|
53
|
-
fun setUp() {
|
|
54
|
-
val kpg = KeyPairGenerator.getInstance("EC")
|
|
55
|
-
kpg.initialize(ECGenParameterSpec("secp256r1"))
|
|
56
|
-
realKeyPair = kpg.generateKeyPair()
|
|
57
|
-
manager = KeyStoreKeyManager()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
@After
|
|
61
|
-
fun tearDown() {
|
|
62
|
-
unmockkAll()
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Independent oracle for the manager's base64url encoding (last 65 bytes of
|
|
66
|
-
// the X.509 SubjectPublicKeyInfo = the uncompressed SEC1 point).
|
|
67
|
-
private fun expectedEncodedPublicKey(): String {
|
|
68
|
-
val x509 = realKeyPair.public.encoded
|
|
69
|
-
val sec1 = x509.copyOfRange(x509.size - 65, x509.size)
|
|
70
|
-
return Base64.getUrlEncoder().withoutPadding().encodeToString(sec1)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// region base64url
|
|
74
|
-
|
|
75
|
-
@Test
|
|
76
|
-
fun base64urlDecode_decodesUrlAlphabetWithoutPadding() {
|
|
77
|
-
// "-_-_AA" is base64url for FB FF BF 00 — exercises the - and _
|
|
78
|
-
// substitutions and the stripped padding (same vector as the iOS test).
|
|
79
|
-
val decoded = KeyStoreKeyManager.base64urlDecode("-_-_AA")
|
|
80
|
-
assertArrayEquals(byteArrayOf(0xFB.toByte(), 0xFF.toByte(), 0xBF.toByte(), 0x00), decoded)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
@Test
|
|
84
|
-
fun base64urlDecode_roundTripsArbitraryBytes() {
|
|
85
|
-
val original = byteArrayOf(0x04, 0xAB.toByte(), 0xCD.toByte(), 0x00, 0x7F)
|
|
86
|
-
val encoded = Base64.getUrlEncoder().withoutPadding().encodeToString(original)
|
|
87
|
-
assertArrayEquals(original, KeyStoreKeyManager.base64urlDecode(encoded))
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// endregion
|
|
91
|
-
|
|
92
|
-
// region isAvailable
|
|
93
|
-
|
|
94
|
-
@Test
|
|
95
|
-
fun isAvailable_trueWhenKeyStoreLoads() {
|
|
96
|
-
mockkStatic(KeyStore::class)
|
|
97
|
-
every { KeyStore.getInstance(androidKeyStore) } returns mockk(relaxed = true)
|
|
98
|
-
assertTrue(manager.isAvailable(null))
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
@Test
|
|
102
|
-
fun isAvailable_falseWhenKeyStoreThrows() {
|
|
103
|
-
mockkStatic(KeyStore::class)
|
|
104
|
-
every { KeyStore.getInstance(androidKeyStore) } throws RuntimeException("no provider")
|
|
105
|
-
assertFalse(manager.isAvailable(null))
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
@Test
|
|
109
|
-
fun isAvailable_trueWithStrongBoxFeature() {
|
|
110
|
-
mockkStatic(KeyStore::class)
|
|
111
|
-
every { KeyStore.getInstance(androidKeyStore) } returns mockk(relaxed = true)
|
|
112
|
-
val context = mockk<Context>(relaxed = true)
|
|
113
|
-
every { context.packageManager.hasSystemFeature(any()) } returns true
|
|
114
|
-
assertTrue(manager.isAvailable(context))
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// endregion
|
|
118
|
-
|
|
119
|
-
// region hasKey / deleteKey
|
|
120
|
-
|
|
121
|
-
@Test
|
|
122
|
-
fun hasKey_reflectsContainsAlias() {
|
|
123
|
-
val keyStore = mockk<KeyStore>(relaxed = true)
|
|
124
|
-
mockkStatic(KeyStore::class)
|
|
125
|
-
every { KeyStore.getInstance(androidKeyStore) } returns keyStore
|
|
126
|
-
every { keyStore.containsAlias("present") } returns true
|
|
127
|
-
every { keyStore.containsAlias("absent") } returns false
|
|
128
|
-
|
|
129
|
-
assertTrue(manager.hasKey("present"))
|
|
130
|
-
assertFalse(manager.hasKey("absent"))
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
@Test
|
|
134
|
-
fun deleteKey_deletesEntryForAlias() {
|
|
135
|
-
val keyStore = mockk<KeyStore>(relaxed = true)
|
|
136
|
-
mockkStatic(KeyStore::class)
|
|
137
|
-
every { KeyStore.getInstance(androidKeyStore) } returns keyStore
|
|
138
|
-
|
|
139
|
-
manager.deleteKey("doomed")
|
|
140
|
-
|
|
141
|
-
verify { keyStore.deleteEntry("doomed") }
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// endregion
|
|
145
|
-
|
|
146
|
-
// region getPublicKey
|
|
147
|
-
|
|
148
|
-
@Test
|
|
149
|
-
fun getPublicKey_nullWhenEntryMissing() {
|
|
150
|
-
val keyStore = mockk<KeyStore>(relaxed = true)
|
|
151
|
-
mockkStatic(KeyStore::class)
|
|
152
|
-
every { KeyStore.getInstance(androidKeyStore) } returns keyStore
|
|
153
|
-
every { keyStore.getEntry("missing", null) } returns null
|
|
154
|
-
|
|
155
|
-
assertNull(manager.getPublicKey("missing"))
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
@Test
|
|
159
|
-
fun getPublicKey_returnsBase64urlSec1PointForPrivateKeyEntry() {
|
|
160
|
-
val keyStore = mockk<KeyStore>(relaxed = true)
|
|
161
|
-
val entry = mockk<KeyStore.PrivateKeyEntry>(relaxed = true)
|
|
162
|
-
every { entry.certificate.publicKey.encoded } returns realKeyPair.public.encoded
|
|
163
|
-
mockkStatic(KeyStore::class)
|
|
164
|
-
every { KeyStore.getInstance(androidKeyStore) } returns keyStore
|
|
165
|
-
every { keyStore.getEntry("tag", null) } returns entry
|
|
166
|
-
|
|
167
|
-
assertEquals(expectedEncodedPublicKey(), manager.getPublicKey("tag"))
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// endregion
|
|
171
|
-
|
|
172
|
-
// region sign
|
|
173
|
-
|
|
174
|
-
@Test
|
|
175
|
-
fun sign_throwsWhenKeyMissing() {
|
|
176
|
-
val keyStore = mockk<KeyStore>(relaxed = true)
|
|
177
|
-
mockkStatic(KeyStore::class)
|
|
178
|
-
every { KeyStore.getInstance(androidKeyStore) } returns keyStore
|
|
179
|
-
every { keyStore.getEntry("missing", null) } returns null
|
|
180
|
-
|
|
181
|
-
val ex = assertThrows(IllegalArgumentException::class.java) {
|
|
182
|
-
manager.sign("missing", byteArrayOf(1, 2, 3))
|
|
183
|
-
}
|
|
184
|
-
assertEquals("Key not found: missing", ex.message)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
@Test
|
|
188
|
-
fun sign_usesEcdsaSha256AndReturnsBase64urlSignature() {
|
|
189
|
-
val keyStore = mockk<KeyStore>(relaxed = true)
|
|
190
|
-
val entry = mockk<KeyStore.PrivateKeyEntry>(relaxed = true)
|
|
191
|
-
every { entry.privateKey } returns realKeyPair.private
|
|
192
|
-
mockkStatic(KeyStore::class)
|
|
193
|
-
every { KeyStore.getInstance(androidKeyStore) } returns keyStore
|
|
194
|
-
every { keyStore.getEntry("tag", null) } returns entry
|
|
195
|
-
|
|
196
|
-
val signatureBytes = byteArrayOf(0xDE.toByte(), 0xAD.toByte())
|
|
197
|
-
val signature = mockk<Signature>(relaxed = true)
|
|
198
|
-
every { signature.sign() } returns signatureBytes
|
|
199
|
-
mockkStatic(Signature::class)
|
|
200
|
-
every { Signature.getInstance("SHA256withECDSA") } returns signature
|
|
201
|
-
|
|
202
|
-
val payload = byteArrayOf(0x01, 0x02)
|
|
203
|
-
val result = manager.sign("tag", payload)
|
|
204
|
-
|
|
205
|
-
assertEquals(
|
|
206
|
-
Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes),
|
|
207
|
-
result,
|
|
208
|
-
)
|
|
209
|
-
verify { signature.initSign(realKeyPair.private) }
|
|
210
|
-
verify { signature.update(payload) }
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// endregion
|
|
214
|
-
|
|
215
|
-
// region generateKeyPair
|
|
216
|
-
|
|
217
|
-
private fun stubKeyStoreWithoutAlias(): KeyStore {
|
|
218
|
-
val keyStore = mockk<KeyStore>(relaxed = true)
|
|
219
|
-
mockkStatic(KeyStore::class)
|
|
220
|
-
every { KeyStore.getInstance(androidKeyStore) } returns keyStore
|
|
221
|
-
every { keyStore.containsAlias(any()) } returns false
|
|
222
|
-
return keyStore
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
@Test
|
|
226
|
-
fun generateKeyPair_throwsWhenAliasAlreadyExists() {
|
|
227
|
-
val keyStore = mockk<KeyStore>(relaxed = true)
|
|
228
|
-
mockkStatic(KeyStore::class)
|
|
229
|
-
every { KeyStore.getInstance(androidKeyStore) } returns keyStore
|
|
230
|
-
every { keyStore.containsAlias("dupe") } returns true
|
|
231
|
-
|
|
232
|
-
val ex = assertThrows(IllegalArgumentException::class.java) {
|
|
233
|
-
manager.generateKeyPair("dupe")
|
|
234
|
-
}
|
|
235
|
-
assertEquals("Key already exists for alias: dupe", ex.message)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
@Test
|
|
239
|
-
fun generateKeyPair_initializesSpecWithExpectedShape() {
|
|
240
|
-
stubKeyStoreWithoutAlias()
|
|
241
|
-
val kpg = mockk<KeyPairGenerator>(relaxed = true)
|
|
242
|
-
every { kpg.generateKeyPair() } returns realKeyPair
|
|
243
|
-
mockkStatic(KeyPairGenerator::class)
|
|
244
|
-
every { KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, androidKeyStore) } returns kpg
|
|
245
|
-
|
|
246
|
-
val specSlot = slot<java.security.spec.AlgorithmParameterSpec>()
|
|
247
|
-
every { kpg.initialize(capture(specSlot)) } returns Unit
|
|
248
|
-
|
|
249
|
-
val encoded = manager.generateKeyPair("mytag")
|
|
250
|
-
|
|
251
|
-
val spec = specSlot.captured as KeyGenParameterSpec
|
|
252
|
-
assertEquals("mytag", spec.keystoreAlias)
|
|
253
|
-
assertEquals(
|
|
254
|
-
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY,
|
|
255
|
-
spec.purposes,
|
|
256
|
-
)
|
|
257
|
-
assertTrue(spec.digests.contains(KeyProperties.DIGEST_SHA256))
|
|
258
|
-
assertEquals(
|
|
259
|
-
"secp256r1",
|
|
260
|
-
(spec.algorithmParameterSpec as ECGenParameterSpec).name,
|
|
261
|
-
)
|
|
262
|
-
// On API >= P the first attempt requests StrongBox backing.
|
|
263
|
-
assertTrue(spec.isStrongBoxBacked)
|
|
264
|
-
assertEquals(expectedEncodedPublicKey(), encoded)
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
@Test
|
|
268
|
-
fun generateKeyPair_fallsBackToSoftwareWhenStrongBoxUnavailable() {
|
|
269
|
-
stubKeyStoreWithoutAlias()
|
|
270
|
-
val kpg = mockk<KeyPairGenerator>(relaxed = true)
|
|
271
|
-
// First (StrongBox) attempt fails; second (software) attempt succeeds.
|
|
272
|
-
every { kpg.generateKeyPair() } throws StrongBoxUnavailableException() andThen realKeyPair
|
|
273
|
-
mockkStatic(KeyPairGenerator::class)
|
|
274
|
-
every { KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, androidKeyStore) } returns kpg
|
|
275
|
-
|
|
276
|
-
val specSlots = mutableListOf<java.security.spec.AlgorithmParameterSpec>()
|
|
277
|
-
every { kpg.initialize(capture(specSlots)) } returns Unit
|
|
278
|
-
|
|
279
|
-
val encoded = manager.generateKeyPair("mytag")
|
|
280
|
-
|
|
281
|
-
assertEquals(2, specSlots.size)
|
|
282
|
-
assertTrue((specSlots[0] as KeyGenParameterSpec).isStrongBoxBacked)
|
|
283
|
-
assertFalse((specSlots[1] as KeyGenParameterSpec).isStrongBoxBacked)
|
|
284
|
-
assertEquals(expectedEncodedPublicKey(), encoded)
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// endregion
|
|
288
|
-
}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
package xyz.dynamic.client.webview
|
|
2
|
-
|
|
3
|
-
import android.net.Uri
|
|
4
|
-
import android.webkit.WebResourceError
|
|
5
|
-
import android.webkit.WebResourceRequest
|
|
6
|
-
import android.webkit.WebResourceResponse
|
|
7
|
-
import android.webkit.WebView
|
|
8
|
-
import androidx.webkit.WebViewFeature
|
|
9
|
-
import io.mockk.every
|
|
10
|
-
import io.mockk.mockk
|
|
11
|
-
import io.mockk.mockkStatic
|
|
12
|
-
import io.mockk.unmockkAll
|
|
13
|
-
import io.mockk.verify
|
|
14
|
-
import org.junit.After
|
|
15
|
-
import org.junit.Assert.assertFalse
|
|
16
|
-
import org.junit.Assert.assertTrue
|
|
17
|
-
import org.junit.Before
|
|
18
|
-
import org.junit.Test
|
|
19
|
-
import org.junit.runner.RunWith
|
|
20
|
-
import org.robolectric.RobolectricTestRunner
|
|
21
|
-
import org.robolectric.annotation.Config
|
|
22
|
-
|
|
23
|
-
// Exercises the WebViewClient → host delegation in isolation: the host is a
|
|
24
|
-
// MockK mock so no Activity/WebView runtime is needed. Runs under Robolectric
|
|
25
|
-
// because DynamicWebViewClient extends the framework WebViewClient (whose
|
|
26
|
-
// android.jar stub constructor throws off-device) and the tests parse Uris.
|
|
27
|
-
@RunWith(RobolectricTestRunner::class)
|
|
28
|
-
@Config(sdk = [35])
|
|
29
|
-
class DynamicWebViewClientTest {
|
|
30
|
-
|
|
31
|
-
private lateinit var host: DynamicWebViewHost
|
|
32
|
-
private lateinit var client: DynamicWebViewClient
|
|
33
|
-
|
|
34
|
-
@Before
|
|
35
|
-
fun setUp() {
|
|
36
|
-
host = mockk(relaxed = true)
|
|
37
|
-
client = DynamicWebViewClient(host)
|
|
38
|
-
// WebViewFeature is a static facade over the device WebView; stub it so
|
|
39
|
-
// the tests are deterministic. Default: document-start scripts
|
|
40
|
-
// unsupported (so onPageStarted falls back to injecting the bridge).
|
|
41
|
-
mockkStatic(WebViewFeature::class)
|
|
42
|
-
every { WebViewFeature.isFeatureSupported(any()) } returns false
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
@After
|
|
46
|
-
fun tearDown() {
|
|
47
|
-
unmockkAll()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// region navigation pinning
|
|
51
|
-
|
|
52
|
-
private fun mainFrameRequestTo(target: String): WebResourceRequest =
|
|
53
|
-
mockk {
|
|
54
|
-
every { isForMainFrame } returns true
|
|
55
|
-
every { url } returns Uri.parse(target)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
@Test
|
|
59
|
-
fun shouldOverrideUrlLoading_blocksCrossOriginMainFrameNavigation() {
|
|
60
|
-
every { host.expectedOrigin } returns "https://app.dynamicauth.com"
|
|
61
|
-
|
|
62
|
-
val handled = client.shouldOverrideUrlLoading(
|
|
63
|
-
null,
|
|
64
|
-
mainFrameRequestTo("https://evil.example.com/phish"),
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
// true == "we handled it" == the WebView does NOT perform the navigation.
|
|
68
|
-
assertTrue(handled)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
@Test
|
|
72
|
-
fun shouldOverrideUrlLoading_allowsSameOriginMainFrameNavigation() {
|
|
73
|
-
every { host.expectedOrigin } returns "https://app.dynamicauth.com"
|
|
74
|
-
|
|
75
|
-
val handled = client.shouldOverrideUrlLoading(
|
|
76
|
-
null,
|
|
77
|
-
mainFrameRequestTo("https://app.dynamicauth.com/waas-v1/abc"),
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
assertFalse(handled)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
@Test
|
|
84
|
-
fun shouldOverrideUrlLoading_ignoresSubframeNavigation() {
|
|
85
|
-
every { host.expectedOrigin } returns "https://app.dynamicauth.com"
|
|
86
|
-
val request = mockk<WebResourceRequest> {
|
|
87
|
-
every { isForMainFrame } returns false
|
|
88
|
-
every { url } returns Uri.parse("https://evil.example.com/x")
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Subframe loads are left to the WebView regardless of origin.
|
|
92
|
-
assertFalse(client.shouldOverrideUrlLoading(null, request))
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
@Test
|
|
96
|
-
fun shouldOverrideUrlLoading_allowsWhenOriginNotYetPinned() {
|
|
97
|
-
every { host.expectedOrigin } returns null
|
|
98
|
-
|
|
99
|
-
assertFalse(
|
|
100
|
-
client.shouldOverrideUrlLoading(
|
|
101
|
-
null,
|
|
102
|
-
mainFrameRequestTo("https://anything.example.com"),
|
|
103
|
-
),
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// endregion
|
|
108
|
-
|
|
109
|
-
// region bridge-glue injection
|
|
110
|
-
|
|
111
|
-
@Test
|
|
112
|
-
fun onPageStarted_injectsBridgeScriptWhenDocumentStartUnsupported() {
|
|
113
|
-
every {
|
|
114
|
-
WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)
|
|
115
|
-
} returns false
|
|
116
|
-
val view = mockk<WebView>(relaxed = true)
|
|
117
|
-
|
|
118
|
-
client.onPageStarted(view, "https://app.dynamicauth.com", null)
|
|
119
|
-
|
|
120
|
-
verify { view.evaluateJavascript(BRIDGE_USER_SCRIPT, null) }
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
@Test
|
|
124
|
-
fun onPageStarted_skipsInjectionWhenDocumentStartSupported() {
|
|
125
|
-
every {
|
|
126
|
-
WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)
|
|
127
|
-
} returns true
|
|
128
|
-
val view = mockk<WebView>(relaxed = true)
|
|
129
|
-
|
|
130
|
-
client.onPageStarted(view, "https://app.dynamicauth.com", null)
|
|
131
|
-
|
|
132
|
-
verify(exactly = 0) { view.evaluateJavascript(any(), any()) }
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// endregion
|
|
136
|
-
|
|
137
|
-
// region error / lifecycle routing
|
|
138
|
-
|
|
139
|
-
@Test
|
|
140
|
-
fun onPageCommitVisible_notifiesHost() {
|
|
141
|
-
client.onPageCommitVisible(null, "https://app.dynamicauth.com")
|
|
142
|
-
verify { host.onCommitVisible() }
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
@Test
|
|
146
|
-
fun onReceivedError_mainFrame_reportsNavigationFailed() {
|
|
147
|
-
val request = mockk<WebResourceRequest> { every { isForMainFrame } returns true }
|
|
148
|
-
val error = mockk<WebResourceError> { every { description } returns "net::ERR_FAILED" }
|
|
149
|
-
|
|
150
|
-
client.onReceivedError(null, request, error)
|
|
151
|
-
|
|
152
|
-
verify { host.onNavigationFailed("net::ERR_FAILED") }
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
@Test
|
|
156
|
-
fun onReceivedError_subFrame_isIgnored() {
|
|
157
|
-
val request = mockk<WebResourceRequest> { every { isForMainFrame } returns false }
|
|
158
|
-
val error = mockk<WebResourceError>(relaxed = true)
|
|
159
|
-
|
|
160
|
-
client.onReceivedError(null, request, error)
|
|
161
|
-
|
|
162
|
-
verify(exactly = 0) { host.onNavigationFailed(any()) }
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
@Test
|
|
166
|
-
fun onReceivedHttpError_mainFrame4xx_reportsHttpError() {
|
|
167
|
-
val request = mockk<WebResourceRequest> { every { isForMainFrame } returns true }
|
|
168
|
-
val response = mockk<WebResourceResponse> { every { statusCode } returns 404 }
|
|
169
|
-
|
|
170
|
-
client.onReceivedHttpError(null, request, response)
|
|
171
|
-
|
|
172
|
-
verify { host.onHttpError(404) }
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
@Test
|
|
176
|
-
fun onReceivedHttpError_below400_isIgnored() {
|
|
177
|
-
val request = mockk<WebResourceRequest> { every { isForMainFrame } returns true }
|
|
178
|
-
val response = mockk<WebResourceResponse> { every { statusCode } returns 302 }
|
|
179
|
-
|
|
180
|
-
client.onReceivedHttpError(null, request, response)
|
|
181
|
-
|
|
182
|
-
verify(exactly = 0) { host.onHttpError(any()) }
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
@Test
|
|
186
|
-
fun onRenderProcessGone_notifiesHostAndHandlesCrash() {
|
|
187
|
-
val handled = client.onRenderProcessGone(null, null)
|
|
188
|
-
|
|
189
|
-
// Returning true tells the framework the crash is handled, preventing a
|
|
190
|
-
// host-process crash.
|
|
191
|
-
assertTrue(handled)
|
|
192
|
-
verify { host.onRenderProcessGone() }
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// endregion
|
|
196
|
-
}
|