From 1fcbeb0865d96ef037cdeaad18c558912ddf65b0 Mon Sep 17 00:00:00 2001 From: septs Date: Tue, 25 Feb 2025 10:53:19 +0800 Subject: [PATCH] feat: detect use product --- .../im/angry/openeuicc/core/EuiccChannel.kt | 4 ++ .../angry/openeuicc/core/EuiccChannelImpl.kt | 4 ++ .../openeuicc/core/EuiccChannelWrapper.kt | 22 +-------- .../openeuicc/core/OmapiApduInterface.kt | 6 +++ .../openeuicc/core/usb/UsbApduInterface.kt | 7 +++ .../angry/openeuicc/ui/EuiccInfoActivity.kt | 25 ++++++++-- .../im/angry/openeuicc/util/StringUtils.kt | 9 ++-- .../im/angry/openeuicc/vendored/estkme.kt | 46 +++++++++++++++++++ .../java/im/angry/openeuicc/vendored/nine.kt | 22 +++++++++ app-common/src/main/res/values/strings.xml | 4 ++ .../core/TelephonyManagerApduInterface.kt | 28 +++++------ .../net/typeblog/lpac_jni/ApduInterface.kt | 3 +- .../java/net/typeblog/lpac_jni/EuiccInfo2.kt | 33 +++++++++---- .../impl/LocalProfileAssistantImpl.kt | 19 ++++---- 14 files changed, 171 insertions(+), 61 deletions(-) create mode 100644 app-common/src/main/java/im/angry/openeuicc/vendored/estkme.kt create mode 100644 app-common/src/main/java/im/angry/openeuicc/vendored/nine.kt diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt index 5f399ea..cf8b6d0 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt @@ -1,6 +1,8 @@ package im.angry.openeuicc.core import im.angry.openeuicc.util.* +import im.angry.openeuicc.vendored.ESTKmeInfo +import net.typeblog.lpac_jni.ApduInterface import net.typeblog.lpac_jni.LocalProfileAssistant interface EuiccChannel { @@ -28,5 +30,7 @@ interface EuiccChannel { */ val intrinsicChannelName: String? + fun forkApduInterface(): ApduInterface + fun close() } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt index 3da829a..f49ac6f 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt @@ -1,6 +1,8 @@ package im.angry.openeuicc.core import im.angry.openeuicc.util.* +import im.angry.openeuicc.vendored.ESTKmeInfo +import im.angry.openeuicc.vendored.getESTKmeInfo import kotlinx.coroutines.flow.Flow import net.typeblog.lpac_jni.ApduInterface import net.typeblog.lpac_jni.LocalProfileAssistant @@ -34,6 +36,8 @@ class EuiccChannelImpl( override val atr: ByteArray? get() = (apduInterface as? ApduInterfaceAtrProvider)?.atr + override fun forkApduInterface() = apduInterface.clone() + override val valid: Boolean get() = lpa.valid diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt index 4204e82..1a108e5 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt @@ -1,9 +1,8 @@ package im.angry.openeuicc.core -import im.angry.openeuicc.util.* import net.typeblog.lpac_jni.LocalProfileAssistant -class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel { +class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel by orig { private var _inner: EuiccChannel? = orig private val channel: EuiccChannel @@ -15,28 +14,11 @@ class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel { return _inner!! } - override val type: String - get() = channel.type - override val port: UiccPortInfoCompat - get() = channel.port - override val slotId: Int - get() = channel.slotId - override val logicalSlotId: Int - get() = channel.logicalSlotId - override val portId: Int - get() = channel.portId private val lpaDelegate = lazy { LocalProfileAssistantWrapper(channel.lpa) } - override val lpa: LocalProfileAssistant by lpaDelegate - override val valid: Boolean - get() = channel.valid - override val intrinsicChannelName: String? - get() = channel.intrinsicChannelName - override val atr: ByteArray? - get() = channel.atr - override fun close() = channel.close() + override val lpa: LocalProfileAssistant by lpaDelegate fun invalidateWrapper() { _inner = null diff --git a/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt b/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt index c70669d..7fd7188 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt @@ -83,4 +83,10 @@ class OmapiApduInterface( throw e } } + + override fun clone() = OmapiApduInterface( + service, + port, + verboseLoggingFlow, + ) } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt b/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt index 624ef89..8bb6470 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt @@ -169,4 +169,11 @@ class UsbApduInterface( return resp } + + override fun clone() = UsbApduInterface( + conn, + bulkIn, + bulkOut, + verboseLoggingFlow, + ) } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt index e88ad01..a2ac085 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt @@ -23,6 +23,9 @@ import im.angry.openeuicc.common.R import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.util.* +import im.angry.openeuicc.vendored.ESTKmeInfo +import im.angry.openeuicc.vendored.getESTKmeInfo +import im.angry.openeuicc.vendored.getNineVersion import kotlinx.coroutines.launch import net.typeblog.lpac_jni.impl.PKID_GSMA_LIVE_CI import net.typeblog.lpac_jni.impl.PKID_GSMA_TEST_CI @@ -106,6 +109,15 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { formatByBoolean(channel.port.card.isRemovable, YES_NO) ) ) + getESTKmeInfo(channel.forkApduInterface())?.let { + add(Item(R.string.euicc_info_sku, it.skuName)) + add(Item(R.string.euicc_info_sn, it.serialNumber)) + add(Item(R.string.euicc_info_bl_ver, it.bootloaderVersion)) + add(Item(R.string.euicc_info_fw_ver, it.firmwareVersion)) + } + getNineVersion(channel.lpa.eID, channel.lpa.euiccInfo2)?.let { + add(Item(R.string.euicc_info_sku, "9eSIM $it")) + } add( Item( R.string.euicc_info_eid, @@ -114,10 +126,15 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { ) ) channel.lpa.euiccInfo2.let { info -> - add(Item(R.string.euicc_info_sgp22_version, info?.sgp22Version)) - add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion)) - add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion)) - add(Item(R.string.euicc_info_pp_version, info?.ppVersion)) + add(Item(R.string.euicc_info_sgp22_version, info?.sgp22Version.toString())) + add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion.toString())) + add( + Item( + R.string.euicc_info_globalplatform_version, + info?.globalPlatformVersion.toString() + ) + ) + add(Item(R.string.euicc_info_pp_version, info?.ppVersion.toString())) add(Item(R.string.euicc_info_sas_accreditation_number, info?.sasAccreditationNumber)) add(Item(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace))) } diff --git a/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt index 8d72462..f252d76 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt @@ -12,13 +12,10 @@ fun String.decodeHex(): ByteArray { return out } -fun ByteArray.encodeHex(): String { - val sb = StringBuilder() - val length = size - for (i in 0 until length) { - sb.append(String.format("%02X", this[i])) +fun ByteArray.encodeHex(): String = buildString { + for (element in this@encodeHex) { + append(String.format("%02X", element)) } - return sb.toString() } fun formatFreeSpace(size: Int): String = diff --git a/app-common/src/main/java/im/angry/openeuicc/vendored/estkme.kt b/app-common/src/main/java/im/angry/openeuicc/vendored/estkme.kt new file mode 100644 index 0000000..8c06706 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/vendored/estkme.kt @@ -0,0 +1,46 @@ +package im.angry.openeuicc.vendored + +import android.util.Log +import net.typeblog.lpac_jni.ApduInterface + +data class ESTKmeInfo( + val serialNumber: String?, + val bootloaderVersion: String?, + val firmwareVersion: String?, + val skuName: String?, +) + +fun getESTKmeInfo(iface: ApduInterface): ESTKmeInfo? { + // @formatter:off + val aid = byteArrayOf( + 0xA0.toByte(), 0x65.toByte(), 0x73.toByte(), 0x74.toByte(), 0x6B.toByte(), 0x6D.toByte(), + 0x65.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), + 0xFF.toByte(), 0x6D.toByte(), 0x67.toByte(), 0x74.toByte(), + ) + // @formatter:on + return try { + iface.connect() + iface.logicalChannelOpen(aid) + fun transmit(p1: Byte) = + decode(iface.transmit(byteArrayOf(0x00, 0x00, p1, 0x00, 0x00)).clone()) + return ESTKmeInfo( + transmit(0x00), // serial number + transmit(0x01), // bootloader version + transmit(0x02), // firmware version + transmit(0x03), // sku name + ) + } catch (e: Exception) { + Log.d("ESTKme", "Failed to get ESTKme info", e) + null + } finally { + iface.disconnect() + } +} + +private fun isSuccessResponse(b: ByteArray) = + b.size >= 2 && b[b.size - 2] == 0x90.toByte() && b[b.size - 1] == 0x00.toByte() + +private fun decode(b: ByteArray): String? { + if (!isSuccessResponse(b)) return null + return b.dropLast(2).toByteArray().decodeToString() +} diff --git a/app-common/src/main/java/im/angry/openeuicc/vendored/nine.kt b/app-common/src/main/java/im/angry/openeuicc/vendored/nine.kt new file mode 100644 index 0000000..e0d2679 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/vendored/nine.kt @@ -0,0 +1,22 @@ +package im.angry.openeuicc.vendored + +import net.typeblog.lpac_jni.EuiccInfo2 +import net.typeblog.lpac_jni.Version + +private val prefix = Regex("^89044045(84|21)67274948") + +fun getNineVersion(eid: String, euiccInfo2: EuiccInfo2?): String? { + if (euiccInfo2 == null) return null + if (!prefix.matches(eid)) return null + val version = euiccInfo2.euiccFirmwareVersion + return when { + // @formatter:off + version >= Version(36, 7, 2) -> "v2" + version >= Version(36, 9, 3) -> "v2.1" + version >= Version(36, 17, 4) -> "v2s" + version >= Version(36, 17, 39) -> "v3 (beta)" + version >= Version(36, 18, 5) -> "v3" + // @formatter:on + else -> null + } +} diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 71e2418..5a8063c 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -125,6 +125,10 @@ eUICC Info (%s) Access Mode Removable + Product Name + Serial Number + Firmware Number + Bootloader Number EID SGP.22 Version eUICC OS Version diff --git a/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt b/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt index 6b09368..265bd6a 100644 --- a/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt +++ b/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt @@ -59,24 +59,26 @@ class TelephonyManagerApduInterface( } val cla = tx[0].toUByte().toInt() - val instruction = tx[1].toUByte().toInt() + val ins = tx[1].toUByte().toInt() val p1 = tx[2].toUByte().toInt() val p2 = tx[3].toUByte().toInt() val p3 = tx[4].toUByte().toInt() val p4 = tx.drop(5).toByteArray().encodeHex() - return tm.iccTransmitApduLogicalChannelByPortCompat(port.card.physicalSlotIndex, port.portIndex, lastChannel, - cla, - instruction, - p1, - p2, - p3, - p4 - ).also { - if (runBlocking { verboseLoggingFlow.first() }) { - Log.d(TAG, "TelephonyManager APDU response: $it") - } - }?.decodeHex() ?: byteArrayOf() + // @formatter:off + val result = tm.iccTransmitApduLogicalChannelByPortCompat( + port.card.physicalSlotIndex, port.portIndex, lastChannel, + cla, ins, p1, p2, p3, p4 + ) + // @formatter:on + if (runBlocking { verboseLoggingFlow.first() }) + Log.d(TAG, "TelephonyManager APDU response: $result") + return result?.decodeHex() ?: byteArrayOf() } + override fun clone() = TelephonyManagerApduInterface( + port, + tm, + verboseLoggingFlow + ) } \ No newline at end of file diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt index dfa92df..8075779 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt @@ -3,12 +3,13 @@ package net.typeblog.lpac_jni /* * Should reflect euicc_apdu_interface in lpac/euicc/interface.h */ -interface ApduInterface { +interface ApduInterface : Cloneable { fun connect() fun disconnect() fun logicalChannelOpen(aid: ByteArray): Int fun logicalChannelClose(handle: Int) fun transmit(tx: ByteArray): ByteArray + public override fun clone(): ApduInterface /** * Is this APDU connection still valid? diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt index 6c73051..2a9b3e5 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt @@ -2,14 +2,31 @@ package net.typeblog.lpac_jni /* Corresponds to EuiccInfo2 in SGP.22 */ data class EuiccInfo2( - val sgp22Version: String, - val profileVersion: String, - val euiccFirmwareVersion: String, - val globalPlatformVersion: String, + val sgp22Version: Version, + val profileVersion: Version, + val euiccFirmwareVersion: Version, + val globalPlatformVersion: Version, val sasAccreditationNumber: String, - val ppVersion: String, + val ppVersion: Version, val freeNvram: Int, val freeRam: Int, - val euiccCiPKIdListForSigning: Array, - val euiccCiPKIdListForVerification: Array, -) \ No newline at end of file + val euiccCiPKIdListForSigning: Set, + val euiccCiPKIdListForVerification: Set, +) + +data class Version( + val major: Int, + val minor: Int, + val patch: Int, +) { + constructor(version: String) : this(version.split('.').map(String::toInt)) + constructor(parts: List) : this(parts[0], parts[1], parts[2]) + + operator fun compareTo(other: Version): Int { + if (major != other.major) return major - other.major + if (minor != other.minor) return minor - other.minor + return patch - other.patch + } + + override fun toString() = "$major.$minor.$patch" +} diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt index 8aafe94..827a3b5 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt @@ -10,6 +10,7 @@ import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.LocalProfileNotification import net.typeblog.lpac_jni.ProfileDownloadCallback +import net.typeblog.lpac_jni.Version class LocalProfileAssistantImpl( isdrAid: ByteArray, @@ -84,8 +85,8 @@ class LocalProfileAssistantImpl( throw IllegalArgumentException("Failed to initialize LPA") } - val pkids = euiccInfo2?.euiccCiPKIdListForVerification ?: arrayOf() - httpInterface.usePublicKeyIds(pkids) + val pkids = euiccInfo2?.euiccCiPKIdListForVerification ?: setOf() + httpInterface.usePublicKeyIds(pkids.toTypedArray()) } override fun setEs10xMss(mss: Byte) { @@ -172,16 +173,16 @@ class LocalProfileAssistantImpl( } val ret = EuiccInfo2( - LpacJni.euiccInfo2GetSGP22Version(cInfo), - LpacJni.euiccInfo2GetProfileVersion(cInfo), - LpacJni.euiccInfo2GetEuiccFirmwareVersion(cInfo), - LpacJni.euiccInfo2GetGlobalPlatformVersion(cInfo), + Version(LpacJni.euiccInfo2GetSGP22Version(cInfo)), + Version(LpacJni.euiccInfo2GetProfileVersion(cInfo)), + Version(LpacJni.euiccInfo2GetEuiccFirmwareVersion(cInfo)), + Version(LpacJni.euiccInfo2GetGlobalPlatformVersion(cInfo)), LpacJni.euiccInfo2GetSasAcreditationNumber(cInfo), - LpacJni.euiccInfo2GetPpVersion(cInfo), + Version(LpacJni.euiccInfo2GetPpVersion(cInfo)), LpacJni.euiccInfo2GetFreeNonVolatileMemory(cInfo).toInt(), LpacJni.euiccInfo2GetFreeVolatileMemory(cInfo).toInt(), - euiccCiPKIdListForSigning.toTypedArray(), - euiccCiPKIdListForVerification.toTypedArray() + euiccCiPKIdListForSigning.toTypedArray().toSet(), + euiccCiPKIdListForVerification.toTypedArray().toSet(), ) LpacJni.euiccInfo2Free(cInfo)