Compare commits

...

10 commits

10 changed files with 133 additions and 45 deletions

1
.idea/.gitignore generated vendored
View file

@ -9,5 +9,6 @@
/navEditor.xml /navEditor.xml
/runConfigurations.xml /runConfigurations.xml
/workspace.xml /workspace.xml
/AndroidProjectSystem.xml
**/*.iml **/*.iml

View file

@ -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

View file

@ -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 =

View file

@ -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.withLogicalChannel("A06573746B6D65FFFFFFFFFFFF6D6774".decodeHex()) { transmit ->
fun invoke(p1: Byte) = decode(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
}
}

View file

@ -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
}
}

View file

@ -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>

View file

@ -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)
} }

View file

@ -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"
}

View file

@ -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)

View file

@ -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",