Compare commits
10 commits
ce6b78d045
...
9ddfd62030
Author | SHA1 | Date | |
---|---|---|---|
9ddfd62030 | |||
fd4023194a | |||
ba8a4b87cb | |||
1f9a9d5f22 | |||
c211709344 | |||
fd13867615 | |||
3913e2f5d4 | |||
f12f6ccaa2 | |||
30279aeac7 | |||
31a905436e |
10 changed files with 133 additions and 45 deletions
1
.idea/.gitignore
generated
vendored
1
.idea/.gitignore
generated
vendored
|
@ -9,5 +9,6 @@
|
||||||
/navEditor.xml
|
/navEditor.xml
|
||||||
/runConfigurations.xml
|
/runConfigurations.xml
|
||||||
/workspace.xml
|
/workspace.xml
|
||||||
|
/AndroidProjectSystem.xml
|
||||||
|
|
||||||
**/*.iml
|
**/*.iml
|
|
@ -7,6 +7,7 @@ import android.util.Log
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.single
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import net.typeblog.lpac_jni.ApduInterface
|
import net.typeblog.lpac_jni.ApduInterface
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,8 @@ import im.angry.openeuicc.common.R
|
||||||
import im.angry.openeuicc.core.EuiccChannel
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.core.EuiccChannelManager
|
import im.angry.openeuicc.core.EuiccChannelManager
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
|
import im.angry.openeuicc.vendored.getESTKmeInfo
|
||||||
|
import im.angry.openeuicc.vendored.getSIMLinkVersion
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.typeblog.lpac_jni.impl.PKID_GSMA_LIVE_CI
|
import net.typeblog.lpac_jni.impl.PKID_GSMA_LIVE_CI
|
||||||
import net.typeblog.lpac_jni.impl.PKID_GSMA_TEST_CI
|
import net.typeblog.lpac_jni.impl.PKID_GSMA_TEST_CI
|
||||||
|
@ -100,24 +102,22 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
|
|
||||||
private fun buildEuiccInfoItems(channel: EuiccChannel) = buildList {
|
private fun buildEuiccInfoItems(channel: EuiccChannel) = buildList {
|
||||||
add(Item(R.string.euicc_info_access_mode, channel.type))
|
add(Item(R.string.euicc_info_access_mode, channel.type))
|
||||||
add(
|
add(Item(R.string.euicc_info_removable, formatByBoolean(channel.port.card.isRemovable, YES_NO)))
|
||||||
Item(
|
add(Item(R.string.euicc_info_eid, channel.lpa.eID, copiedToastResId = R.string.toast_eid_copied))
|
||||||
R.string.euicc_info_removable,
|
getESTKmeInfo(channel.apduInterface)?.let {
|
||||||
formatByBoolean(channel.port.card.isRemovable, YES_NO)
|
add(Item(R.string.euicc_info_sku, it.skuName))
|
||||||
)
|
add(Item(R.string.euicc_info_sn, it.serialNumber, copiedToastResId = R.string.toast_sn_copied))
|
||||||
)
|
add(Item(R.string.euicc_info_bl_ver, it.bootloaderVersion))
|
||||||
add(
|
add(Item(R.string.euicc_info_fw_ver, it.firmwareVersion))
|
||||||
Item(
|
}
|
||||||
R.string.euicc_info_eid,
|
getSIMLinkVersion(channel.lpa.eID, channel.lpa.euiccInfo2?.euiccFirmwareVersion)?.let {
|
||||||
channel.lpa.eID,
|
add(Item(R.string.euicc_info_sku, "9eSIM $it"))
|
||||||
copiedToastResId = R.string.toast_eid_copied
|
}
|
||||||
)
|
|
||||||
)
|
|
||||||
channel.lpa.euiccInfo2.let { info ->
|
channel.lpa.euiccInfo2.let { info ->
|
||||||
add(Item(R.string.euicc_info_sgp22_version, info?.sgp22Version))
|
add(Item(R.string.euicc_info_sgp22_version, info?.sgp22Version.toString()))
|
||||||
add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion))
|
add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion.toString()))
|
||||||
add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion))
|
add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion.toString()))
|
||||||
add(Item(R.string.euicc_info_pp_version, info?.ppVersion))
|
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_sas_accreditation_number, info?.sasAccreditationNumber))
|
||||||
add(Item(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace)))
|
add(Item(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace)))
|
||||||
}
|
}
|
||||||
|
@ -134,13 +134,8 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
}
|
}
|
||||||
add(Item(R.string.euicc_info_ci_type, getString(resId)))
|
add(Item(R.string.euicc_info_ci_type, getString(resId)))
|
||||||
}
|
}
|
||||||
add(
|
val atr = channel.atr?.encodeHex() ?: getString(R.string.information_unavailable)
|
||||||
Item(
|
add(Item(R.string.euicc_info_atr, atr, copiedToastResId = R.string.toast_atr_copied))
|
||||||
R.string.euicc_info_atr,
|
|
||||||
channel.atr?.encodeHex() ?: getString(R.string.information_unavailable),
|
|
||||||
copiedToastResId = R.string.toast_atr_copied,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatByBoolean(b: Boolean, res: Pair<Int, Int>): String =
|
private fun formatByBoolean(b: Boolean, res: Pair<Int, Int>): String =
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package im.angry.openeuicc.vendored
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import im.angry.openeuicc.core.ApduInterfaceAtrProvider
|
||||||
|
import im.angry.openeuicc.util.TAG
|
||||||
|
import im.angry.openeuicc.util.decodeHex
|
||||||
|
import net.typeblog.lpac_jni.ApduInterface
|
||||||
|
|
||||||
|
data class ESTKmeInfo(
|
||||||
|
val serialNumber: String?,
|
||||||
|
val bootloaderVersion: String?,
|
||||||
|
val firmwareVersion: String?,
|
||||||
|
val skuName: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun isESTKmeATR(iface: ApduInterface): Boolean {
|
||||||
|
if (iface !is ApduInterfaceAtrProvider) return false
|
||||||
|
val atr = iface.atr ?: return false
|
||||||
|
val fpr = "estk.me".encodeToByteArray()
|
||||||
|
for (index in atr.indices) {
|
||||||
|
if (atr.size - index < fpr.size) break
|
||||||
|
if (atr.sliceArray(index until index + fpr.size).contentEquals(fpr)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getESTKmeInfo(iface: ApduInterface): ESTKmeInfo? {
|
||||||
|
if (!isESTKmeATR(iface)) return null
|
||||||
|
fun decode(b: ByteArray): String? {
|
||||||
|
if (b.size < 2) return null
|
||||||
|
if (b[b.size - 2] != 0x90.toByte() || b[b.size - 1] != 0x00.toByte()) return null
|
||||||
|
return b.sliceArray(0 until b.size - 2).decodeToString()
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
iface.openChannel("A06573746B6D65FFFFFFFFFFFF6D6774".decodeHex()) {
|
||||||
|
fun invoke(p1: Byte) = decode(it.transmit(byteArrayOf(0x00, 0x00, p1, 0x00, 0x00)))
|
||||||
|
ESTKmeInfo(
|
||||||
|
invoke(0x00), // serial number
|
||||||
|
invoke(0x01), // bootloader version
|
||||||
|
invoke(0x02), // firmware version
|
||||||
|
invoke(0x03), // sku name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(TAG, "Failed to get ESTKmeInfo", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package im.angry.openeuicc.vendored
|
||||||
|
|
||||||
|
import net.typeblog.lpac_jni.Version
|
||||||
|
|
||||||
|
private val prefix = Regex("^89044045(84|21)67274948") // SIMLink EID prefix
|
||||||
|
|
||||||
|
fun getSIMLinkVersion(eid: String, version: Version?): String? {
|
||||||
|
if (version == null || prefix.find(eid, 0) == null) return null
|
||||||
|
return when {
|
||||||
|
// @formatter:off
|
||||||
|
version >= Version(36, 18, 5) -> "v3 (final)"
|
||||||
|
version >= Version(36, 17, 39) -> "v3 (beta)"
|
||||||
|
version >= Version(36, 17, 4) -> "v2s"
|
||||||
|
version >= Version(36, 9, 3) -> "v2.1"
|
||||||
|
version >= Version(36, 7, 2) -> "v2"
|
||||||
|
// @formatter:on
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,7 @@
|
||||||
<string name="toast_profile_enable_failed">Cannot switch to new eSIM profile.</string>
|
<string name="toast_profile_enable_failed">Cannot switch to new eSIM profile.</string>
|
||||||
<string name="toast_profile_delete_confirm_text_mismatched">Confirmation string mismatch</string>
|
<string name="toast_profile_delete_confirm_text_mismatched">Confirmation string mismatch</string>
|
||||||
<string name="toast_iccid_copied">ICCID copied to clipboard</string>
|
<string name="toast_iccid_copied">ICCID copied to clipboard</string>
|
||||||
|
<string name="toast_sn_copied">Serial Number copied to clipboard</string>
|
||||||
<string name="toast_eid_copied">EID copied to clipboard</string>
|
<string name="toast_eid_copied">EID copied to clipboard</string>
|
||||||
<string name="toast_atr_copied">ATR copied to clipboard</string>
|
<string name="toast_atr_copied">ATR copied to clipboard</string>
|
||||||
|
|
||||||
|
@ -125,6 +126,10 @@
|
||||||
<string name="euicc_info_activity_title">eUICC Info (%s)</string>
|
<string name="euicc_info_activity_title">eUICC Info (%s)</string>
|
||||||
<string name="euicc_info_access_mode">Access Mode</string>
|
<string name="euicc_info_access_mode">Access Mode</string>
|
||||||
<string name="euicc_info_removable">Removable</string>
|
<string name="euicc_info_removable">Removable</string>
|
||||||
|
<string name="euicc_info_sku">Product Name</string>
|
||||||
|
<string name="euicc_info_sn">Product Serial Number</string>
|
||||||
|
<string name="euicc_info_bl_ver">Product Bootloader Version</string>
|
||||||
|
<string name="euicc_info_fw_ver">Product Firmware Version</string>
|
||||||
<string name="euicc_info_eid" translatable="false">EID</string>
|
<string name="euicc_info_eid" translatable="false">EID</string>
|
||||||
<string name="euicc_info_sgp22_version">SGP.22 Version</string>
|
<string name="euicc_info_sgp22_version">SGP.22 Version</string>
|
||||||
<string name="euicc_info_firmware_version">eUICC OS Version</string>
|
<string name="euicc_info_firmware_version">eUICC OS Version</string>
|
||||||
|
|
|
@ -17,10 +17,10 @@ interface ApduInterface {
|
||||||
*/
|
*/
|
||||||
val valid: Boolean
|
val valid: Boolean
|
||||||
|
|
||||||
fun <T> withLogicalChannel(aid: ByteArray, cb: ((ByteArray) -> ByteArray) -> T): T {
|
fun <T> withLogicalChannel(aid: ByteArray, callback: ((ByteArray) -> ByteArray) -> T): T {
|
||||||
val handle = logicalChannelOpen(aid)
|
val handle = logicalChannelOpen(aid)
|
||||||
return try {
|
return try {
|
||||||
cb { transmit(handle, it) }
|
callback { tx -> transmit(handle, tx) }
|
||||||
} finally {
|
} finally {
|
||||||
logicalChannelClose(handle)
|
logicalChannelClose(handle)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,31 @@ package net.typeblog.lpac_jni
|
||||||
|
|
||||||
/* Corresponds to EuiccInfo2 in SGP.22 */
|
/* Corresponds to EuiccInfo2 in SGP.22 */
|
||||||
data class EuiccInfo2(
|
data class EuiccInfo2(
|
||||||
val sgp22Version: String,
|
val sgp22Version: Version,
|
||||||
val profileVersion: String,
|
val profileVersion: Version,
|
||||||
val euiccFirmwareVersion: String,
|
val euiccFirmwareVersion: Version,
|
||||||
val globalPlatformVersion: String,
|
val globalPlatformVersion: Version,
|
||||||
val sasAccreditationNumber: String,
|
val sasAccreditationNumber: String,
|
||||||
val ppVersion: String,
|
val ppVersion: Version,
|
||||||
val freeNvram: Int,
|
val freeNvram: Int,
|
||||||
val freeRam: Int,
|
val freeRam: Int,
|
||||||
val euiccCiPKIdListForSigning: Array<String>,
|
val euiccCiPKIdListForSigning: Set<String>,
|
||||||
val euiccCiPKIdListForVerification: Array<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))
|
||||||
|
private 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.LocalProfileInfo
|
||||||
import net.typeblog.lpac_jni.LocalProfileNotification
|
import net.typeblog.lpac_jni.LocalProfileNotification
|
||||||
import net.typeblog.lpac_jni.ProfileDownloadCallback
|
import net.typeblog.lpac_jni.ProfileDownloadCallback
|
||||||
|
import net.typeblog.lpac_jni.Version
|
||||||
|
|
||||||
class LocalProfileAssistantImpl(
|
class LocalProfileAssistantImpl(
|
||||||
isdrAid: ByteArray,
|
isdrAid: ByteArray,
|
||||||
|
@ -84,8 +85,8 @@ class LocalProfileAssistantImpl(
|
||||||
throw IllegalArgumentException("Failed to initialize LPA")
|
throw IllegalArgumentException("Failed to initialize LPA")
|
||||||
}
|
}
|
||||||
|
|
||||||
val pkids = euiccInfo2?.euiccCiPKIdListForVerification ?: arrayOf()
|
val pkids = euiccInfo2?.euiccCiPKIdListForVerification ?: setOf()
|
||||||
httpInterface.usePublicKeyIds(pkids)
|
httpInterface.usePublicKeyIds(pkids.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setEs10xMss(mss: Byte) {
|
override fun setEs10xMss(mss: Byte) {
|
||||||
|
@ -172,16 +173,16 @@ class LocalProfileAssistantImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
val ret = EuiccInfo2(
|
val ret = EuiccInfo2(
|
||||||
LpacJni.euiccInfo2GetSGP22Version(cInfo),
|
Version(LpacJni.euiccInfo2GetSGP22Version(cInfo)),
|
||||||
LpacJni.euiccInfo2GetProfileVersion(cInfo),
|
Version(LpacJni.euiccInfo2GetProfileVersion(cInfo)),
|
||||||
LpacJni.euiccInfo2GetEuiccFirmwareVersion(cInfo),
|
Version(LpacJni.euiccInfo2GetEuiccFirmwareVersion(cInfo)),
|
||||||
LpacJni.euiccInfo2GetGlobalPlatformVersion(cInfo),
|
Version(LpacJni.euiccInfo2GetGlobalPlatformVersion(cInfo)),
|
||||||
LpacJni.euiccInfo2GetSasAcreditationNumber(cInfo),
|
LpacJni.euiccInfo2GetSasAcreditationNumber(cInfo),
|
||||||
LpacJni.euiccInfo2GetPpVersion(cInfo),
|
Version(LpacJni.euiccInfo2GetPpVersion(cInfo)),
|
||||||
LpacJni.euiccInfo2GetFreeNonVolatileMemory(cInfo).toInt(),
|
LpacJni.euiccInfo2GetFreeNonVolatileMemory(cInfo).toInt(),
|
||||||
LpacJni.euiccInfo2GetFreeVolatileMemory(cInfo).toInt(),
|
LpacJni.euiccInfo2GetFreeVolatileMemory(cInfo).toInt(),
|
||||||
euiccCiPKIdListForSigning.toTypedArray(),
|
euiccCiPKIdListForSigning.toTypedArray().toSet(),
|
||||||
euiccCiPKIdListForVerification.toTypedArray()
|
euiccCiPKIdListForVerification.toTypedArray().toSet(),
|
||||||
)
|
)
|
||||||
|
|
||||||
LpacJni.euiccInfo2Free(cInfo)
|
LpacJni.euiccInfo2Free(cInfo)
|
||||||
|
|
|
@ -14,7 +14,7 @@ const val DEFAULT_PKID_GSMA_RSP2_ROOT_CI1 = "81370f5125d0b1d408d4c3b232e6d25e795
|
||||||
|
|
||||||
// List of GSMA Live CIs
|
// List of GSMA Live CIs
|
||||||
// https://www.gsma.com/solutions-and-impact/technologies/esim/gsma-root-ci/
|
// https://www.gsma.com/solutions-and-impact/technologies/esim/gsma-root-ci/
|
||||||
val PKID_GSMA_LIVE_CI = arrayOf(
|
val PKID_GSMA_LIVE_CI = setOf(
|
||||||
// GSMA RSP2 Root CI1 (SGP.22 v2+v3, CA: DigiCert)
|
// GSMA RSP2 Root CI1 (SGP.22 v2+v3, CA: DigiCert)
|
||||||
// https://euicc-manual.osmocom.org/docs/pki/ci/files/81370f.txt
|
// https://euicc-manual.osmocom.org/docs/pki/ci/files/81370f.txt
|
||||||
DEFAULT_PKID_GSMA_RSP2_ROOT_CI1,
|
DEFAULT_PKID_GSMA_RSP2_ROOT_CI1,
|
||||||
|
@ -25,7 +25,7 @@ val PKID_GSMA_LIVE_CI = arrayOf(
|
||||||
|
|
||||||
// SGP.26 v3.0, 2023-12-01
|
// SGP.26 v3.0, 2023-12-01
|
||||||
// https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2023/12/SGP.26-v3.0.pdf
|
// https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2023/12/SGP.26-v3.0.pdf
|
||||||
val PKID_GSMA_TEST_CI = arrayOf(
|
val PKID_GSMA_TEST_CI = setOf(
|
||||||
// Test CI (SGP.26, NIST P256)
|
// Test CI (SGP.26, NIST P256)
|
||||||
// https://euicc-manual.osmocom.org/docs/pki/ci/files/34eecf.txt
|
// https://euicc-manual.osmocom.org/docs/pki/ci/files/34eecf.txt
|
||||||
"34eecf13156518d48d30bdf06853404d115f955d",
|
"34eecf13156518d48d30bdf06853404d115f955d",
|
||||||
|
|
Loading…
Add table
Reference in a new issue