Loading src/main/java/at/bitfire/cert4android/CustomCertManager.kt +21 −14 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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} */ Loading @@ -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() Loading Loading @@ -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) } } Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading src/main/java/at/bitfire/cert4android/CustomCertService.kt +17 −7 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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) { Loading Loading @@ -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) Loading Loading @@ -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) } Loading @@ -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) Loading Loading
src/main/java/at/bitfire/cert4android/CustomCertManager.kt +21 −14 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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} */ Loading @@ -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() Loading Loading @@ -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) } } Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading
src/main/java/at/bitfire/cert4android/CustomCertService.kt +17 −7 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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) { Loading Loading @@ -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) Loading Loading @@ -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) } Loading @@ -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) Loading