feat: detect use product
This commit is contained in:
parent
03bfdf373c
commit
1fcbeb0865
14 changed files with 171 additions and 61 deletions
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -83,4 +83,10 @@ class OmapiApduInterface(
|
|||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
override fun clone() = OmapiApduInterface(
|
||||
service,
|
||||
port,
|
||||
verboseLoggingFlow,
|
||||
)
|
||||
}
|
|
@ -169,4 +169,11 @@ class UsbApduInterface(
|
|||
|
||||
return resp
|
||||
}
|
||||
|
||||
override fun clone() = UsbApduInterface(
|
||||
conn,
|
||||
bulkIn,
|
||||
bulkOut,
|
||||
verboseLoggingFlow,
|
||||
)
|
||||
}
|
|
@ -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)))
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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()
|
||||
}
|
22
app-common/src/main/java/im/angry/openeuicc/vendored/nine.kt
Normal file
22
app-common/src/main/java/im/angry/openeuicc/vendored/nine.kt
Normal file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -125,6 +125,10 @@
|
|||
<string name="euicc_info_activity_title">eUICC Info (%s)</string>
|
||||
<string name="euicc_info_access_mode">Access Mode</string>
|
||||
<string name="euicc_info_removable">Removable</string>
|
||||
<string name="euicc_info_sku">Product Name</string>
|
||||
<string name="euicc_info_sn">Serial Number</string>
|
||||
<string name="euicc_info_fw_ver">Firmware Number</string>
|
||||
<string name="euicc_info_bl_ver">Bootloader Number</string>
|
||||
<string name="euicc_info_eid" translatable="false">EID</string>
|
||||
<string name="euicc_info_sgp22_version">SGP.22 Version</string>
|
||||
<string name="euicc_info_firmware_version">eUICC OS Version</string>
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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?
|
||||
|
|
|
@ -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<String>,
|
||||
val euiccCiPKIdListForVerification: Array<String>,
|
||||
)
|
||||
val euiccCiPKIdListForSigning: Set<String>,
|
||||
val euiccCiPKIdListForVerification: Set<String>,
|
||||
)
|
||||
|
||||
data class Version(
|
||||
val major: Int,
|
||||
val minor: Int,
|
||||
val patch: Int,
|
||||
) {
|
||||
constructor(version: String) : this(version.split('.').map(String::toInt))
|
||||
constructor(parts: List<Int>) : 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"
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue