From 20b634e1be15cce81f7432e68c505d28cb7d8344 Mon Sep 17 00:00:00 2001 From: septs Date: Wed, 5 Mar 2025 09:21:09 +0800 Subject: [PATCH 1/3] feat: custom isd-r aids --- .../core/DefaultEuiccChannelFactory.kt | 2 + .../angry/openeuicc/core/EuiccChannelImpl.kt | 50 +++++++++++++++---- .../openeuicc/core/OmapiApduInterface.kt | 4 +- .../im/angry/openeuicc/ui/SettingsFragment.kt | 32 ++++++++++++ .../im/angry/openeuicc/util/InputFilters.kt | 10 ++++ .../angry/openeuicc/util/PreferenceUtils.kt | 3 ++ app-common/src/main/res/values/strings.xml | 2 + app-common/src/main/res/xml/pref_settings.xml | 7 +++ .../core/PrivilegedEuiccChannelFactory.kt | 1 + 9 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 app-common/src/main/java/im/angry/openeuicc/util/InputFilters.kt diff --git a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt index ea0bd60..6961a26 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt @@ -46,6 +46,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha ), context.preferenceRepository.verboseLoggingFlow, context.preferenceRepository.ignoreTLSCertificateFlow, + context.preferenceRepository.isdRAidFallbackFlow, ).also { Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to 60") it.lpa.setEs10xMss(60) @@ -78,6 +79,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha ), context.preferenceRepository.verboseLoggingFlow, context.preferenceRepository.ignoreTLSCertificateFlow, + context.preferenceRepository.isdRAidFallbackFlow, ) } 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 a56b1cc..ee9ff06 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,8 +1,10 @@ package im.angry.openeuicc.core -import im.angry.openeuicc.util.UiccPortInfoCompat -import im.angry.openeuicc.util.decodeHex +import android.util.Log +import im.angry.openeuicc.util.* import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking import net.typeblog.lpac_jni.ApduInterface import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.impl.HttpInterfaceImpl @@ -14,23 +16,28 @@ class EuiccChannelImpl( override val intrinsicChannelName: String?, override val apduInterface: ApduInterface, verboseLoggingFlow: Flow, - ignoreTLSCertificateFlow: Flow + ignoreTLSCertificateFlow: Flow, + private val isdRAidFallback: Flow>, ) : EuiccChannel { companion object { - // TODO: This needs to go somewhere else. - val ISDR_AID = "A0000005591010FFFFFFFF8900000100".decodeHex() + private const val TAG = "EuiccChannelImpl" + + // Specs: SGP.02 v4.3 (Section 2.2.3) + // https://www.gsma.com/esim/wp-content/uploads/2023/02/SGP.02-v4.3.pdf#page=27 + val STANDARD_ISDR_AID = "A0000005591010FFFFFFFF8900000100".decodeHex() } + private val isdRAid = findISDRAID() + override val slotId = port.card.physicalSlotIndex override val logicalSlotId = port.logicalSlotIndex override val portId = port.portIndex - override val lpa: LocalProfileAssistant = - LocalProfileAssistantImpl( - ISDR_AID, - apduInterface, - HttpInterfaceImpl(verboseLoggingFlow, ignoreTLSCertificateFlow) - ) + override val lpa: LocalProfileAssistant = LocalProfileAssistantImpl( + isdRAid, + apduInterface, + HttpInterfaceImpl(verboseLoggingFlow, ignoreTLSCertificateFlow), + ) override val atr: ByteArray? get() = (apduInterface as? ApduInterfaceAtrProvider)?.atr @@ -39,4 +46,25 @@ class EuiccChannelImpl( get() = lpa.valid override fun close() = lpa.close() + + private fun findISDRAID(): ByteArray { + try { + apduInterface.connect() + for (aid in runBlocking { isdRAidFallback.first() }) { + val channel = aid.decodeHex() + try { + Log.d(TAG, "Trying ISD-R AID: $aid") + apduInterface.withLogicalChannel(channel) { true } + Log.d(TAG, "Selected ISD-R AID: $aid") + return channel + } catch (e: Exception) { + Log.d(TAG, "Failed to select ISD-R AID: $aid") + continue + } + } + } finally { + apduInterface.disconnect() + } + return STANDARD_ISDR_AID + } } 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 b3f42b5..33514e3 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 @@ -42,10 +42,10 @@ class OmapiApduInterface( } override fun logicalChannelOpen(aid: ByteArray): Int { - val channel = session.openLogicalChannel(aid) - check(channel != null) { "Failed to open logical channel (${aid.encodeHex()})" } val index = channels.indexOf(null) check(index != -1) { "No free logical channel slots" } + val channel = session.openLogicalChannel(aid) + check(channel != null) { "Failed to open logical channel (${aid.encodeHex()})" } synchronized(channels) { channels[index] = channel } return index } diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt index fab680f..c420229 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt @@ -8,6 +8,7 @@ import android.provider.Settings import android.widget.Toast import androidx.lifecycle.lifecycleScope import androidx.preference.CheckBoxPreference +import androidx.preference.EditTextPreference import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat @@ -72,6 +73,9 @@ open class SettingsFragment: PreferenceFragmentCompat() { requirePreference("pref_advanced_verbose_logging") .bindBooleanFlow(preferenceRepository.verboseLoggingFlow) + requirePreference("pref_developer_custom_aids") + .let(::setCustomAIDs) + requirePreference("pref_developer_unfiltered_profile_list") .bindBooleanFlow(preferenceRepository.unfilteredProfileListFlow) @@ -79,6 +83,34 @@ open class SettingsFragment: PreferenceFragmentCompat() { .bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow) } + private fun setCustomAIDs(preference: EditTextPreference) { + val flow = preferenceRepository.isdRAidFallbackFlow + val separator = "\n" + + fun prepare(text: String) = text.uppercase().lines() + .map(String::trim).toSet() + .filter { it.length % 2 == 0 && it.length in 10..32 && it.all(Char::isHex) } + .take(10) // limit to 10 AIDs + + lifecycleScope.launch { + flow.collect { preference.text = it.joinToString(separator) } + } + + preference.setOnBindEditTextListener { + it.filters += allowedInputFilter { c -> c.isHex() || c == '\n' } + it.setText(prepare(it.text.toString()).joinToString(separator)) + it.setSelection(it.text.length) + it.requestFocus() + } + + preference.setOnPreferenceChangeListener { _, newValue -> + runBlocking { + flow.updatePreference(prepare(newValue as String).toSet()) + } + true + } + } + protected fun requirePreference(key: CharSequence) = findPreference(key)!! diff --git a/app-common/src/main/java/im/angry/openeuicc/util/InputFilters.kt b/app-common/src/main/java/im/angry/openeuicc/util/InputFilters.kt new file mode 100644 index 0000000..2bb8866 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/util/InputFilters.kt @@ -0,0 +1,10 @@ +package im.angry.openeuicc.util + +import android.text.InputFilter + +fun allowedInputFilter(predicate: (Char) -> Boolean) = + InputFilter { source, start, end, _, _, _ -> + source.substring(start until end).filter(predicate) + } + +fun Char.isHex() = isDigit() || uppercaseChar() in 'A'..'F' \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt index f5e3ca2..5478945 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt @@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringSetPreferencesKey import androidx.datastore.preferences.preferencesDataStore import androidx.fragment.app.Fragment import im.angry.openeuicc.OpenEuiccApplication @@ -31,6 +32,7 @@ internal object PreferenceKeys { // ---- Developer Options ---- val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled") + val ISD_R_AID_FALLBACK = stringSetPreferencesKey("isd_r_aid_fallback") val UNFILTERED_PROFILE_LIST = booleanPreferencesKey("unfiltered_profile_list") val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate") } @@ -48,6 +50,7 @@ class PreferenceRepository(private val context: Context) { // ---- Developer Options ---- val developerOptionsEnabledFlow = bindFlow(PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, false) + val isdRAidFallbackFlow = bindFlow(PreferenceKeys.ISD_R_AID_FALLBACK, emptySet()) val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false) val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false) diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index a45ce1f..b1b58d6 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -171,6 +171,8 @@ Logs View recent debug logs of the application Developer Options + ISD-R AIDs Fallback + Prefer to use standard ISD-R AID access, one ISD-R AID per line (hexadecimal string) Show unfiltered profile list Include non-production profiles in the list Ignore SM-DP+ TLS certificate diff --git a/app-common/src/main/res/xml/pref_settings.xml b/app-common/src/main/res/xml/pref_settings.xml index bb5bd50..e6a75f4 100644 --- a/app-common/src/main/res/xml/pref_settings.xml +++ b/app-common/src/main/res/xml/pref_settings.xml @@ -57,6 +57,13 @@ app:title="@string/pref_developer" app:iconSpaceReserved="false"> + + Date: Wed, 5 Mar 2025 09:26:38 +0800 Subject: [PATCH 2/3] chore: set monospace for edittext --- .../src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt index c420229..2e5a0a6 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt @@ -1,6 +1,7 @@ package im.angry.openeuicc.ui import android.content.Intent +import android.graphics.Typeface import android.net.Uri import android.os.Build import android.os.Bundle @@ -98,6 +99,7 @@ open class SettingsFragment: PreferenceFragmentCompat() { preference.setOnBindEditTextListener { it.filters += allowedInputFilter { c -> c.isHex() || c == '\n' } + it.typeface = Typeface.MONOSPACE it.setText(prepare(it.text.toString()).joinToString(separator)) it.setSelection(it.text.length) it.requestFocus() -- 2.45.3 From a898c36b0267c92c5434b523e9780aa839545d6d Mon Sep 17 00:00:00 2001 From: septs Date: Wed, 5 Mar 2025 09:40:56 +0800 Subject: [PATCH 3/3] feat: custom isd-r aids --- .../main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 ee9ff06..c86c007 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 @@ -48,9 +48,13 @@ class EuiccChannelImpl( override fun close() = lpa.close() private fun findISDRAID(): ByteArray { + val aids = buildList { + add(STANDARD_ISDR_AID.encodeHex()) + addAll(runBlocking { isdRAidFallback.first() }) + } try { apduInterface.connect() - for (aid in runBlocking { isdRAidFallback.first() }) { + for (aid in aids) { val channel = aid.decodeHex() try { Log.d(TAG, "Trying ISD-R AID: $aid") -- 2.45.3