Skip to content

Commit 81e6f55

Browse files
committed
Make the pnv kb jwt
1 parent e244392 commit 81e6f55

File tree

1 file changed

+76
-7
lines changed

1 file changed

+76
-7
lines changed

app/src/main/java/com/credman/cmwallet/pnv/PnvTokenRegistry.kt

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package com.credman.cmwallet.pnv
22

3+
import android.security.keystore.KeyGenParameterSpec
4+
import android.security.keystore.KeyProperties
35
import android.util.Base64
46
import android.util.Log
57
import androidx.credentials.provider.CallingAppInfo
8+
import com.credman.cmwallet.CmWalletApplication.Companion.TAG
69
import com.credman.cmwallet.CmWalletApplication.Companion.computeClientId
710
import com.credman.cmwallet.createJWTES256
811
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
2225
import com.credman.cmwallet.pnv.PnvTokenRegistry.Companion.TEST_PNV_2_VERIFY_PHONE_NUMBER
2326
import com.credman.cmwallet.pnv.PnvTokenRegistry.Companion.VCT_GET_PHONE_NUMBER
2427
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
2532
import kotlinx.serialization.json.buildJsonObject
2633
import kotlinx.serialization.json.put
2734
import org.json.JSONArray
2835
import org.json.JSONObject
2936
import java.io.ByteArrayOutputStream
3037
import java.nio.ByteBuffer
3138
import java.nio.ByteOrder
39+
import java.security.KeyPair
40+
import java.security.KeyPairGenerator
41+
import java.security.KeyStore
42+
import java.security.MessageDigest
3243
import java.security.interfaces.ECPrivateKey
44+
import java.time.Instant
45+
import java.util.Enumeration
3346

3447
/**
3548
* A phone number verification entry to be registered with the Credential Manager.
@@ -175,6 +188,7 @@ data class PnvTokenRegistry(
175188
registryCredentials.put(PNV_CRED_FORMAT, sdJwtCredentials)
176189
val registryJson = JSONObject()
177190
registryJson.put(CREDENTIALS, registryCredentials)
191+
Log.d(TAG, "Phone Number to be registered:\n$registryJson")
178192
out.write(registryJson.toString().toByteArray())
179193
return out.toByteArray()
180194
}
@@ -200,6 +214,38 @@ private class SdJwtRegistryItem(
200214
val displayData: ItemDisplayData,
201215
)
202216

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+
203249
fun maybeHandlePnv(
204250
requestJson: String,
205251
providerIdx: Int,
@@ -281,24 +327,47 @@ fun maybeHandlePnv(
281327
}
282328
val encryptedTempTokenJwe = jweSerialization(aggregatorEncKey, tempTokenJson.toString())
283329

284-
val tmpKey =
330+
val tmpDeviceTelModuleKey =
285331
"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(
289355
header = buildJsonObject {
356+
put("typ", "kb+jwt") // MUST be kb+jwt
290357
put("alg", "ES256")
291358
},
292359
payload = buildJsonObject {
360+
put("iat", Instant.now().epochSecond)
361+
put("aud", origin)
293362
put("nonce", openId4VPRequest.nonce)
294-
put("origin", origin)
295363
put("encrypted_credential", encryptedTempTokenJwe)
364+
put("sd_hash", digest)
296365
},
297-
privateKey = privateKey
366+
privateKey = deviceKp.private
298367
)
299368

300369
// We don't use selective disclosure, so the sd-jwt is simply jwt + "~"
301-
val tempTokenDcSdJwt = "${tempTokenDcJwt}~"
370+
val tempTokenDcSdJwt = "${deviceTelModuleJwt}~${kbJwt}"
302371

303372
val vpToken = JSONObject().apply {
304373
put(dcqlCredId, tempTokenDcSdJwt)

0 commit comments

Comments
 (0)