1+ package com.credman.cmwallet.pnv
2+
3+ import org.json.JSONArray
4+ import org.json.JSONObject
5+ import java.io.ByteArrayOutputStream
6+ import java.nio.ByteBuffer
7+ import java.nio.ByteOrder
8+
9+ /* *
10+ * A phone number verification entry to be registered with the Credential Manager.
11+ *
12+ * @param tokenId The Securely generated ID that will be used to identify a user selection
13+ * @param title The display title for this TS43 token entry. Ideally this should be an obfuscated
14+ * phone number, such as ***-***-1234, or some other information that allows the user to
15+ * disambiguate this entry from others, especially in the multi-sim use case.
16+ * @param subscriptionId The subscription ID of the SIM card that this token is associated with.
17+ * @param carrierId The carrier ID of the SIM card that this token is associated with.
18+ * @param useCases The set of use cases that this token is. Allowed values are:
19+ * [USE_CASE_VERIFY_PHONE_NUMBER], [USE_CASE_GET_PHONE_NUMBER], [USE_CASE_GET_SUBSCRIBER_INFO]
20+ */
21+ data class PnvTokenRegistry (
22+ val tokenId : String ,
23+ val vct : String ,
24+ val title : String ,
25+ val providerConsent : String? ,
26+ val subscriptionHint : Int? ,
27+ val carrierHint : String? ,
28+ val phoneNumberHint : String?
29+ ) {
30+ /* * Converts this TS43 entry to the more generic SD-JWT registry item(s). */
31+ private fun toSdJwtRegistryItems (): SdJwtRegistryItem {
32+ return SdJwtRegistryItem (
33+ id = tokenId,
34+ vct = vct,
35+ claims =
36+ listOf (
37+ RegistryClaim (" subscription_hint" , null , subscriptionHint),
38+ RegistryClaim (" carrier_hint" , null , carrierHint),
39+ RegistryClaim (" phone_number_hint" , null , phoneNumberHint),
40+ ),
41+ displayData = ItemDisplayData (title = title, subtitle = null , description = providerConsent),
42+ )
43+ }
44+
45+ companion object {
46+ const val VCT_GET_PHONE_NUMBER = " number-verification/device-phone-number/ts43"
47+ const val VCT_VERIFY_PHONE_NUMBER = " number-verification/verify/ts43"
48+ const val PNV_CRED_FORMAT = " dc+sd-jwt-pnv"
49+
50+ internal const val CREDENTIALS = " credentials"
51+ internal const val ID = " id"
52+ internal const val TITLE = " title"
53+ internal const val SUBTITLE = " subtitle"
54+ internal const val DISCLAIMER = " disclaimer"
55+ internal const val PATHS = " paths"
56+ internal const val VALUE = " value"
57+ internal const val DISPLAY = " display"
58+
59+ val TEST_PNV_1_GET_PHONE_NUMBER = PnvTokenRegistry (
60+ tokenId = " pnv_1" ,
61+ vct = VCT_GET_PHONE_NUMBER ,
62+ title = " Phone Number" ,
63+ providerConsent = " CMWallet will enable your carrier {carrier name} to share your phone number iwth {app/domain name}" ,
64+ subscriptionHint = 1 ,
65+ carrierHint = " 310250" ,
66+ phoneNumberHint = " +16502154321"
67+ )
68+ val TEST_PNV_1_VERIFY_PHONE_NUMBER = TEST_PNV_1_GET_PHONE_NUMBER .copy(
69+ vct = VCT_VERIFY_PHONE_NUMBER ,
70+ )
71+ val TEST_PNV_2_GET_PHONE_NUMBER = PnvTokenRegistry (
72+ tokenId = " pnv_2" ,
73+ vct = VCT_GET_PHONE_NUMBER ,
74+ title = " Phone Number" ,
75+ providerConsent = " CMWallet will enable your carrier MOCK-CARRIER-2 to share your phone number" ,
76+ subscriptionHint = 2 ,
77+ carrierHint = " 380250" ,
78+ phoneNumberHint = " +16502157890"
79+ )
80+
81+ fun buildRegistryDatabase (items : List <PnvTokenRegistry >): ByteArray {
82+ val out = ByteArrayOutputStream ()
83+
84+ // We don't support icon for phone number tokens, yet
85+ // Write the offset to the json
86+ val jsonOffset = 4
87+ val buffer = ByteBuffer .allocate(4 )
88+ buffer.order(ByteOrder .LITTLE_ENDIAN )
89+ buffer.putInt(jsonOffset)
90+ out .write(buffer.array())
91+
92+ val sdJwtCredentials = JSONObject ()
93+ for (item in items.map { it.toSdJwtRegistryItems() }) {
94+ val credJson = JSONObject ()
95+ credJson.put(ID , item.id)
96+ credJson.put(TITLE , item.displayData.title)
97+ credJson.putOpt(SUBTITLE , item.displayData.subtitle)
98+ credJson.putOpt(DISCLAIMER , item.displayData.description)
99+ val paths = JSONObject ()
100+ for (claim in item.claims) {
101+ paths.put(claim.path, JSONObject ().putOpt(DISPLAY , claim.display).putOpt(VALUE , claim.value))
102+ }
103+
104+ credJson.put(PATHS , paths)
105+ val vctType = item.vct
106+ when (val current = sdJwtCredentials.opt(vctType) ? : JSONArray ()) {
107+ is JSONArray -> sdJwtCredentials.put(vctType, current.put(credJson))
108+ else -> throw IllegalStateException (" Unexpected type ${current::class .java} " )
109+ }
110+ }
111+ val registryCredentials = JSONObject ()
112+ registryCredentials.put(PNV_CRED_FORMAT , sdJwtCredentials)
113+ val registryJson = JSONObject ()
114+ registryJson.put(CREDENTIALS , registryCredentials)
115+ out .write(registryJson.toString().toByteArray())
116+ return out .toByteArray()
117+ }
118+ }
119+ }
120+
121+ private class RegistryClaim (
122+ val path : String , // Single depth only
123+ val display : String? ,
124+ val value : Any? ,
125+ )
126+
127+ private class ItemDisplayData (val title : String , val subtitle : String? , val description : String? )
128+
129+ private class SdJwtRegistryItem (
130+ val id : String ,
131+ val vct : String ,
132+ val claims : List <RegistryClaim >,
133+ val displayData : ItemDisplayData ,
134+ )
0 commit comments