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

Fix race condition

parent 5a654299
Pipeline #10560288 passed with stage
in 1 minute and 51 seconds
......@@ -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
......
......@@ -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)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment