forked from PeterCxy/OpenEUICC
Compare commits
2 commits
6d962a12b5
...
55d96c6732
Author | SHA1 | Date | |
---|---|---|---|
55d96c6732 | |||
06873545e2 |
4 changed files with 78 additions and 41 deletions
|
@ -19,13 +19,12 @@ 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.DEFAULT_PKID_GSMA_RSP2_ROOT_CI1
|
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
|
||||||
|
|
||||||
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
|
||||||
|
@ -107,21 +106,17 @@ 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 ->
|
||||||
add(
|
// SGP.28 v1.0, eSIM CI Registration Criteria (Page 5 of 9, 2019-10-24)
|
||||||
Pair(
|
// https://www.gsma.com/newsroom/wp-content/uploads/SGP.28-v1.0.pdf#page=5
|
||||||
R.string.euicc_info_gsma_prod,
|
// FS.27 v2.0, Security Guidelines for UICC Profiles (Page 25 of 27, 2024-01-30)
|
||||||
formatByBoolean(
|
// 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
|
||||||
signers.contains(DEFAULT_PKID_GSMA_RSP2_ROOT_CI1),
|
val resId = when {
|
||||||
SUPPORTED_UNSUPPORTED
|
signers.isEmpty() -> R.string.unknown // the case is not mp, but it's is not common
|
||||||
)
|
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(
|
}
|
||||||
Pair(
|
add(Pair(R.string.euicc_info_ci_type, getString(resId)))
|
||||||
R.string.euicc_info_gsma_test,
|
|
||||||
formatByBoolean(PKID_GSMA_TEST_CI.any(signers::contains), SUPPORTED_UNSUPPORTED)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ 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
|
||||||
|
@ -16,31 +17,47 @@ 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("iccid", iccid)
|
putString(FIELD_ICCID, iccid)
|
||||||
putString("name", name)
|
putString(FIELD_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().newEditable(
|
hint = Editable.Factory.getInstance()
|
||||||
getString(R.string.profile_delete_confirm_input, requireArguments().getString("name")!!)
|
.newEditable(getString(R.string.profile_delete_confirm_input, name))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val inputMatchesName: Boolean
|
private val inputMatchesName: Boolean
|
||||||
get() = editText.text.toString() == requireArguments().getString("name")!!
|
get() = editText.text.toString() == 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, requireArguments().getString("name")))
|
setMessage(getString(R.string.profile_delete_confirm, 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)
|
||||||
|
@ -48,9 +65,8 @@ 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 && inputMatchesName) delete()
|
if (!deleting) delete()
|
||||||
}
|
}
|
||||||
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener {
|
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener {
|
||||||
if (!deleting) dismiss()
|
if (!deleting) dismiss()
|
||||||
|
@ -58,8 +74,15 @@ 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
|
||||||
|
@ -68,12 +91,7 @@ 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
|
||||||
|
|
|
@ -29,6 +29,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_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>
|
||||||
|
@ -122,11 +123,10 @@
|
||||||
<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_gsma_prod">GSMA Production Certificate</string>
|
<string name="euicc_info_ci_type">Certificate Issuer</string>
|
||||||
<string name="euicc_info_gsma_test">GSMA Test Certificate</string>
|
<string name="euicc_info_ci_gsma_live">GSMA Live CI</string>
|
||||||
|
<string name="euicc_info_ci_gsma_test">GSMA Test CI</string>
|
||||||
<string name="supported">Supported</string>
|
<string name="euicc_info_ci_unknown">Unknown eSIM CA</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>
|
||||||
|
|
|
@ -7,8 +7,32 @@ import java.security.cert.CertificateFactory
|
||||||
|
|
||||||
const val DEFAULT_PKID_GSMA_RSP2_ROOT_CI1 = "81370f5125d0b1d408d4c3b232e6d25e795bebfb"
|
const val DEFAULT_PKID_GSMA_RSP2_ROOT_CI1 = "81370f5125d0b1d408d4c3b232e6d25e795bebfb"
|
||||||
|
|
||||||
val PKID_GSMA_TEST_CI =
|
// SGP.28 v1.0, eSIM CI Registration Criteria (Page 5 of 9, 2019-10-24)
|
||||||
arrayOf("34eecf13156518d48d30bdf06853404d115f955d", "2209f61cd9ec5c9c854e787341ff83ecf9776a5b")
|
// https://www.gsma.com/newsroom/wp-content/uploads/SGP.28-v1.0.pdf#page=5
|
||||||
|
// 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 ->
|
||||||
|
@ -60,7 +84,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 3
|
// Specs: SGP.21 and SGP.22 version 2 and version 3
|
||||||
"4c27967ad20c14b391e9601e41e604ad57c0222f" to """
|
"4c27967ad20c14b391e9601e41e604ad57c0222f" to """
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIB9zCCAZ2gAwIBAgIUSpBSCCDYPOEG/IFHUCKpZ2pIAQMwCgYIKoZIzj0EAwIw
|
MIIB9zCCAZ2gAwIBAgIUSpBSCCDYPOEG/IFHUCKpZ2pIAQMwCgYIKoZIzj0EAwIw
|
||||||
|
|
Loading…
Add table
Reference in a new issue