Compare commits

..

No commits in common. "55d96c6732c1e0223fb6f1c5296e8d1306058f29" and "6d962a12b5de1794e9f65e4dbddc40713c65ba39" have entirely different histories.

4 changed files with 41 additions and 78 deletions

View file

@ -19,12 +19,13 @@ 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 kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.typeblog.lpac_jni.impl.PKID_GSMA_LIVE_CI import net.typeblog.lpac_jni.impl.DEFAULT_PKID_GSMA_RSP2_ROOT_CI1
import net.typeblog.lpac_jni.impl.PKID_GSMA_TEST_CI import net.typeblog.lpac_jni.impl.PKID_GSMA_TEST_CI
class EuiccInfoActivity : BaseEuiccAccessActivity() { class EuiccInfoActivity : BaseEuiccAccessActivity() {
companion object { companion object {
private val YES_NO = Pair(R.string.yes, R.string.no) private val YES_NO = Pair(R.string.yes, R.string.no)
private val SUPPORTED_UNSUPPORTED = Pair(R.string.supported, R.string.unsupported)
} }
private lateinit var swipeRefresh: SwipeRefreshLayout private lateinit var swipeRefresh: SwipeRefreshLayout
@ -106,17 +107,21 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() {
add(Pair(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace))) add(Pair(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace)))
} }
channel.lpa.euiccInfo2?.euiccCiPKIdListForSigning.orEmpty().let { signers -> channel.lpa.euiccInfo2?.euiccCiPKIdListForSigning.orEmpty().let { signers ->
// SGP.28 v1.0, eSIM CI Registration Criteria (Page 5 of 9, 2019-10-24) add(
// https://www.gsma.com/newsroom/wp-content/uploads/SGP.28-v1.0.pdf#page=5 Pair(
// FS.27 v2.0, Security Guidelines for UICC Profiles (Page 25 of 27, 2024-01-30) R.string.euicc_info_gsma_prod,
// https://www.gsma.com/solutions-and-impact/technologies/security/wp-content/uploads/2024/01/FS.27-Security-Guidelines-for-UICC-Credentials-v2.0-FINAL-23-July.pdf#page=25 formatByBoolean(
val resId = when { signers.contains(DEFAULT_PKID_GSMA_RSP2_ROOT_CI1),
signers.isEmpty() -> R.string.unknown // the case is not mp, but it's is not common SUPPORTED_UNSUPPORTED
PKID_GSMA_LIVE_CI.any(signers::contains) -> R.string.euicc_info_ci_gsma_live )
PKID_GSMA_TEST_CI.any(signers::contains) -> R.string.euicc_info_ci_gsma_test )
else -> R.string.euicc_info_ci_unknown )
} add(
add(Pair(R.string.euicc_info_ci_type, getString(resId))) Pair(
R.string.euicc_info_gsma_test,
formatByBoolean(PKID_GSMA_TEST_CI.any(signers::contains), SUPPORTED_UNSUPPORTED)
)
)
} }
} }

View file

@ -4,7 +4,6 @@ import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.widget.EditText import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -17,47 +16,31 @@ import kotlinx.coroutines.launch
class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker { class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker {
companion object { companion object {
const val TAG = "ProfileDeleteFragment" const val TAG = "ProfileDeleteFragment"
private const val FIELD_ICCID = "iccid"
private const val FIELD_NAME = "name"
fun newInstance(slotId: Int, portId: Int, iccid: String, name: String): ProfileDeleteFragment { fun newInstance(slotId: Int, portId: Int, iccid: String, name: String): ProfileDeleteFragment {
val instance = newInstanceEuicc(ProfileDeleteFragment::class.java, slotId, portId) val instance = newInstanceEuicc(ProfileDeleteFragment::class.java, slotId, portId)
instance.requireArguments().apply { instance.requireArguments().apply {
putString(FIELD_ICCID, iccid) putString("iccid", iccid)
putString(FIELD_NAME, name) putString("name", name)
} }
return instance return instance
} }
} }
private val iccid by lazy {
requireArguments().getString(FIELD_ICCID)!!
}
private val name by lazy {
requireArguments().getString(FIELD_NAME)!!
}
private val editText by lazy { private val editText by lazy {
EditText(requireContext()).apply { EditText(requireContext()).apply {
hint = Editable.Factory.getInstance() hint = Editable.Factory.getInstance().newEditable(
.newEditable(getString(R.string.profile_delete_confirm_input, name)) getString(R.string.profile_delete_confirm_input, requireArguments().getString("name")!!)
)
} }
} }
private val inputMatchesName: Boolean private val inputMatchesName: Boolean
get() = editText.text.toString() == name get() = editText.text.toString() == requireArguments().getString("name")!!
private var toast: Toast? = null
private var deleting = false private var deleting = false
private val alertDialog: AlertDialog
get() = requireDialog() as AlertDialog
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
AlertDialog.Builder(requireContext(), R.style.AlertDialogTheme).apply { AlertDialog.Builder(requireContext(), R.style.AlertDialogTheme).apply {
setMessage(getString(R.string.profile_delete_confirm, name)) setMessage(getString(R.string.profile_delete_confirm, requireArguments().getString("name")))
setView(editText) setView(editText)
setPositiveButton(android.R.string.ok, null) // Set listener to null to prevent auto closing setPositiveButton(android.R.string.ok, null) // Set listener to null to prevent auto closing
setNegativeButton(android.R.string.cancel, null) setNegativeButton(android.R.string.cancel, null)
@ -65,8 +48,9 @@ class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
val alertDialog = dialog!! as AlertDialog
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
if (!deleting) delete() if (!deleting && inputMatchesName) delete()
} }
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener {
if (!deleting) dismiss() if (!deleting) dismiss()
@ -74,15 +58,8 @@ class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker {
} }
private fun delete() { private fun delete() {
toast?.cancel()
if (!inputMatchesName) {
val resId = R.string.toast_profile_delete_confirm_text_mismatched
toast = Toast.makeText(requireContext(), resId, Toast.LENGTH_LONG).also {
it.show()
}
return
}
deleting = true deleting = true
val alertDialog = dialog!! as AlertDialog
alertDialog.setCanceledOnTouchOutside(false) alertDialog.setCanceledOnTouchOutside(false)
alertDialog.setCancelable(false) alertDialog.setCancelable(false)
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
@ -91,7 +68,12 @@ class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker {
requireParentFragment().lifecycleScope.launch { requireParentFragment().lifecycleScope.launch {
ensureEuiccChannelManager() ensureEuiccChannelManager()
euiccChannelManagerService.waitForForegroundTask() euiccChannelManagerService.waitForForegroundTask()
euiccChannelManagerService.launchProfileDeleteTask(slotId, portId, iccid).onStart {
euiccChannelManagerService.launchProfileDeleteTask(
slotId,
portId,
requireArguments().getString("iccid")!!
).onStart {
if (parentFragment is EuiccProfilesChangedListener) { if (parentFragment is EuiccProfilesChangedListener) {
// Trigger a refresh in the parent fragment -- it should wait until // Trigger a refresh in the parent fragment -- it should wait until
// any foreground task is completed before actually doing a refresh // any foreground task is completed before actually doing a refresh

View file

@ -29,7 +29,6 @@
<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_name_too_long">Nickname cannot be longer than 64 characters</string> <string name="toast_profile_name_too_long">Nickname cannot be longer than 64 characters</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="slot_select">Select Slot</string> <string name="slot_select">Select Slot</string>
@ -123,10 +122,11 @@
<string name="euicc_info_sas_accreditation_number">SAS Accreditation Number</string> <string name="euicc_info_sas_accreditation_number">SAS Accreditation Number</string>
<string name="euicc_info_pp_version">Protected Profile Version</string> <string name="euicc_info_pp_version">Protected Profile Version</string>
<string name="euicc_info_free_nvram">Free NVRAM (eSIM profile storage)</string> <string name="euicc_info_free_nvram">Free NVRAM (eSIM profile storage)</string>
<string name="euicc_info_ci_type">Certificate Issuer</string> <string name="euicc_info_gsma_prod">GSMA Production Certificate</string>
<string name="euicc_info_ci_gsma_live">GSMA Live CI</string> <string name="euicc_info_gsma_test">GSMA Test Certificate</string>
<string name="euicc_info_ci_gsma_test">GSMA Test CI</string>
<string name="euicc_info_ci_unknown">Unknown eSIM CA</string> <string name="supported">Supported</string>
<string name="unsupported">Unsupported</string>
<string name="yes">Yes</string> <string name="yes">Yes</string>
<string name="no">No</string> <string name="no">No</string>

View file

@ -7,32 +7,8 @@ import java.security.cert.CertificateFactory
const val DEFAULT_PKID_GSMA_RSP2_ROOT_CI1 = "81370f5125d0b1d408d4c3b232e6d25e795bebfb" const val DEFAULT_PKID_GSMA_RSP2_ROOT_CI1 = "81370f5125d0b1d408d4c3b232e6d25e795bebfb"
// SGP.28 v1.0, eSIM CI Registration Criteria (Page 5 of 9, 2019-10-24) val PKID_GSMA_TEST_CI =
// https://www.gsma.com/newsroom/wp-content/uploads/SGP.28-v1.0.pdf#page=5 arrayOf("34eecf13156518d48d30bdf06853404d115f955d", "2209f61cd9ec5c9c854e787341ff83ecf9776a5b")
// FS.27 v2.0, Security Guidelines for UICC Profiles (Page 25 of 27, 2024-01-30)
// https://www.gsma.com/solutions-and-impact/technologies/security/wp-content/uploads/2024/01/FS.27-Security-Guidelines-for-UICC-Credentials-v2.0-FINAL-23-July.pdf#page=25
// List of GSMA Live CIs
// https://www.gsma.com/solutions-and-impact/technologies/esim/gsma-root-ci/
val PKID_GSMA_LIVE_CI = arrayOf(
// GSMA RSP2 Root CI1 (SGP.22 v2+v3, CA: DigiCert)
// https://euicc-manual.osmocom.org/docs/pki/ci/files/81370f.txt
DEFAULT_PKID_GSMA_RSP2_ROOT_CI1,
// OISITE GSMA CI G1 (SGP.22 v2+v3, CA: WISeKey)
// https://euicc-manual.osmocom.org/docs/pki/ci/files/4c2796.txt
"4c27967ad20c14b391e9601e41e604ad57c0222f",
)
// 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
val PKID_GSMA_TEST_CI = arrayOf(
// Test CI (SGP.26, NIST P256)
// https://euicc-manual.osmocom.org/docs/pki/ci/files/34eecf.txt
"34eecf13156518d48d30bdf06853404d115f955d",
// Test CI (SGP.26, BRP P256r1)
// https://euicc-manual.osmocom.org/docs/pki/ci/files/2209f6.txt
"2209f61cd9ec5c9c854e787341ff83ecf9776a5b",
)
private fun getCertificate(keyId: String): Certificate? = private fun getCertificate(keyId: String): Certificate? =
KNOWN_CI_CERTS[keyId]?.toByteArray()?.let { cert -> KNOWN_CI_CERTS[keyId]?.toByteArray()?.let { cert ->
@ -84,7 +60,7 @@ internal val KNOWN_CI_CERTS = hashMapOf(
-----END CERTIFICATE----- -----END CERTIFICATE-----
""".trimIndent(), """.trimIndent(),
// OISITE GSMA CI G1 (CA: WISeKey) // OISITE GSMA CI G1 (CA: WISeKey)
// Specs: SGP.21 and SGP.22 version 2 and version 3 // Specs: SGP.21 and SGP.22 version 3
"4c27967ad20c14b391e9601e41e604ad57c0222f" to """ "4c27967ad20c14b391e9601e41e604ad57c0222f" to """
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIB9zCCAZ2gAwIBAgIUSpBSCCDYPOEG/IFHUCKpZ2pIAQMwCgYIKoZIzj0EAwIw MIIB9zCCAZ2gAwIBAgIUSpBSCCDYPOEG/IFHUCKpZ2pIAQMwCgYIKoZIzj0EAwIw