Skip to content

Commit 1d67fa5

Browse files
septsPeterCxy
authored andcommitted
feat: detect used product (#147)
Reviewed-on: https://gitea.angry.im/PeterCxy/OpenEUICC/pulls/147 Co-authored-by: septs <github@septs.pw> Co-committed-by: septs <github@septs.pw>
1 parent c8ecdee commit 1d67fa5

File tree

8 files changed

+143
-57
lines changed

8 files changed

+143
-57
lines changed

.idea/.gitignore

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt

+19-24
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import im.angry.openeuicc.common.R
2323
import im.angry.openeuicc.core.EuiccChannel
2424
import im.angry.openeuicc.core.EuiccChannelManager
2525
import im.angry.openeuicc.util.*
26+
import im.angry.openeuicc.vendored.getESTKmeInfo
27+
import im.angry.openeuicc.vendored.getSIMLinkVersion
2628
import kotlinx.coroutines.launch
2729
import net.typeblog.lpac_jni.impl.PKID_GSMA_LIVE_CI
2830
import net.typeblog.lpac_jni.impl.PKID_GSMA_TEST_CI
@@ -100,24 +102,22 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
100102

101103
private fun buildEuiccInfoItems(channel: EuiccChannel) = buildList {
102104
add(Item(R.string.euicc_info_access_mode, channel.type))
103-
add(
104-
Item(
105-
R.string.euicc_info_removable,
106-
formatByBoolean(channel.port.card.isRemovable, YES_NO)
107-
)
108-
)
109-
add(
110-
Item(
111-
R.string.euicc_info_eid,
112-
channel.lpa.eID,
113-
copiedToastResId = R.string.toast_eid_copied
114-
)
115-
)
105+
add(Item(R.string.euicc_info_removable, formatByBoolean(channel.port.card.isRemovable, YES_NO)))
106+
add(Item(R.string.euicc_info_eid, channel.lpa.eID, copiedToastResId = R.string.toast_eid_copied))
107+
getESTKmeInfo(channel.apduInterface)?.let {
108+
add(Item(R.string.euicc_info_sku, it.skuName))
109+
add(Item(R.string.euicc_info_sn, it.serialNumber, copiedToastResId = R.string.toast_sn_copied))
110+
add(Item(R.string.euicc_info_bl_ver, it.bootloaderVersion))
111+
add(Item(R.string.euicc_info_fw_ver, it.firmwareVersion))
112+
}
113+
getSIMLinkVersion(channel.lpa.eID, channel.lpa.euiccInfo2?.euiccFirmwareVersion)?.let {
114+
add(Item(R.string.euicc_info_sku, "9eSIM $it"))
115+
}
116116
channel.lpa.euiccInfo2.let { info ->
117-
add(Item(R.string.euicc_info_sgp22_version, info?.sgp22Version))
118-
add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion))
119-
add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion))
120-
add(Item(R.string.euicc_info_pp_version, info?.ppVersion))
117+
add(Item(R.string.euicc_info_sgp22_version, info?.sgp22Version.toString()))
118+
add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion.toString()))
119+
add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion.toString()))
120+
add(Item(R.string.euicc_info_pp_version, info?.ppVersion.toString()))
121121
add(Item(R.string.euicc_info_sas_accreditation_number, info?.sasAccreditationNumber))
122122
add(Item(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace)))
123123
}
@@ -134,13 +134,8 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
134134
}
135135
add(Item(R.string.euicc_info_ci_type, getString(resId)))
136136
}
137-
add(
138-
Item(
139-
R.string.euicc_info_atr,
140-
channel.atr?.encodeHex() ?: getString(R.string.information_unavailable),
141-
copiedToastResId = R.string.toast_atr_copied,
142-
)
143-
)
137+
val atr = channel.atr?.encodeHex() ?: getString(R.string.information_unavailable)
138+
add(Item(R.string.euicc_info_atr, atr, copiedToastResId = R.string.toast_atr_copied))
144139
}
145140

146141
private fun formatByBoolean(b: Boolean, res: Pair<Int, Int>): String =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package im.angry.openeuicc.vendored
2+
3+
import android.util.Log
4+
import im.angry.openeuicc.core.ApduInterfaceAtrProvider
5+
import im.angry.openeuicc.util.TAG
6+
import im.angry.openeuicc.util.decodeHex
7+
import net.typeblog.lpac_jni.ApduInterface
8+
9+
data class ESTKmeInfo(
10+
val serialNumber: String?,
11+
val bootloaderVersion: String?,
12+
val firmwareVersion: String?,
13+
val skuName: String?,
14+
)
15+
16+
fun isESTKmeATR(iface: ApduInterface): Boolean {
17+
if (iface !is ApduInterfaceAtrProvider) return false
18+
val atr = iface.atr ?: return false
19+
val fpr = "estk.me".encodeToByteArray()
20+
for (index in atr.indices) {
21+
if (atr.size - index < fpr.size) break
22+
if (atr.sliceArray(index until index + fpr.size).contentEquals(fpr)) return true
23+
}
24+
return false
25+
}
26+
27+
fun getESTKmeInfo(iface: ApduInterface): ESTKmeInfo? {
28+
if (!isESTKmeATR(iface)) return null
29+
fun decode(b: ByteArray): String? {
30+
if (b.size < 2) return null
31+
if (b[b.size - 2] != 0x90.toByte() || b[b.size - 1] != 0x00.toByte()) return null
32+
return b.sliceArray(0 until b.size - 2).decodeToString()
33+
}
34+
return try {
35+
iface.withLogicalChannel("A06573746B6D65FFFFFFFFFFFF6D6774".decodeHex()) { transmit ->
36+
fun invoke(p1: Byte) = decode(transmit(byteArrayOf(0x00, 0x00, p1, 0x00, 0x00)))
37+
ESTKmeInfo(
38+
invoke(0x00), // serial number
39+
invoke(0x01), // bootloader version
40+
invoke(0x02), // firmware version
41+
invoke(0x03), // sku name
42+
)
43+
}
44+
} catch (e: Exception) {
45+
Log.d(TAG, "Failed to get ESTKmeInfo", e)
46+
null
47+
}
48+
}
49+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package im.angry.openeuicc.vendored
2+
3+
import net.typeblog.lpac_jni.Version
4+
5+
private val prefix = Regex("^89044045(84|21)67274948") // SIMLink EID prefix
6+
7+
fun getSIMLinkVersion(eid: String, version: Version?): String? {
8+
if (version == null || prefix.find(eid, 0) == null) return null
9+
return when {
10+
// @formatter:off
11+
version >= Version(37, 1, 41) -> "v3.1 (beta 1)"
12+
version >= Version(36, 18, 5) -> "v3 (final)"
13+
version >= Version(36, 17, 39) -> "v3 (beta)"
14+
version >= Version(36, 17, 4) -> "v2s"
15+
version >= Version(36, 9, 3) -> "v2.1"
16+
version >= Version(36, 7, 2) -> "v2"
17+
// @formatter:on
18+
else -> null
19+
}
20+
}

app-common/src/main/res/values/strings.xml

+5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<string name="toast_profile_enable_failed">Cannot switch to new eSIM profile.</string>
3232
<string name="toast_profile_delete_confirm_text_mismatched">Confirmation string mismatch</string>
3333
<string name="toast_iccid_copied">ICCID copied to clipboard</string>
34+
<string name="toast_sn_copied">Serial Number copied to clipboard</string>
3435
<string name="toast_eid_copied">EID copied to clipboard</string>
3536
<string name="toast_atr_copied">ATR copied to clipboard</string>
3637

@@ -125,6 +126,10 @@
125126
<string name="euicc_info_activity_title">eUICC Info (%s)</string>
126127
<string name="euicc_info_access_mode">Access Mode</string>
127128
<string name="euicc_info_removable">Removable</string>
129+
<string name="euicc_info_sku">Product Name</string>
130+
<string name="euicc_info_sn">Product Serial Number</string>
131+
<string name="euicc_info_bl_ver">Product Bootloader Version</string>
132+
<string name="euicc_info_fw_ver">Product Firmware Version</string>
128133
<string name="euicc_info_eid" translatable="false">EID</string>
129134
<string name="euicc_info_sgp22_version">SGP.22 Version</string>
130135
<string name="euicc_info_firmware_version">eUICC OS Version</string>

libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt

+25-8
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,31 @@ package net.typeblog.lpac_jni
22

33
/* Corresponds to EuiccInfo2 in SGP.22 */
44
data class EuiccInfo2(
5-
val sgp22Version: String,
6-
val profileVersion: String,
7-
val euiccFirmwareVersion: String,
8-
val globalPlatformVersion: String,
5+
val sgp22Version: Version,
6+
val profileVersion: Version,
7+
val euiccFirmwareVersion: Version,
8+
val globalPlatformVersion: Version,
99
val sasAccreditationNumber: String,
10-
val ppVersion: String,
10+
val ppVersion: Version,
1111
val freeNvram: Int,
1212
val freeRam: Int,
13-
val euiccCiPKIdListForSigning: Array<String>,
14-
val euiccCiPKIdListForVerification: Array<String>,
15-
)
13+
val euiccCiPKIdListForSigning: Set<String>,
14+
val euiccCiPKIdListForVerification: Set<String>,
15+
)
16+
17+
data class Version(
18+
val major: Int,
19+
val minor: Int,
20+
val patch: Int,
21+
) {
22+
constructor(version: String) : this(version.split('.').map(String::toInt))
23+
private constructor(parts: List<Int>) : this(parts[0], parts[1], parts[2])
24+
25+
operator fun compareTo(other: Version): Int {
26+
if (major != other.major) return major - other.major
27+
if (minor != other.minor) return minor - other.minor
28+
return patch - other.patch
29+
}
30+
31+
override fun toString() = "$major.$minor.$patch"
32+
}

libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt

+22-23
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import net.typeblog.lpac_jni.LocalProfileAssistant
1010
import net.typeblog.lpac_jni.LocalProfileInfo
1111
import net.typeblog.lpac_jni.LocalProfileNotification
1212
import net.typeblog.lpac_jni.ProfileDownloadCallback
13+
import net.typeblog.lpac_jni.Version
1314

