@necessarysdk/react-native 0.0.0-stage → 0.1.5
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/CHANGELOG.md +22 -0
- package/LICENSE +201 -0
- package/NOTICE +2 -0
- package/NecessaryReactNative.podspec +23 -0
- package/README.md +429 -2
- package/android/build.gradle +83 -0
- package/android/src/main/AndroidManifest.xml +41 -0
- package/android/src/main/cpp/CMakeLists.txt +7 -0
- package/android/src/main/cpp/code_integrity.cpp +484 -0
- package/android/src/main/java/com/necessarysdk/reactnative/Causes.kt +49 -0
- package/android/src/main/java/com/necessarysdk/reactnative/InstallationKeyManager.kt +388 -0
- package/android/src/main/java/com/necessarysdk/reactnative/IntegrityProbeManager.kt +1152 -0
- package/android/src/main/java/com/necessarysdk/reactnative/KeyPresence.kt +62 -0
- package/android/src/main/java/com/necessarysdk/reactnative/NecessaryModules.kt +94 -0
- package/android/src/main/java/com/necessarysdk/reactnative/NecessaryNative.kt +323 -0
- package/android/src/main/java/com/necessarysdk/reactnative/NecessaryPackage.kt +38 -0
- package/android/src/main/java/com/necessarysdk/reactnative/ProbeReads.kt +314 -0
- package/android/src/main/java/com/necessarysdk/reactnative/ScreenGuard.kt +56 -0
- package/android/src/main/java/com/necessarysdk/reactnative/SecureStore.kt +127 -0
- package/android/src/main/java/com/necessarysdk/reactnative/StepUpKeyEntry.kt +57 -0
- package/android/src/main/java/com/necessarysdk/reactnative/StepUpKeyManager.kt +762 -0
- package/contract/report-1/android-8-ios-5/RULES.md +149 -0
- package/contract/report-1/android-8-ios-5/report.schema.json +2753 -0
- package/contract/report-1/android-8-ios-5/samples/android-8-complete.json +205 -0
- package/contract/report-1/android-8-ios-5/samples/android-8-incomplete.json +205 -0
- package/contract/report-1/android-8-ios-5/samples/android-8-invalid.json +205 -0
- package/contract/report-1/android-8-ios-5/samples/android-8-location-one-fix.json +228 -0
- package/contract/report-1/android-8-ios-5/samples/android-8-location-two-fixes.json +228 -0
- package/contract/report-1/android-8-ios-5/samples/android-8-refused.json +208 -0
- package/contract/report-1/android-8-ios-5/samples/ios-5-complete.json +106 -0
- package/contract/report-1/android-8-ios-5/samples/ios-5-incomplete.json +105 -0
- package/contract/report-1/android-8-ios-5/samples/ios-5-invalid.json +107 -0
- package/contract/report-1/android-8-ios-5/samples/ios-5-partial.json +110 -0
- package/contract/report-1/android-8-ios-5/samples/ios-5-refused.json +109 -0
- package/doc/integration_guide.md +771 -0
- package/doc/measurements.md +774 -0
- package/ios/InstallationKeyManager.swift +694 -0
- package/ios/IntegrityProbeManager.swift +960 -0
- package/ios/NecessaryModules.h +34 -0
- package/ios/NecessaryModules.mm +208 -0
- package/ios/NecessaryNative.swift +327 -0
- package/ios/PrivacyInfo.xcprivacy +14 -0
- package/ios/ProbeReads.swift +353 -0
- package/lib/client.d.ts +105 -0
- package/lib/client.js +176 -0
- package/lib/collector.d.ts +18 -0
- package/lib/collector.js +49 -0
- package/lib/deviceTrust.d.ts +253 -0
- package/lib/deviceTrust.js +690 -0
- package/lib/errors.d.ts +43 -0
- package/lib/errors.js +56 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +34 -0
- package/lib/installationKey.d.ts +62 -0
- package/lib/installationKey.js +185 -0
- package/lib/installationStore.d.ts +49 -0
- package/lib/installationStore.js +127 -0
- package/lib/native/NativeNecessaryInstallationKey.d.ts +10 -0
- package/lib/native/NativeNecessaryInstallationKey.js +4 -0
- package/lib/native/NativeNecessaryIntegrity.d.ts +8 -0
- package/lib/native/NativeNecessaryIntegrity.js +4 -0
- package/lib/native/NativeNecessarySecureScreen.d.ts +7 -0
- package/lib/native/NativeNecessarySecureScreen.js +4 -0
- package/lib/native/NativeNecessaryStepUpKey.d.ts +9 -0
- package/lib/native/NativeNecessaryStepUpKey.js +4 -0
- package/lib/native/NativeNecessaryStorage.d.ts +14 -0
- package/lib/native/NativeNecessaryStorage.js +4 -0
- package/lib/protocol.d.ts +24 -0
- package/lib/protocol.js +50 -0
- package/lib/reinstallHint.d.ts +23 -0
- package/lib/reinstallHint.js +65 -0
- package/lib/report.d.ts +62 -0
- package/lib/report.js +140 -0
- package/lib/requestProof.d.ts +116 -0
- package/lib/requestProof.js +220 -0
- package/lib/secureScreen.d.ts +15 -0
- package/lib/secureScreen.js +32 -0
- package/lib/stepUpKey.d.ts +105 -0
- package/lib/stepUpKey.js +187 -0
- package/lib/transport.d.ts +39 -0
- package/lib/transport.js +117 -0
- package/lib/util/bytes.d.ts +18 -0
- package/lib/util/bytes.js +207 -0
- package/lib/util/native.d.ts +20 -0
- package/lib/util/native.js +49 -0
- package/lib/util/release.d.ts +2 -0
- package/lib/util/release.js +7 -0
- package/lib/util/target.d.ts +18 -0
- package/lib/util/target.js +128 -0
- package/package.json +81 -4
- package/src/client.ts +221 -0
- package/src/collector.ts +64 -0
- package/src/deviceTrust.ts +842 -0
- package/src/errors.ts +71 -0
- package/src/index.ts +19 -0
- package/src/installationKey.ts +223 -0
- package/src/installationStore.ts +143 -0
- package/src/native/NativeNecessaryInstallationKey.ts +12 -0
- package/src/native/NativeNecessaryIntegrity.ts +15 -0
- package/src/native/NativeNecessarySecureScreen.ts +9 -0
- package/src/native/NativeNecessaryStepUpKey.ts +11 -0
- package/src/native/NativeNecessaryStorage.ts +16 -0
- package/src/protocol.ts +52 -0
- package/src/reinstallHint.ts +69 -0
- package/src/report.ts +174 -0
- package/src/requestProof.ts +295 -0
- package/src/secureScreen.ts +29 -0
- package/src/stepUpKey.ts +238 -0
- package/src/transport.ts +145 -0
- package/src/util/bytes.ts +194 -0
- package/src/util/native.ts +56 -0
- package/src/util/release.ts +6 -0
- package/src/util/target.ts +132 -0
- package/vectors/client_server.json +264 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
package com.necessarysdk.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.pm.PackageManager
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import android.security.keystore.KeyGenParameterSpec
|
|
7
|
+
import android.security.keystore.KeyInfo
|
|
8
|
+
import android.security.keystore.KeyProperties
|
|
9
|
+
import android.util.Base64
|
|
10
|
+
import java.math.BigInteger
|
|
11
|
+
import java.security.GeneralSecurityException
|
|
12
|
+
import java.security.KeyFactory
|
|
13
|
+
import java.security.KeyPairGenerator
|
|
14
|
+
import java.security.KeyStore
|
|
15
|
+
import java.security.ProviderException
|
|
16
|
+
import java.security.Signature
|
|
17
|
+
import java.security.interfaces.ECPublicKey
|
|
18
|
+
import java.security.spec.ECGenParameterSpec
|
|
19
|
+
|
|
20
|
+
class InstallationKeyFailure(
|
|
21
|
+
val errorCode: String,
|
|
22
|
+
override val message: String,
|
|
23
|
+
cause: Throwable? = null,
|
|
24
|
+
) : Exception(message, cause) {
|
|
25
|
+
/**
|
|
26
|
+
* What the app is told: this failure's words, then every cause beneath it
|
|
27
|
+
* (Causes). [detail] adds AndroidKeyStore's error code where its API has one.
|
|
28
|
+
*/
|
|
29
|
+
fun report(detail: (Throwable) -> String? = { null }): String =
|
|
30
|
+
Causes.withCauses(message, cause, detail)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Owns one non-exportable P-256 signing key in AndroidKeyStore.
|
|
35
|
+
*
|
|
36
|
+
* The private key object is only a handle. Android performs signing through the
|
|
37
|
+
* keystore provider, and this class returns only the public EC coordinates and
|
|
38
|
+
* DER-encoded ECDSA signatures to the JavaScript side.
|
|
39
|
+
*/
|
|
40
|
+
class InstallationKeyManager(private val context: Context) {
|
|
41
|
+
companion object {
|
|
42
|
+
private const val ANDROID_KEY_STORE = "AndroidKeyStore"
|
|
43
|
+
private const val CURVE_NAME = "secp256r1"
|
|
44
|
+
private const val KEY_METADATA_VERSION = 2
|
|
45
|
+
private const val MAX_PAYLOAD_BYTES = 65536
|
|
46
|
+
|
|
47
|
+
// Making and deleting the key run one at a time in the process,
|
|
48
|
+
// whichever runtime asks: two first starts at once would otherwise each
|
|
49
|
+
// find no key, and the second would generate over the first's.
|
|
50
|
+
private val keyLock = Any()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private val keyAlias: String =
|
|
54
|
+
"${context.packageName}.device_recognition.installation_key.v2"
|
|
55
|
+
|
|
56
|
+
// Never holds a key: deleting it says whether the keystore answers
|
|
57
|
+
// (KeyPresence).
|
|
58
|
+
private val probeAlias: String = "$keyAlias.probe"
|
|
59
|
+
|
|
60
|
+
fun getOrCreateKey(): Map<String, Any> = synchronized(keyLock) { loadOrMakeKey() }
|
|
61
|
+
|
|
62
|
+
private fun loadOrMakeKey(): Map<String, Any> {
|
|
63
|
+
requireSupportedAndroid()
|
|
64
|
+
val keyStore = loadKeyStore()
|
|
65
|
+
var created = false
|
|
66
|
+
|
|
67
|
+
// A key is made only where the keystore, answering, holds none: a
|
|
68
|
+
// lookup that failed reads as "no key", and generating would replace
|
|
69
|
+
// the installation's key with a new one.
|
|
70
|
+
val found = presence(keyStore)
|
|
71
|
+
when (found.answer) {
|
|
72
|
+
KeyPresence.Answer.PRESENT -> {}
|
|
73
|
+
KeyPresence.Answer.ABSENT -> {
|
|
74
|
+
generateKeyPair(keyStore)
|
|
75
|
+
created = true
|
|
76
|
+
}
|
|
77
|
+
KeyPresence.Answer.UNAVAILABLE -> throw keyUnavailable(found.cause)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
var entry = privateKeyEntry(keyStore)
|
|
81
|
+
if (!isExpectedP256Key(entry)) {
|
|
82
|
+
// An interrupted development build may have left a different key
|
|
83
|
+
// under the alias. Replace it and say that a new key exists, so the
|
|
84
|
+
// JavaScript side also rotates the installation UUID.
|
|
85
|
+
keyStore.deleteEntry(keyAlias)
|
|
86
|
+
generateKeyPair(keyStore)
|
|
87
|
+
created = true
|
|
88
|
+
entry = privateKeyEntry(keyStore)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
val publicKey = entry.certificate.publicKey as? ECPublicKey
|
|
92
|
+
?: throw InstallationKeyFailure(
|
|
93
|
+
"INVALID_PUBLIC_KEY",
|
|
94
|
+
"AndroidKeyStore did not return an EC public key.",
|
|
95
|
+
)
|
|
96
|
+
val x = unsignedFixed(publicKey.w.affineX, 32)
|
|
97
|
+
val y = unsignedFixed(publicKey.w.affineY, 32)
|
|
98
|
+
val security = describeSecurity(entry)
|
|
99
|
+
|
|
100
|
+
return mapOf(
|
|
101
|
+
"version" to KEY_METADATA_VERSION,
|
|
102
|
+
"key_alias" to keyAlias,
|
|
103
|
+
"algorithm" to "ES256",
|
|
104
|
+
"signature_format" to "asn1_der",
|
|
105
|
+
"provider" to ANDROID_KEY_STORE,
|
|
106
|
+
"security_level" to security.first,
|
|
107
|
+
"hardware_backed" to security.second,
|
|
108
|
+
"private_key_exportable" to false,
|
|
109
|
+
"created" to created,
|
|
110
|
+
"public_key" to mapOf(
|
|
111
|
+
"kty" to "EC",
|
|
112
|
+
"crv" to "P-256",
|
|
113
|
+
"alg" to "ES256",
|
|
114
|
+
"x" to base64Url(x),
|
|
115
|
+
"y" to base64Url(y),
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fun signPayload(payloadBase64Url: String): String {
|
|
121
|
+
requireSupportedAndroid()
|
|
122
|
+
val payload = decodeBase64Url(payloadBase64Url)
|
|
123
|
+
if (payload.size > MAX_PAYLOAD_BYTES) {
|
|
124
|
+
throw InstallationKeyFailure(
|
|
125
|
+
"PAYLOAD_TOO_LARGE",
|
|
126
|
+
"The challenge payload is larger than $MAX_PAYLOAD_BYTES bytes.",
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
val keyStore = loadKeyStore()
|
|
131
|
+
val found = presence(keyStore)
|
|
132
|
+
when (found.answer) {
|
|
133
|
+
KeyPresence.Answer.PRESENT -> {}
|
|
134
|
+
KeyPresence.Answer.ABSENT -> throw InstallationKeyFailure(
|
|
135
|
+
"KEY_NOT_FOUND",
|
|
136
|
+
"The Android installation key is missing. Restart the app to create a fresh installation identity.",
|
|
137
|
+
)
|
|
138
|
+
KeyPresence.Answer.UNAVAILABLE -> throw keyUnavailable(found.cause)
|
|
139
|
+
}
|
|
140
|
+
val entry = privateKeyEntry(keyStore)
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
val signer = Signature.getInstance("SHA256withECDSA")
|
|
144
|
+
signer.initSign(entry.privateKey)
|
|
145
|
+
signer.update(payload)
|
|
146
|
+
// The JCA SHA256withECDSA signature format is ASN.1 DER.
|
|
147
|
+
return base64Url(signer.sign())
|
|
148
|
+
} catch (error: GeneralSecurityException) {
|
|
149
|
+
throw InstallationKeyFailure(
|
|
150
|
+
"SIGNING_FAILED",
|
|
151
|
+
"AndroidKeyStore could not sign the installation challenge.",
|
|
152
|
+
error,
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Deletes the key, whatever a lookup says: a lookup that failed reads as
|
|
159
|
+
* "no key", and skipping the deletion then would leave the key in place
|
|
160
|
+
* after a reset. Whether one was there is for the answer only.
|
|
161
|
+
*/
|
|
162
|
+
fun deleteKey(): Boolean = synchronized(keyLock) {
|
|
163
|
+
requireSupportedAndroid()
|
|
164
|
+
val keyStore = loadKeyStore()
|
|
165
|
+
try {
|
|
166
|
+
val existed = keyStore.containsAlias(keyAlias)
|
|
167
|
+
keyStore.deleteEntry(keyAlias)
|
|
168
|
+
if (keyStore.containsAlias(keyAlias)) {
|
|
169
|
+
throw InstallationKeyFailure(
|
|
170
|
+
"KEY_DELETE_FAILED",
|
|
171
|
+
"AndroidKeyStore did not delete the installation key: it is still present.",
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
existed
|
|
175
|
+
} catch (error: GeneralSecurityException) {
|
|
176
|
+
throw InstallationKeyFailure(
|
|
177
|
+
"KEY_DELETE_FAILED",
|
|
178
|
+
"AndroidKeyStore could not delete the installation key.",
|
|
179
|
+
error,
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private fun presence(keyStore: KeyStore): KeyPresence.Found =
|
|
185
|
+
KeyPresence.of(object : KeyPresence.Keystore {
|
|
186
|
+
override fun containsKey() = keyStore.containsAlias(keyAlias)
|
|
187
|
+
override fun deleteProbe() = keyStore.deleteEntry(probeAlias)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
private fun keyUnavailable(cause: Throwable?) = InstallationKeyFailure(
|
|
191
|
+
"KEY_UNAVAILABLE",
|
|
192
|
+
"AndroidKeyStore could not be reached for the installation key; the key was kept, never " +
|
|
193
|
+
"replaced. Try again; if this persists, reset() starts a new installation.",
|
|
194
|
+
cause,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
private fun requireSupportedAndroid() {
|
|
198
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
199
|
+
throw InstallationKeyFailure(
|
|
200
|
+
"UNSUPPORTED_ANDROID_VERSION",
|
|
201
|
+
"This prototype requires Android 6.0 (API 23) or newer.",
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private fun loadKeyStore(): KeyStore {
|
|
207
|
+
try {
|
|
208
|
+
return KeyStore.getInstance(ANDROID_KEY_STORE).apply { load(null) }
|
|
209
|
+
} catch (error: GeneralSecurityException) {
|
|
210
|
+
throw InstallationKeyFailure(
|
|
211
|
+
"KEYSTORE_UNAVAILABLE",
|
|
212
|
+
"AndroidKeyStore is unavailable.",
|
|
213
|
+
error,
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private fun generateKeyPair(keyStore: KeyStore) {
|
|
219
|
+
val canTryStrongBox = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
|
220
|
+
context.packageManager.hasSystemFeature(
|
|
221
|
+
PackageManager.FEATURE_STRONGBOX_KEYSTORE,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
if (canTryStrongBox) {
|
|
225
|
+
try {
|
|
226
|
+
generateKeyPair(preferStrongBox = true)
|
|
227
|
+
return
|
|
228
|
+
} catch (error: Exception) {
|
|
229
|
+
// Some devices advertise StrongBox but cannot satisfy a
|
|
230
|
+
// particular algorithm/digest combination. Remove any partial
|
|
231
|
+
// alias and retry with the regular AndroidKeyStore provider.
|
|
232
|
+
try {
|
|
233
|
+
if (keyStore.containsAlias(keyAlias)) {
|
|
234
|
+
keyStore.deleteEntry(keyAlias)
|
|
235
|
+
}
|
|
236
|
+
} catch (_: Exception) {
|
|
237
|
+
// The normal generation attempt below will produce the
|
|
238
|
+
// actionable error if the keystore is genuinely unusable.
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
generateKeyPair(preferStrongBox = false)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private fun generateKeyPair(preferStrongBox: Boolean) {
|
|
247
|
+
try {
|
|
248
|
+
val generator = KeyPairGenerator.getInstance(
|
|
249
|
+
KeyProperties.KEY_ALGORITHM_EC,
|
|
250
|
+
ANDROID_KEY_STORE,
|
|
251
|
+
)
|
|
252
|
+
val builder = KeyGenParameterSpec.Builder(
|
|
253
|
+
keyAlias,
|
|
254
|
+
KeyProperties.PURPOSE_SIGN,
|
|
255
|
+
)
|
|
256
|
+
.setAlgorithmParameterSpec(ECGenParameterSpec(CURVE_NAME))
|
|
257
|
+
.setDigests(KeyProperties.DIGEST_SHA256)
|
|
258
|
+
.setUserAuthenticationRequired(false)
|
|
259
|
+
|
|
260
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && preferStrongBox) {
|
|
261
|
+
builder.setIsStrongBoxBacked(true)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
generator.initialize(builder.build())
|
|
265
|
+
generator.generateKeyPair()
|
|
266
|
+
} catch (error: GeneralSecurityException) {
|
|
267
|
+
throw InstallationKeyFailure(
|
|
268
|
+
"KEY_GENERATION_FAILED",
|
|
269
|
+
if (preferStrongBox) {
|
|
270
|
+
"StrongBox could not create the P-256 installation key."
|
|
271
|
+
} else {
|
|
272
|
+
"AndroidKeyStore could not create the P-256 installation key."
|
|
273
|
+
},
|
|
274
|
+
error,
|
|
275
|
+
)
|
|
276
|
+
} catch (error: ProviderException) {
|
|
277
|
+
throw InstallationKeyFailure(
|
|
278
|
+
"KEY_GENERATION_FAILED",
|
|
279
|
+
if (preferStrongBox) {
|
|
280
|
+
"StrongBox could not create the P-256 installation key."
|
|
281
|
+
} else {
|
|
282
|
+
"AndroidKeyStore could not create the P-256 installation key."
|
|
283
|
+
},
|
|
284
|
+
error,
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private fun privateKeyEntry(keyStore: KeyStore): KeyStore.PrivateKeyEntry {
|
|
290
|
+
try {
|
|
291
|
+
// getEntry answers null when its own lookup fails (the keystore
|
|
292
|
+
// just reported the key), and an UnsupportedOperationException
|
|
293
|
+
// when the alias holds no key entry -- on Android 7 to 11 also when
|
|
294
|
+
// that lookup fails: both may pass, so the key is kept.
|
|
295
|
+
val entry = keyStore.getEntry(keyAlias, null)
|
|
296
|
+
?: throw keyUnavailable(null)
|
|
297
|
+
return entry as? KeyStore.PrivateKeyEntry
|
|
298
|
+
?: throw InstallationKeyFailure(
|
|
299
|
+
"KEY_NOT_FOUND",
|
|
300
|
+
"The Android installation key entry has the wrong type.",
|
|
301
|
+
)
|
|
302
|
+
} catch (error: UnsupportedOperationException) {
|
|
303
|
+
throw keyUnavailable(error)
|
|
304
|
+
} catch (error: GeneralSecurityException) {
|
|
305
|
+
throw InstallationKeyFailure(
|
|
306
|
+
"KEY_LOOKUP_FAILED",
|
|
307
|
+
"AndroidKeyStore could not load the installation key.",
|
|
308
|
+
error,
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private fun isExpectedP256Key(entry: KeyStore.PrivateKeyEntry): Boolean {
|
|
314
|
+
val publicKey = entry.certificate.publicKey as? ECPublicKey ?: return false
|
|
315
|
+
return publicKey.params.curve.field.fieldSize == 256
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private fun describeSecurity(
|
|
319
|
+
entry: KeyStore.PrivateKeyEntry,
|
|
320
|
+
): Pair<String, Boolean> {
|
|
321
|
+
return try {
|
|
322
|
+
val keyFactory = KeyFactory.getInstance(
|
|
323
|
+
entry.privateKey.algorithm,
|
|
324
|
+
ANDROID_KEY_STORE,
|
|
325
|
+
)
|
|
326
|
+
val keyInfo = keyFactory.getKeySpec(entry.privateKey, KeyInfo::class.java)
|
|
327
|
+
|
|
328
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
329
|
+
when (keyInfo.securityLevel) {
|
|
330
|
+
KeyProperties.SECURITY_LEVEL_STRONGBOX -> "strongbox" to true
|
|
331
|
+
KeyProperties.SECURITY_LEVEL_TRUSTED_ENVIRONMENT ->
|
|
332
|
+
"trusted_execution_environment" to true
|
|
333
|
+
KeyProperties.SECURITY_LEVEL_SOFTWARE -> "software" to false
|
|
334
|
+
else -> "unknown" to false
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
if (keyInfo.isInsideSecureHardware) {
|
|
338
|
+
"secure_hardware" to true
|
|
339
|
+
} else {
|
|
340
|
+
"software" to false
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} catch (_: Exception) {
|
|
344
|
+
"unknown" to false
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private fun unsignedFixed(value: BigInteger, size: Int): ByteArray {
|
|
349
|
+
var raw = value.toByteArray()
|
|
350
|
+
if (raw.size == size + 1 && raw[0] == 0.toByte()) {
|
|
351
|
+
raw = raw.copyOfRange(1, raw.size)
|
|
352
|
+
}
|
|
353
|
+
if (raw.size > size) {
|
|
354
|
+
throw InstallationKeyFailure(
|
|
355
|
+
"INVALID_PUBLIC_KEY",
|
|
356
|
+
"The EC coordinate is larger than P-256 permits.",
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
return ByteArray(size).also { output ->
|
|
360
|
+
System.arraycopy(raw, 0, output, size - raw.size, raw.size)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private fun base64Url(value: ByteArray): String {
|
|
365
|
+
return Base64.encodeToString(
|
|
366
|
+
value,
|
|
367
|
+
Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING,
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private fun decodeBase64Url(value: String): ByteArray {
|
|
372
|
+
if (value.isBlank()) {
|
|
373
|
+
throw InstallationKeyFailure(
|
|
374
|
+
"INVALID_PAYLOAD",
|
|
375
|
+
"The challenge payload is empty.",
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
return try {
|
|
379
|
+
Base64.decode(value, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING)
|
|
380
|
+
} catch (error: IllegalArgumentException) {
|
|
381
|
+
throw InstallationKeyFailure(
|
|
382
|
+
"INVALID_PAYLOAD",
|
|
383
|
+
"The challenge payload is not valid base64url.",
|
|
384
|
+
error,
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|