ソースを参照

refactor: Trust SM-DP+ TLS certs based on euiccCiPKIdListForVerification

Unfortunately, because there is no way to access the certificate itself
from the eUICC, we have to hard-code known & supported certificates
still.

However, this approach makes sure that only those certificates listed by
the eUICC are trusted during their SM-DP+ sessions. Were these added
directly as part of the Android security config, then all certificates
would be blindly trusted for all SM-DP+ sessions (and even normal TLS
connections if the app were to make them).

As a result we can now trust more known certificates, including GSMA
Test CIs. These are hard-coded as a hash map.
Peter Cai 1 年間 前
コミット
c033ef5ba9

+ 1 - 2
app-common/src/main/AndroidManifest.xml

@@ -6,8 +6,7 @@
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.INTERNET" />
 
 
-    <application
-        android:networkSecurityConfig="@xml/network_security_config">
+    <application>
         <activity
         <activity
             android:name="im.angry.openeuicc.ui.SettingsActivity"
             android:name="im.angry.openeuicc.ui.SettingsActivity"
             android:label="@string/pref_settings" />
             android:label="@string/pref_settings" />

+ 0 - 15
app-common/src/main/res/raw/symantec_gsma_rspv2_root_ci1

@@ -1,15 +0,0 @@
------BEGIN CERTIFICATE-----
-MIICSTCCAe+gAwIBAgIQbmhWeneg7nyF7hg5Y9+qejAKBggqhkjOPQQDAjBEMRgw
-FgYDVQQKEw9HU00gQXNzb2NpYXRpb24xKDAmBgNVBAMTH0dTTSBBc3NvY2lhdGlv
-biAtIFJTUDIgUm9vdCBDSTEwIBcNMTcwMjIyMDAwMDAwWhgPMjA1MjAyMjEyMzU5
-NTlaMEQxGDAWBgNVBAoTD0dTTSBBc3NvY2lhdGlvbjEoMCYGA1UEAxMfR1NNIEFz
-c29jaWF0aW9uIC0gUlNQMiBSb290IENJMTBZMBMGByqGSM49AgEGCCqGSM49AwEH
-A0IABJ1qutL0HCMX52GJ6/jeibsAqZfULWj/X10p/Min6seZN+hf5llovbCNuB2n
-unLz+O8UD0SUCBUVo8e6n9X1TuajgcAwgb0wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
-EwEB/wQFMAMBAf8wEwYDVR0RBAwwCogIKwYBBAGC6WAwFwYDVR0gAQH/BA0wCzAJ
-BgdngRIBAgEAME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9nc21hLWNybC5zeW1h
-dXRoLmNvbS9vZmZsaW5lY2EvZ3NtYS1yc3AyLXJvb3QtY2kxLmNybDAdBgNVHQ4E
-FgQUgTcPUSXQsdQI1MOyMubSXnlb6/swCgYIKoZIzj0EAwIDSAAwRQIgIJdYsOMF
-WziPK7l8nh5mu0qiRiVf25oa9ullG/OIASwCIQDqCmDrYf+GziHXBOiwJwnBaeBO
-aFsiLzIEOaUuZwdNUw==
------END CERTIFICATE-----

+ 0 - 9
app-common/src/main/res/xml/network_security_config.xml

@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<network-security-config>
-    <base-config>
-        <trust-anchors>
-            <certificates src="@raw/symantec_gsma_rspv2_root_ci1"/>
-            <certificates src="system"/>
-        </trust-anchors>
-    </base-config>
-</network-security-config>

+ 2 - 0
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt

@@ -9,4 +9,6 @@ data class EuiccInfo2(
     val ppVersion: String,
     val ppVersion: String,
     val freeNvram: Int,
     val freeNvram: Int,
     val freeRam: Int,
     val freeRam: Int,
+    val euiccCiPKIdListForSigning: Array<String>,
+    val euiccCiPKIdListForVerification: Array<String>,
 )
 )

+ 6 - 0
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/HttpInterface.kt

@@ -1,5 +1,7 @@
 package net.typeblog.lpac_jni
 package net.typeblog.lpac_jni
 
 
+import javax.net.ssl.TrustManager
+
 /*
 /*
  * Should reflect euicc_http_interface in lpac/euicc/interface.h
  * Should reflect euicc_http_interface in lpac/euicc/interface.h
  */
  */
