1
1
package com.credman.cmwallet.pnv
2
2
3
+ import android.security.keystore.KeyGenParameterSpec
4
+ import android.security.keystore.KeyProperties
3
5
import android.util.Base64
4
6
import android.util.Log
5
7
import androidx.credentials.provider.CallingAppInfo
8
+ import com.credman.cmwallet.CmWalletApplication.Companion.TAG
6
9
import com.credman.cmwallet.CmWalletApplication.Companion.computeClientId
7
10
import com.credman.cmwallet.createJWTES256
8
11
import com.credman.cmwallet.data.repository.CredentialRepository.Companion.ICON
@@ -22,14 +25,24 @@ import com.credman.cmwallet.pnv.PnvTokenRegistry.Companion.TEST_PNV_1_VERIFY_PHO
22
25
import com.credman.cmwallet.pnv.PnvTokenRegistry.Companion.TEST_PNV_2_VERIFY_PHONE_NUMBER
23
26
import com.credman.cmwallet.pnv.PnvTokenRegistry.Companion.VCT_GET_PHONE_NUMBER
24
27
import com.credman.cmwallet.pnv.PnvTokenRegistry.Companion.VCT_VERIFY_PHONE_NUMBER
28
+ import com.credman.cmwallet.toBase64UrlNoPadding
29
+ import com.credman.cmwallet.toJWK
30
+ import kotlinx.serialization.json.add
31
+ import kotlinx.serialization.json.buildJsonArray
25
32
import kotlinx.serialization.json.buildJsonObject
26
33
import kotlinx.serialization.json.put
27
34
import org.json.JSONArray
28
35
import org.json.JSONObject
29
36
import java.io.ByteArrayOutputStream
30
37
import java.nio.ByteBuffer
31
38
import java.nio.ByteOrder
39
+ import java.security.KeyPair
40
+ import java.security.KeyPairGenerator
41
+ import java.security.KeyStore
42
+ import java.security.MessageDigest
32
43
import java.security.interfaces.ECPrivateKey
44
+ import java.time.Instant
45
+ import java.util.Enumeration
33
46
34
47
/* *
35
48
* A phone number verification entry to be registered with the Credential Manager.
@@ -175,6 +188,7 @@ data class PnvTokenRegistry(
175
188
registryCredentials.put(PNV_CRED_FORMAT , sdJwtCredentials)
176
189
val registryJson = JSONObject ()
177
190
registryJson.put(CREDENTIALS , registryCredentials)
191
+ Log .d(TAG , " Phone Number to be registered:\n $registryJson " )
178
192
out .write(registryJson.toString().toByteArray())
179
193
return out .toByteArray()
180
194
}
@@ -200,6 +214,38 @@ private class SdJwtRegistryItem(
200
214
val displayData : ItemDisplayData ,
201
215
)
202
216
217
+ private fun getDeviceKey (): KeyPair {
218
+ val alias = " pnv"
219
+ val ks: KeyStore = KeyStore .getInstance(" AndroidKeyStore" ).apply {
220
+ load(null )
221
+ }
222
+ if (ks.containsAlias(alias)) {
223
+ val entry = ks.getEntry(alias, null )
224
+ if (entry !is KeyStore .PrivateKeyEntry ) {
225
+ throw IllegalStateException (" Not an instance of a PrivateKeyEntry" )
226
+ }
227
+ val private = entry.privateKey
228
+ val public = ks.getCertificate(alias).publicKey
229
+ return KeyPair (public, private)
230
+ } else {
231
+ val kpg: KeyPairGenerator = KeyPairGenerator .getInstance(
232
+ KeyProperties .KEY_ALGORITHM_EC ,
233
+ " AndroidKeyStore"
234
+ )
235
+ val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec .Builder (
236
+ alias,
237
+ KeyProperties .PURPOSE_SIGN or KeyProperties .PURPOSE_VERIFY
238
+ ).run {
239
+ setDigests(KeyProperties .DIGEST_SHA256 )
240
+ build()
241
+ }
242
+ kpg.initialize(parameterSpec)
243
+
244
+ val kp = kpg.generateKeyPair()
245
+ return kp
246
+ }
247
+ }
248
+
203
249
fun maybeHandlePnv (
204
250
requestJson : String ,
205
251
providerIdx : Int ,
@@ -281,24 +327,47 @@ fun maybeHandlePnv(
281
327
}
282
328
val encryptedTempTokenJwe = jweSerialization(aggregatorEncKey, tempTokenJson.toString())
283
329
284
- val tmpKey =
330
+ val tmpDeviceTelModuleKey =
285
331
" MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg6ef4-enmfQHRWUW40-Soj3aFB0rsEOp3tYMW-HJPBvChRANCAAT5N1NLZcub4bOgWfBwF8MHPGkfJ8Dm300cioatq9XovaLgG205FEXUOuNMEMQuLbrn8oiOC0nTnNIVn-OtSmSb"
286
- val privateKey =
287
- loadECPrivateKey(Base64 .decode(tmpKey, Base64 .URL_SAFE )) as ECPrivateKey
288
- val tempTokenDcJwt = createJWTES256(
332
+ val deviceTelModulePrivateKey =
333
+ loadECPrivateKey(Base64 .decode(tmpDeviceTelModuleKey, Base64 .URL_SAFE )) as ECPrivateKey
334
+ val deviceKp = getDeviceKey()
335
+
336
+ val deviceTelModuleJwt = createJWTES256(
337
+ header = buildJsonObject {
338
+ put(" alg" , " ES256" )
339
+ put(" x5c" , buildJsonArray {
340
+ add(" MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+TdTS2XLm+GzoFnwcBfDBzxpHyfA5t9NHIqGravV6L2i4BttORRF1DrjTBDELi265/KIjgtJ05zSFZ/jrUpkmw==" )
341
+ })
342
+ },
343
+ payload = buildJsonObject {
344
+ put(" cnf" , buildJsonObject {
345
+ put(" jwk" , deviceKp.public.toJWK())
346
+ })
347
+ },
348
+ privateKey = deviceTelModulePrivateKey
349
+ )
350
+ val sdJwt = deviceTelModuleJwt + " ~"
351
+
352
+ val md = MessageDigest .getInstance(" SHA-256" )
353
+ val digest = md.digest(sdJwt.encodeToByteArray()).toBase64UrlNoPadding()
354
+ val kbJwt = createJWTES256(
289
355
header = buildJsonObject {
356
+ put(" typ" , " kb+jwt" ) // MUST be kb+jwt
290
357
put(" alg" , " ES256" )
291
358
},
292
359
payload = buildJsonObject {
360
+ put(" iat" , Instant .now().epochSecond)
361
+ put(" aud" , origin)
293
362
put(" nonce" , openId4VPRequest.nonce)
294
- put(" origin" , origin)
295
363
put(" encrypted_credential" , encryptedTempTokenJwe)
364
+ put(" sd_hash" , digest)
296
365
},
297
- privateKey = privateKey
366
+ privateKey = deviceKp.private
298
367
)
299
368
300
369
// We don't use selective disclosure, so the sd-jwt is simply jwt + "~"
301
- val tempTokenDcSdJwt = " ${tempTokenDcJwt} ~ "
370
+ val tempTokenDcSdJwt = " ${deviceTelModuleJwt} ~ ${kbJwt} "
302
371
303
372
val vpToken = JSONObject ().apply {
304
373
put(dcqlCredId, tempTokenDcSdJwt)
0 commit comments