Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2303f98d authored by Ricki Hirner's avatar Ricki Hirner
Browse files

Fix race condition

parent 5a654299
Loading
Loading
Loading
Loading
+21 −14
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@ import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.*
import android.util.SparseArray
import android.util.SparseBooleanArray
import java.io.Closeable
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
@@ -39,7 +39,8 @@ class CustomCertManager: X509TrustManager, Closeable {
        var SERVICE_TIMEOUT: Long = 5*60*1000

        val nextDecisionID = AtomicInteger()
        val decisions = SparseArray<Boolean?>()

        val decisions = SparseBooleanArray()
        val decisionLock = Object()

        /** thread to receive replies from {@link CustomCertService} */
@@ -51,15 +52,12 @@ class CustomCertManager: X509TrustManager, Closeable {
        /** messenger to receive replies from {@link CustomCertService} */
        val messenger = Messenger(Handler(messengerThread.looper, MessageHandler()))

        @JvmField
        val MSG_CERTIFICATE_DECISION = 0

        // Messenger for receiving replies from CustomCertificateService
        private class MessageHandler: Handler.Callback {
            override fun handleMessage(msg: Message): Boolean {
                Constants.log.fine("Received reply from CustomCertificateService: " + msg)
                return when (msg.what) {
                    MSG_CERTIFICATE_DECISION ->
                    CustomCertService.MSG_CERTIFICATE_DECISION ->
                        synchronized(decisionLock) {
                            decisions.put(msg.arg1, msg.arg2 != 0)
                            decisionLock.notifyAll()
@@ -127,7 +125,7 @@ class CustomCertManager: X509TrustManager, Closeable {
    constructor(context: Context, trustSystemCerts: Boolean): this(context, trustSystemCerts, null)

    override fun close() {
        serviceConnection?.let(context::unbindService)
        serviceConnection?.let { context.unbindService(it) }
    }


@@ -164,13 +162,14 @@ class CustomCertManager: X509TrustManager, Closeable {
    }

    internal fun checkCustomTrusted(cert: X509Certificate) {
        Constants.log.fine("Querying custom certificate trustworthiness")
        val decisionID = nextDecisionID.getAndIncrement()
        Constants.log.fine("Querying custom certificate trustworthiness (expecting decision $decisionID)")

        val service : Messenger = this.service ?: throw CertificateException("Custom certificate service not available")

        var msg = Message.obtain()
        msg.what = CustomCertService.MSG_CHECK_TRUSTED
        msg.arg1 = nextDecisionID.getAndIncrement()
        msg.arg1 = decisionID
        val id = msg.arg1
        msg.replyTo = messenger

@@ -185,15 +184,23 @@ class CustomCertManager: X509TrustManager, Closeable {
            throw CertificateException("Couldn't query custom certificate trustworthiness", e)
        }

        // wait for a reply
        val startTime = System.currentTimeMillis()
        synchronized(decisionLock) {
            while (System.currentTimeMillis() < startTime + SERVICE_TIMEOUT) {
            var idx = decisions.indexOfKey(id)

            // wait for a reply for up to SERVICE_TIMEOUT milliseconds, if necessary
            val startTime = System.currentTimeMillis()
            while (idx < 0 && System.currentTimeMillis() < startTime + SERVICE_TIMEOUT) {
                Constants.log.finer("Waiting for reply from service (decision $id)")
                try {
                    decisionLock.wait(SERVICE_TIMEOUT)
                } catch(e: InterruptedException) {
                }
                decisions.get(id)?.let { decision ->
                idx = decisions.indexOfKey(id)
            }

            if (idx >= 0) {
                Constants.log.finer("Decision $id received from service")
                decisions.valueAt(idx).let { decision ->
                    decisions.delete(id)
                    if (decision)
                        // certificate trusted
@@ -204,7 +211,7 @@ class CustomCertManager: X509TrustManager, Closeable {
            }
        }

        // timeout occurred, send cancellation
        Constants.log.finer("Timeout for decision $id, sending cancellation to service")
        msg = Message.obtain()
        msg.what = CustomCertService.MSG_CHECK_TRUSTED_ABORT
        msg.arg1 = id
+17 −7
Original line number Diff line number Diff line
@@ -12,7 +12,10 @@ import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.*
import android.os.Handler
import android.os.Message
import android.os.Messenger
import android.os.RemoteException
import android.support.v4.app.NotificationManagerCompat
import android.support.v7.app.NotificationCompat
import android.widget.Toast
@@ -44,12 +47,15 @@ class CustomCertService: Service() {


        // bound service; Messenger for IPC

        val MSG_CHECK_TRUSTED = 1
        val MSG_DATA_CERTIFICATE = "certificate"
        val MSG_DATA_APP_IN_FOREGROUND ="appInForeground"

        val MSG_CHECK_TRUSTED_ABORT = 2

        // reply messages sent by service
        val MSG_CERTIFICATE_DECISION = 0

    }

    var keyStoreFile: File? = null
@@ -69,7 +75,7 @@ class CustomCertService: Service() {
        try {
            FileInputStream(keyStoreFile).use { trustedKeyStore.load(it, null) }
        } catch(e: Exception) {
            Constants.log.log(Level.INFO, "No persisent key store (yet), creating in-memory key store", e)
            Constants.log.log(Level.INFO, "No persistent key store (yet), creating in-memory key store", e)
            try {
                trustedKeyStore.load(null, null)
            } catch(e: Exception) {
@@ -138,10 +144,11 @@ class CustomCertService: Service() {
        pendingDecisions[cert]?.let { receivers ->
            for ((messenger, id) in receivers) {
                val message = Message.obtain()
                message.what = CustomCertManager.MSG_CERTIFICATE_DECISION
                message.what = MSG_CERTIFICATE_DECISION
                message.arg1 = id
                message.arg2 = if (trusted) 1 else 0
                try {
                    Constants.log.finer("Sending user decision $id (trusted: $trusted) to manager")
                    messenger.send(message)
                } catch(e: RemoteException) {
                    Constants.log.log(Level.WARNING, "Couldn't forward decision to CustomCertManager", e)
@@ -200,9 +207,9 @@ class CustomCertService: Service() {
                     */
                    when {
                        service.untrustedCerts.contains(cert) -> {
                            Constants.log.fine("Certificate is cached as untrusted")
                            Constants.log.fine("Certificate is cached as untrusted, rejecting decision $id")
                            try {
                                msg.replyTo.send(obtainMessage(CustomCertManager.MSG_CERTIFICATE_DECISION, id, 0))
                                msg.replyTo.send(obtainMessage(MSG_CERTIFICATE_DECISION, id, 0))
                            } catch(e: RemoteException) {
                                Constants.log.log(Level.WARNING, "Couldn't send distrust information to CustomCertManager", e)
                            }
@@ -210,12 +217,15 @@ class CustomCertService: Service() {
                        }
                        service.inTrustStore(cert) -> {
                            try {
                                msg.replyTo.send(obtainMessage(CustomCertManager.MSG_CERTIFICATE_DECISION, id, 1))
                                Constants.log.fine("Certificate is cached as trusted, accepting decision $id")
                                msg.replyTo.send(obtainMessage(MSG_CERTIFICATE_DECISION, id, 1))
                            } catch(e: RemoteException) {
                                Constants.log.log(Level.WARNING, "Couldn't send trust information to CustomCertManager", e)
                            }
                        }
                        else -> {
                            Constants.log.fine("Certificate is not known, user decision required $id")

                            val receivers = LinkedList<ReplyInfo>()
                            receivers += replyInfo
                            service.pendingDecisions.put(cert, receivers)