1415
class LocalProfileAssistantImpl(
1516
isdrAid: ByteArray,
@@ -84,8 +85,8 @@ class LocalProfileAssistantImpl(
8485
throw IllegalArgumentException("Failed to initialize LPA")
8586
}
8687

87-
val pkids = euiccInfo2?.euiccCiPKIdListForVerification ?: arrayOf()
88-
httpInterface.usePublicKeyIds(pkids)
88+
val pkids = euiccInfo2?.euiccCiPKIdListForVerification ?: setOf()
89+
httpInterface.usePublicKeyIds(pkids.toTypedArray())
8990
}
9091

9192
override fun setEs10xMss(mss: Byte) {
@@ -157,31 +158,29 @@ class LocalProfileAssistantImpl(
157158
val cInfo = LpacJni.es10cexGetEuiccInfo2(contextHandle)
158159
if (cInfo == 0L) return null
159160

160-
val euiccCiPKIdListForSigning = mutableListOf<String>()
161-
var curr = LpacJni.euiccInfo2GetEuiccCiPKIdListForSigning(cInfo)
162-
while (curr != 0L) {
163-
euiccCiPKIdListForSigning.add(LpacJni.stringDeref(curr))
164-
curr = LpacJni.stringArrNext(curr)
165-
}
166-
167-
val euiccCiPKIdListForVerification = mutableListOf<String>()
168-
curr = LpacJni.euiccInfo2GetEuiccCiPKIdListForVerification(cInfo)
169-
while (curr != 0L) {
170-
euiccCiPKIdListForVerification.add(LpacJni.stringDeref(curr))
171-
curr = LpacJni.stringArrNext(curr)
172-
}
173-
174161
val ret = EuiccInfo2(
175-
LpacJni.euiccInfo2GetSGP22Version(cInfo),
176-
LpacJni.euiccInfo2GetProfileVersion(cInfo),
177-
LpacJni.euiccInfo2GetEuiccFirmwareVersion(cInfo),
178-
LpacJni.euiccInfo2GetGlobalPlatformVersion(cInfo),
162+
Version(LpacJni.euiccInfo2GetSGP22Version(cInfo)),
163+
Version(LpacJni.euiccInfo2GetProfileVersion(cInfo)),
164+
Version(LpacJni.euiccInfo2GetEuiccFirmwareVersion(cInfo)),
165+
Version(LpacJni.euiccInfo2GetGlobalPlatformVersion(cInfo)),
179166
LpacJni.euiccInfo2GetSasAcreditationNumber(cInfo),
180-
LpacJni.euiccInfo2GetPpVersion(cInfo),
167+
Version(LpacJni.euiccInfo2GetPpVersion(cInfo)),
181168
LpacJni.euiccInfo2GetFreeNonVolatileMemory(cInfo).toInt(),
182169
LpacJni.euiccInfo2GetFreeVolatileMemory(cInfo).toInt(),
183-
euiccCiPKIdListForSigning.toTypedArray(),
184-
euiccCiPKIdListForVerification.toTypedArray()
170+
buildSet {
171+
var cursor = LpacJni.euiccInfo2GetEuiccCiPKIdListForSigning(cInfo)
172+
while (cursor != 0L) {
173+
add(LpacJni.stringDeref(cursor))
174+
cursor = LpacJni.stringArrNext(cursor)
175+
}
176+
},
177+
buildSet {
178+
var cursor = LpacJni.euiccInfo2GetEuiccCiPKIdListForVerification(cInfo)
179+
while (cursor != 0L) {
180+
add(LpacJni.stringDeref(cursor))
181+
cursor = LpacJni.stringArrNext(cursor)
182+
}
183+
},
185184
)
186185

187186
LpacJni.euiccInfo2Free(cInfo)

libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const val DEFAULT_PKID_GSMA_RSP2_ROOT_CI1 = "81370f5125d0b1d408d4c3b232e6d25e795
1414

1515
// List of GSMA Live CIs
1616
// https://www.gsma.com/solutions-and-impact/technologies/esim/gsma-root-ci/
17-
val PKID_GSMA_LIVE_CI = arrayOf(
17+
val PKID_GSMA_LIVE_CI = setOf(
1818
// GSMA RSP2 Root CI1 (SGP.22 v2+v3, CA: DigiCert)
1919
// https://euicc-manual.osmocom.org/docs/pki/ci/files/81370f.txt
2020
DEFAULT_PKID_GSMA_RSP2_ROOT_CI1,
@@ -25,7 +25,7 @@ val PKID_GSMA_LIVE_CI = arrayOf(
2525

2626
// SGP.26 v3.0, 2023-12-01
2727
// https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2023/12/SGP.26-v3.0.pdf
28-
val PKID_GSMA_TEST_CI = arrayOf(
28+
val PKID_GSMA_TEST_CI = setOf(
2929
// Test CI (SGP.26, NIST P256)
3030
// https://euicc-manual.osmocom.org/docs/pki/ci/files/34eecf.txt
3131
"34eecf13156518d48d30bdf06853404d115f955d",

0 commit comments

Comments
 (0)