@@ -25,4 +27,8 @@ interface HttpInterface {
     }
     }
 
 
     fun transmit(url: String, tx: ByteArray, headers: Array<String>): HttpResponse
     fun transmit(url: String, tx: ByteArray, headers: Array<String>): HttpResponse
+    // The LPA is supposed to pass in a list of pkIds supported by the eUICC.
+    // HttpInterface is responsible for providing TrustManager implementations that
+    // validate based on certificates corresponding to these pkIds
+    fun usePublicKeyIds(pkids: Array<String>)
 }
 }

+ 24 - 2
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/HttpInterfaceImpl.kt

@@ -2,14 +2,20 @@ package net.typeblog.lpac_jni.impl
 
 
 import android.util.Log
 import android.util.Log
 import net.typeblog.lpac_jni.HttpInterface
 import net.typeblog.lpac_jni.HttpInterface
-import java.net.HttpURLConnection
 import java.net.URL
 import java.net.URL
+import java.security.SecureRandom
+import javax.net.ssl.HttpsURLConnection
+import javax.net.ssl.SSLContext
+import javax.net.ssl.TrustManager
+import javax.net.ssl.TrustManagerFactory
 
 
 class HttpInterfaceImpl: HttpInterface {
 class HttpInterfaceImpl: HttpInterface {
     companion object {
     companion object {
         private const val TAG = "HttpInterfaceImpl"
         private const val TAG = "HttpInterfaceImpl"
     }
     }
 
 
+    private lateinit var trustManagers: Array<TrustManager>
+
     override fun transmit(
     override fun transmit(
         url: String,
         url: String,
         tx: ByteArray,
         tx: ByteArray,
@@ -17,8 +23,17 @@ class HttpInterfaceImpl: HttpInterface {
     ): HttpInterface.HttpResponse {
     ): HttpInterface.HttpResponse {
         Log.d(TAG, "transmit(url = $url)")
         Log.d(TAG, "transmit(url = $url)")
 
 
+        val parsedUrl = URL(url)
+        if (parsedUrl.protocol != "https") {
+            throw IllegalArgumentException("SM-DP+ servers must use the HTTPS protocol")
+        }
+
         try {
         try {
-            val conn = URL(url).openConnection() as HttpURLConnection
+            val sslContext = SSLContext.getInstance("TLS")
+            sslContext.init(null, trustManagers, SecureRandom())
+
+            val conn = parsedUrl.openConnection() as HttpsURLConnection
+            conn.sslSocketFactory = sslContext.socketFactory
             conn.requestMethod = "POST"
             conn.requestMethod = "POST"
             conn.doInput = true
             conn.doInput = true
             conn.doOutput = true
             conn.doOutput = true
@@ -40,4 +55,11 @@ class HttpInterfaceImpl: HttpInterface {
             throw e
             throw e
         }
         }
     }
     }
+
+    override fun usePublicKeyIds(pkids: Array<String>) {
+        val trustManagerFactory = TrustManagerFactory.getInstance("PKIX").apply {
+            init(keyIdToKeystore(pkids))
+        }
+        trustManagers = trustManagerFactory.trustManagers
+    }
 }
 }

+ 3 - 0
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt

@@ -26,6 +26,9 @@ class LocalProfileAssistantImpl(
         if (LpacJni.es10xInit(contextHandle) < 0) {
         if (LpacJni.es10xInit(contextHandle) < 0) {
             throw IllegalArgumentException("Failed to initialize LPA")
             throw IllegalArgumentException("Failed to initialize LPA")
         }
         }
+
+        val pkids = euiccInfo2?.euiccCiPKIdListForVerification ?: arrayOf(DEFAULT_PKID_GSMA_RSP2_ROOT_CI1)
+        httpInterface.usePublicKeyIds(pkids)
     }
     }
 
 
     private fun tryReconnect(timeoutMillis: Long) = runBlocking {
     private fun tryReconnect(timeoutMillis: Long) = runBlocking {

+ 148 - 0
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt

@@ -0,0 +1,148 @@
+package net.typeblog.lpac_jni.impl
+
+import java.io.ByteArrayInputStream
+import java.security.KeyStore
+import java.security.cert.CertificateException
+import java.security.cert.CertificateFactory
+
+const val DEFAULT_PKID_GSMA_RSP2_ROOT_CI1 = "81370f5125d0b1d408d4c3b232e6d25e795bebfb"
+
+internal fun keyIdToKeystore(keyIds: Array<String>): KeyStore {
+    val ret = KeyStore.getInstance(KeyStore.getDefaultType())
+    ret.load(null, null)
+    keyIds.forEach {
+        if (it !in KNOWN_CI_CERTS) throw CertificateException("Unknown CI cert ID $it")
+        ByteArrayInputStream(KNOWN_CI_CERTS[it]!!.toByteArray()).use { stream ->
+            val cf = CertificateFactory.getInstance("X.509")
+            ret.setCertificateEntry(it, cf.generateCertificate(stream))
+        }
+    }
+    return ret
+}
+
+// ref: <https://euicc-manual.septs.app/docs/pki/ci/>
+internal val KNOWN_CI_CERTS = hashMapOf(
+    // GSM Association - RSP2 Root CI1 (CA: DigiCert)
+    // Specs: SGP.21 and SGP.22 version 2 and version 3
+    "81370f5125d0b1d408d4c3b232e6d25e795bebfb" to """
+        -----BEGIN CERTIFICATE-----
+        MIICSTCCAe+gAwIBAgIQbmhWeneg7nyF7hg5Y9+qejAKBggqhkjOPQQDAjBEMRgw
+        FgYDVQQKEw9HU00gQXNzb2NpYXRpb24xKDAmBgNVBAMTH0dTTSBBc3NvY2lhdGlv
+        biAtIFJTUDIgUm9vdCBDSTEwIBcNMTcwMjIyMDAwMDAwWhgPMjA1MjAyMjEyMzU5
+        NTlaMEQxGDAWBgNVBAoTD0dTTSBBc3NvY2lhdGlvbjEoMCYGA1UEAxMfR1NNIEFz
+        c29jaWF0aW9uIC0gUlNQMiBSb290IENJMTBZMBMGByqGSM49AgEGCCqGSM49AwEH
+        A0IABJ1qutL0HCMX52GJ6/jeibsAqZfULWj/X10p/Min6seZN+hf5llovbCNuB2n
+        unLz+O8UD0SUCBUVo8e6n9X1TuajgcAwgb0wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
+        EwEB/wQFMAMBAf8wEwYDVR0RBAwwCogIKwYBBAGC6WAwFwYDVR0gAQH/BA0wCzAJ
+        BgdngRIBAgEAME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9nc21hLWNybC5zeW1h
+        dXRoLmNvbS9vZmZsaW5lY2EvZ3NtYS1yc3AyLXJvb3QtY2kxLmNybDAdBgNVHQ4E
+        FgQUgTcPUSXQsdQI1MOyMubSXnlb6/swCgYIKoZIzj0EAwIDSAAwRQIgIJdYsOMF
+        WziPK7l8nh5mu0qiRiVf25oa9ullG/OIASwCIQDqCmDrYf+GziHXBOiwJwnBaeBO
+        aFsiLzIEOaUuZwdNUw==
+        -----END CERTIFICATE-----
+    """.trimIndent(),
+    // OISITE GSMA CI G1 (CA: WISeKey)
+    // Specs: SGP.21 and SGP.22 version 3
+    "4c27967ad20c14b391e9601e41e604ad57c0222f" to """
+        -----BEGIN CERTIFICATE-----
+        MIIB9zCCAZ2gAwIBAgIUSpBSCCDYPOEG/IFHUCKpZ2pIAQMwCgYIKoZIzj0EAwIw
+        QzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xGTAXBgNV
+        BAMMEE9JU1RFIEdTTUEgQ0kgRzEwIBcNMjQwMTE2MjMxNzM5WhgPMjA1OTAxMDcy
+        MzE3MzhaMEMxCzAJBgNVBAYTAkNIMRkwFwYDVQQKDBBPSVNURSBGb3VuZGF0aW9u
+        MRkwFwYDVQQDDBBPSVNURSBHU01BIENJIEcxMFkwEwYHKoZIzj0CAQYIKoZIzj0D
+        AQcDQgAEvZ3s3PFC4NgrCcCMmHJ6DJ66uzAHuLcvjJnOn+TtBNThS7YHLDyHCa2v
+        7D+zTP+XTtgqgcLoB56Gha9EQQQ4xKNtMGswDwYDVR0TAQH/BAUwAwEB/zAQBgNV
+        HREECTAHiAVghXQFDjAXBgNVHSABAf8EDTALMAkGB2eBEgECAQAwHQYDVR0OBBYE
+        FEwnlnrSDBSzkelgHkHmBK1XwCIvMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQD
+        AgNIADBFAiBVcywTj017jKpAQ+gwy4MqK2hQvzve6lkvQkgSP6ykHwIhAI0KFwCD
+        jnPbmcJsG41hUrWNlf+IcrMvFuYii0DasBNi
+        -----END CERTIFICATE-----
+    """.trimIndent(),
+    // Symantec RSP Test Root CA (CA: DigiCert)
+    "665a1433d67c1a2c5db8b52c967f10a057ba5cb2" to """
+        -----BEGIN CERTIFICATE-----
+        MIICkDCCAjagAwIBAgIQPfCO5OYL+cdbbx2ETDO7DDAKBggqhkjOPQQDAjBoMR0w
+        GwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjFHMEUGA1UEAxM+U3ltYW50ZWMg
+        Q29ycG9yYXRpb24gUlNQIFRlc3QgUm9vdCBDQSAtIEZvciBUZXN0IFB1cnBvc2Vz
+        IE9ubHkwHhcNMTcwNzExMDAwMDAwWhcNNDkxMjMxMjM1OTU5WjBoMR0wGwYDVQQK
+        ExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjFHMEUGA1UEAxM+U3ltYW50ZWMgQ29ycG9y
+        YXRpb24gUlNQIFRlc3QgUm9vdCBDQSAtIEZvciBUZXN0IFB1cnBvc2VzIE9ubHkw
+        WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQlbEYt9PTmdWcaX5WC68SYTFyZcbBN
+        vFpJW6bZQpERlMIAuzEpgscbTDccHtNpDqJwMqZXCO7ebCmRLyI6jqe3o4HBMIG+
+        MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MBcGA1UdIAEB/wQNMAsw
+        CQYHZ4ESAQIBADBPBgNVHR8ESDBGMESgQqBAhj5odHRwOi8vcGtpLWNybC5zeW1h
+        dXRoLmNvbS9TeW1hbnRlY1JTUFRlc3RSb290Q0EvTGF0ZXN0Q1JMLmNybDASBgNV
+        HREECzAJiAcrBgEEAYMJMB0GA1UdDgQWBBRmWhQz1nwaLF24tSyWfxCgV7pcsjAK
+        BggqhkjOPQQDAgNIADBFAiAQ1quTqcexvDnKvmAkqoQP09QMXAXxlCyma82NtrYq
+        UQIhAP/W6pRamBGhSliV+EancgbZj+VoOkKdj0o7sP/cKdhZ
+        -----END CERTIFICATE-----
+    """.trimIndent(),
+    // GSMA Test CI
+    // Specs: SGP.26 v1
+    "f54172bdf98a95d65cbeb88a38a1c11d800a85c3" to """
+        -----BEGIN CERTIFICATE-----
+        MIICVjCCAfugAwIBAgIJALh086v6bETTMAoGCCqGSM49BAMCMEkxFTATBgNVBAMM
+        DEdTTUEgVGVzdCBDSTERMA8GA1UECwwIVEVTVENFUlQxEDAOBgNVBAoMB1JTUFRF
+        U1QxCzAJBgNVBAYTAklUMCAXDTE3MDIwMTE1Mzk0MloYDzIwNTIwMjAxMTUzOTQy
+        WjBJMRUwEwYDVQQDDAxHU01BIFRlc3QgQ0kxETAPBgNVBAsMCFRFU1RDRVJUMRAw
+        DgYDVQQKDAdSU1BURVNUMQswCQYDVQQGEwJJVDBZMBMGByqGSM49AgEGCCqGSM49
+        AwEHA0IABJQGV6Zz3CiPidUuqKR3BJknkfnDSwA25jPi0MupRU1l2zLrF5gXmdLy
+        Q4juK5XBCUVGyXkBzq66llCRmi4g0imjgckwgcYwHQYDVR0OBBYEFPVBcr35ipXW
+        XL64ijihwR2ACoXDMA8GA1UdEwEB/wQFMAMBAf8wFwYDVR0gAQH/BA0wCzAJBgdn
+        gRIBAgEAMA4GA1UdDwEB/wQEAwIBBjAOBgNVHREEBzAFiAOINwEwWwYDVR0fBFQw
+        UjAnoCWgI4YhaHR0cDovL2NpLnRlc3QuZ3NtYS5jb20vQ1JMLUEuY3JsMCegJaAj
+        hiFodHRwOi8vY2kudGVzdC5nc21hLmNvbS9DUkwtQi5jcmwwCgYIKoZIzj0EAwID
+        SQAwRgIhAJHyUclxU7nPhTeadItXKkloUkVWxH8z62l7VZEswPLSAiEA3OSec/NJ
+        7NBZEO+d9raahnq/OJ3Ia4QRtN/hlpFQ9fk=
+        -----END CERTIFICATE-----
+    """.trimIndent(),
+    "c0bc70ba36929d43b467ff57570530e57ab8fcd8" to """
+        -----BEGIN CERTIFICATE-----
+        MIICVTCCAfygAwIBAgIJALh086v6bETTMAoGCCqGSM49BAMCMEkxFTATBgNVBAMM
+        DEdTTUEgVGVzdCBDSTERMA8GA1UECwwIVEVTVENFUlQxEDAOBgNVBAoMB1JTUFRF
+        U1QxCzAJBgNVBAYTAklUMCAXDTE3MDQxOTEwMzQzOFoYDzIwNTIwNDE4MTAzNDM4
+        WjBJMRUwEwYDVQQDDAxHU01BIFRlc3QgQ0kxETAPBgNVBAsMCFRFU1RDRVJUMRAw
+        DgYDVQQKDAdSU1BURVNUMQswCQYDVQQGEwJJVDBaMBQGByqGSM49AgEGCSskAwMC
+        CAEBBwNCAAQnh7TVbtgkqea+BOGIf2uYuv64x2P1KDf3ARPpNk7dvhObwMfefhaP
+        HlzQwYU0/FBk5k9obcUzJ8p/Hc6oWimUo4HJMIHGMB0GA1UdDgQWBBTAvHC6NpKd
+        Q7Rn/1dXBTDlerj82DAPBgNVHRMBAf8EBTADAQH/MBcGA1UdIAEB/wQNMAswCQYH
+        Z4ESAQIBADAOBgNVHQ8BAf8EBAMCAQYwDgYDVR0RBAcwBYgDiDcBMFsGA1UdHwRU
+        MFIwJ6AloCOGIWh0dHA6Ly9jaS50ZXN0LmdzbWEuY29tL0NSTC1BLmNybDAnoCWg
+        I4YhaHR0cDovL2NpLnRlc3QuZ3NtYS5jb20vQ1JMLUIuY3JsMAoGCCqGSM49BAMC
+        A0cAMEQCIAbGw59auXFdcsVl3PX/O/7z+3DvCuM6BzZmkTWG0O+SAiAWskTrRp8q
+        L1hrwcMgtLZDG4902UH38YMrQyVSsWHfog==
+        -----END CERTIFICATE-----
+    """.trimIndent(),
+    // Test CI
+    // Specs: SGP.26 v3
+    "34eecf13156518d48d30bdf06853404d115f955d" to """
+        -----BEGIN CERTIFICATE-----
+        MIIB4zCCAYqgAwIBAgIBADAKBggqhkjOPQQDAjBEMRAwDgYDVQQDDAdUZXN0IENJ
+        MREwDwYDVQQLDAhURVNUQ0VSVDEQMA4GA1UECgwHUlNQVEVTVDELMAkGA1UEBhMC
+        SVQwIBcNMjMwNTMxMTI1MDI4WhgPMjA1ODA1MzAxMjUwMjhaMEQxEDAOBgNVBAMM
+        B1Rlc3QgQ0kxETAPBgNVBAsMCFRFU1RDRVJUMRAwDgYDVQQKDAdSU1BURVNUMQsw
+        CQYDVQQGEwJJVDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLlQW4kHaMJSrAK4
+        nVKjGIgKWYxick+Y1x0MKO/Bsb3+KxMdnAObkPZjLosKlKCnH2bHUHhqRyDDSc2Y
+        9+wB6A6jazBpMB0GA1UdDgQWBBQ07s8TFWUY1I0wvfBoU0BNEV+VXTAOBgNVHQ8B
+        Af8EBAMCAQYwFwYDVR0gAQH/BA0wCzAJBgdngRIBAgEAMA8GA1UdEwEB/wQFMAMB
+        Af8wDgYDVR0RBAcwBYgDiDcBMAoGCCqGSM49BAMCA0cAMEQCIEuYVB+bwdn5Z6sL
+        eKFS07FnvHY03QqDm8XYxdjDAxZuAiBneNr+fBYeqDulQWfrGXFLDTbsFBENNdDj
+        jvcHgHpATQ==
+        -----END CERTIFICATE-----
+    """.trimIndent(),
+    "2209f61cd9ec5c9c854e787341ff83ecf9776a5b" to """
+        -----BEGIN CERTIFICATE-----
+        MIIB5DCCAYugAwIBAgIBADAKBggqhkjOPQQDAjBEMRAwDgYDVQQDDAdUZXN0IENJ
+        MREwDwYDVQQLDAhURVNUQ0VSVDEQMA4GA1UECgwHUlNQVEVTVDELMAkGA1UEBhMC
+        SVQwIBcNMjMwNjAyMTMwNTQzWhgPMjA1ODA2MDExMzA1NDNaMEQxEDAOBgNVBAMM
+        B1Rlc3QgQ0kxETAPBgNVBAsMCFRFU1RDRVJUMRAwDgYDVQQKDAdSU1BURVNUMQsw
+        CQYDVQQGEwJJVDBaMBQGByqGSM49AgEGCSskAwMCCAEBBwNCAASF7cCXanl/xSJe
+        PwIeEUeZk4zPPM3iE16JbpOWPqPXaJwGmMKvHwQlRxiLtPWrRBalgkzrr4RgYIqD
+        aTcnvxoFo2swaTAdBgNVHQ4EFgQUIgn2HNnsXJyFTnhzQf+D7Pl3alswDgYDVR0P
+        AQH/BAQDAgEGMBcGA1UdIAEB/wQNMAswCQYHZ4ESAQIBADAPBgNVHRMBAf8EBTAD
+        AQH/MA4GA1UdEQQHMAWIA4g3ATAKBggqhkjOPQQDAgNHADBEAiBLLHbhrIvy1Cue
+        7lDUlQZY2EOK7/I/o2CQO0pj76OqzQIgTQ+kE02RPbMuflDbXKRuVDKFvfZ/vHEW
+        QKvBPWehIXI=
+        -----END CERTIFICATE-----
+    """.trimIndent()
+)

+ 20 - 2
libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c

@@ -53,7 +53,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
 
 
     euicc_info2_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/EuiccInfo2");
     euicc_info2_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/EuiccInfo2");
     euicc_info2_class = (*env)->NewGlobalRef(env, euicc_info2_class);
     euicc_info2_class = (*env)->NewGlobalRef(env, euicc_info2_class);
-    euicc_info2_constructor = (*env)->GetMethodID(env, euicc_info2_class, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II)V");
+    euicc_info2_constructor = (*env)->GetMethodID(env, euicc_info2_class, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II[Ljava/lang/String;[Ljava/lang/String;)V");
 
 
     const char _unused[1];
     const char _unused[1];
     empty_string = (*env)->NewString(env, _unused, 0);
     empty_string = (*env)->NewString(env, _unused, 0);
@@ -262,12 +262,16 @@ JNIEXPORT jobject JNICALL
 Java_net_typeblog_lpac_1jni_LpacJni_es10cexGetEuiccInfo2(JNIEnv *env, jobject thiz, jlong handle) {
 Java_net_typeblog_lpac_1jni_LpacJni_es10cexGetEuiccInfo2(JNIEnv *env, jobject thiz, jlong handle) {
     struct euicc_ctx *ctx = (struct euicc_ctx *) handle;
     struct euicc_ctx *ctx = (struct euicc_ctx *) handle;
     struct es10cex_euiccinfo2 * info;
     struct es10cex_euiccinfo2 * info;
+    jobjectArray euiccCiPKIdListForVerification = NULL;
+    jobjectArray euiccCiPKIdListForSigning = NULL;
     jstring sas_accreditation_number = NULL;
     jstring sas_accreditation_number = NULL;
     jstring global_platform_version = NULL;
     jstring global_platform_version = NULL;
     jstring euicc_firmware_version = NULL;
     jstring euicc_firmware_version = NULL;
     jstring profile_version = NULL;
     jstring profile_version = NULL;
     jstring pp_version = NULL;
     jstring pp_version = NULL;
     jobject ret = NULL;
     jobject ret = NULL;
+    char **curr = NULL;
+    int count = 0;
 
 
     if (es10cex_get_euiccinfo2(ctx, &info) < 0)
     if (es10cex_get_euiccinfo2(ctx, &info) < 0)
         goto out;
         goto out;
@@ -278,12 +282,26 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cexGetEuiccInfo2(JNIEnv *env, jobject th
     sas_accreditation_number = toJString(env, info->sasAcreditationNumber);
     sas_accreditation_number = toJString(env, info->sasAcreditationNumber);
     pp_version = toJString(env, info->ppVersion);
     pp_version = toJString(env, info->ppVersion);
 
 
+    count = LPAC_JNI_NULL_TERM_LIST_COUNT(info->euiccCiPKIdListForSigning, curr);
+    euiccCiPKIdListForSigning = (*env)->NewObjectArray(env, count, string_class, NULL);
+    LPAC_JNI_NULL_TERM_LIST_FOREACH(info->euiccCiPKIdListForSigning, curr, {
+        (*env)->SetObjectArrayElement(env, euiccCiPKIdListForSigning, i, toJString(env, *curr));
+    });
+
+    count = LPAC_JNI_NULL_TERM_LIST_COUNT(info->euiccCiPKIdListForVerification, curr);
+    euiccCiPKIdListForVerification = (*env)->NewObjectArray(env, count, string_class, NULL);
+    LPAC_JNI_NULL_TERM_LIST_FOREACH(info->euiccCiPKIdListForVerification, curr, {
+        (*env)->SetObjectArrayElement(env, euiccCiPKIdListForVerification, i, toJString(env, *curr));
+    });
+
     ret = (*env)->NewObject(env, euicc_info2_class, euicc_info2_constructor,
     ret = (*env)->NewObject(env, euicc_info2_class, euicc_info2_constructor,
                             profile_version, euicc_firmware_version,
                             profile_version, euicc_firmware_version,
                             global_platform_version,
                             global_platform_version,
                             sas_accreditation_number, pp_version,
                             sas_accreditation_number, pp_version,
                             info->extCardResource.freeNonVolatileMemory,
                             info->extCardResource.freeNonVolatileMemory,
-                            info->extCardResource.freeVolatileMemory);
+                            info->extCardResource.freeVolatileMemory,
+                            euiccCiPKIdListForSigning,
+                            euiccCiPKIdListForVerification);
 
 
     out:
     out:
     (*env)->DeleteLocalRef(env, profile_version);
     (*env)->DeleteLocalRef(env, profile_version);

+ 15 - 0
libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h

@@ -28,6 +28,21 @@ struct lpac_jni_ctx {
 #define LPAC_JNI_LINKED_LIST_COUNT(list, curr) \
 #define LPAC_JNI_LINKED_LIST_COUNT(list, curr) \
     (__LPAC_JNI_LINKED_LIST_FOREACH(list, curr, {}, i))
     (__LPAC_JNI_LINKED_LIST_FOREACH(list, curr, {}, i))
 
 
+#define __LPAC_JNI_NULL_TERM_LIST_FOREACH(list, curr, body, after) { \
+    int i = 0;                                                       \
+    curr = list;                                                     \
+    while (*curr != NULL) {                                           \
+        body;                                                        \
+        curr++;                                                      \
+        i++;                                                         \
+    };                                                               \
+    after;                                                           \
+}
+#define LPAC_JNI_NULL_TERM_LIST_FOREACH(list, curr, body) \
+    __LPAC_JNI_NULL_TERM_LIST_FOREACH(list, curr, body, {})
+#define LPAC_JNI_NULL_TERM_LIST_COUNT(list, curr) \
+    (__LPAC_JNI_NULL_TERM_LIST_FOREACH(list, curr, {}, i))
+
 extern JavaVM *jvm;
 extern JavaVM *jvm;
 extern jclass string_class;
 extern jclass string_class;