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