mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-12 18:23:40 +00:00
Moved pairing logic into dialog.
Refactor.
This commit is contained in:
parent
80e9b8223a
commit
373b9b38f6
@ -328,7 +328,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
|||||||
QRCodeDialog.INSTANCE.show(getContext());
|
QRCodeDialog.INSTANCE.show(getContext());
|
||||||
break;
|
break;
|
||||||
case PREFERENCE_CATEGORY_LINK_DEVICE:
|
case PREFERENCE_CATEGORY_LINK_DEVICE:
|
||||||
DeviceLinkingDialog.INSTANCE.show(getContext(), DeviceLinkingView.Mode.Master);
|
DeviceLinkingDialog.Companion.show(getContext(), DeviceLinkingView.Mode.Master);
|
||||||
break;
|
break;
|
||||||
case PREFERENCE_CATEGORY_SEED:
|
case PREFERENCE_CATEGORY_SEED:
|
||||||
File languageFileDirectory = new File(getContext().getApplicationInfo().dataDir);
|
File languageFileDirectory = new File(getContext().getApplicationInfo().dataDir);
|
||||||
|
@ -1,148 +1,61 @@
|
|||||||
package org.thoughtcrime.securesms.loki
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.PorterDuff
|
|
||||||
import android.os.Handler
|
|
||||||
import android.support.v7.app.AlertDialog
|
import android.support.v7.app.AlertDialog
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import kotlinx.android.synthetic.main.view_device_linking.view.*
|
|
||||||
import network.loki.messenger.R
|
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.logging.Log
|
||||||
import org.w3c.dom.Text
|
|
||||||
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkingSession
|
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkingSession
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkingSessionListener
|
||||||
import org.whispersystems.signalservice.loki.api.LokiPairingAuthorisation
|
import org.whispersystems.signalservice.loki.api.LokiPairingAuthorisation
|
||||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
|
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
|
||||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
|
||||||
import org.whispersystems.signalservice.loki.utilities.removing05PrefixIfNeeded
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
|
|
||||||
object DeviceLinkingDialog {
|
class DeviceLinkingDialog private constructor(private val context: Context, private val mode: DeviceLinkingView.Mode, private val delegate: DeviceLinkingDialogDelegate? = null): DeviceLinkingViewDelegate, LokiDeviceLinkingSessionListener {
|
||||||
|
private lateinit var view: DeviceLinkingView
|
||||||
|
|
||||||
fun show(context: Context, mode: DeviceLinkingView.Mode) {
|
private val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
||||||
val view = DeviceLinkingView(context, mode)
|
|
||||||
|
companion object {
|
||||||
|
fun show(context: Context, mode: DeviceLinkingView.Mode) { show(context, mode, null) }
|
||||||
|
fun show(context: Context, mode: DeviceLinkingView.Mode, delegate: DeviceLinkingDialogDelegate?) {
|
||||||
|
val dialog = DeviceLinkingDialog(context, mode, delegate)
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun show() {
|
||||||
|
view = DeviceLinkingView(context, mode, this)
|
||||||
val dialog = AlertDialog.Builder(context).setView(view).show()
|
val dialog = AlertDialog.Builder(context).setView(view).show()
|
||||||
view.dismiss = { dialog.dismiss() }
|
view.dismiss = {
|
||||||
}
|
this.stopListening()
|
||||||
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeviceLinkingView private constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, private val mode: Mode) : LinearLayout(context, attrs, defStyleAttr) {
|
this.startListening()
|
||||||
private var delegate: DeviceLinkingDialogDelegate? = null
|
|
||||||
private lateinit var languageFileDirectory: File
|
|
||||||
var dismiss: (() -> Unit)? = null
|
|
||||||
private var pairingAuthorisation: LokiPairingAuthorisation? = null
|
|
||||||
|
|
||||||
// region Types
|
|
||||||
enum class Mode { Master, Slave }
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Lifecycle
|
|
||||||
constructor(context: Context, mode: Mode) : this(context, null, 0, mode)
|
|
||||||
private constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0, Mode.Master) // Just pass in a dummy mode
|
|
||||||
private constructor(context: Context) : this(context, null)
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (mode == Mode.Slave) {
|
|
||||||
if (delegate == null) { throw IllegalStateException("Missing delegate for device linking dialog in slave mode.") }
|
|
||||||
}
|
}
|
||||||
setUpLanguageFileDirectory()
|
|
||||||
setUpViewHierarchy()
|
// region Private functions
|
||||||
|
private fun startListening() {
|
||||||
LokiDeviceLinkingSession.shared.startListeningForLinkingRequests()
|
LokiDeviceLinkingSession.shared.startListeningForLinkingRequests()
|
||||||
|
LokiDeviceLinkingSession.shared.addListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUpLanguageFileDirectory() {
|
private fun stopListening() {
|
||||||
val languages = listOf( "english", "japanese", "portuguese", "spanish" )
|
LokiDeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
||||||
val directory = File(context.applicationInfo.dataDir)
|
LokiDeviceLinkingSession.shared.removeListener(this)
|
||||||
for (language in languages) {
|
|
||||||
val fileName = "$language.txt"
|
|
||||||
if (directory.list().contains(fileName)) { continue }
|
|
||||||
val inputStream = context.assets.open("mnemonic/$fileName")
|
|
||||||
val file = File(directory, fileName)
|
|
||||||
val outputStream = FileOutputStream(file)
|
|
||||||
val buffer = ByteArray(1024)
|
|
||||||
while (true) {
|
|
||||||
val count = inputStream.read(buffer)
|
|
||||||
if (count < 0) { break }
|
|
||||||
outputStream.write(buffer, 0, count)
|
|
||||||
}
|
|
||||||
inputStream.close()
|
|
||||||
outputStream.close()
|
|
||||||
}
|
|
||||||
languageFileDirectory = directory
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setUpViewHierarchy() {
|
|
||||||
inflate(context, R.layout.view_device_linking, this)
|
|
||||||
spinner.indeterminateDrawable.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)
|
|
||||||
val titleID = when (mode) {
|
|
||||||
Mode.Master -> R.string.view_device_linking_title_1
|
|
||||||
Mode.Slave -> R.string.view_device_linking_title_2
|
|
||||||
}
|
|
||||||
titleTextView.text = resources.getString(titleID)
|
|
||||||
val explanationID = when (mode) {
|
|
||||||
Mode.Master -> R.string.view_device_linking_explanation_1
|
|
||||||
Mode.Slave -> R.string.view_device_linking_explanation_2
|
|
||||||
}
|
|
||||||
explanationTextView.text = resources.getString(explanationID)
|
|
||||||
mnemonicTextView.visibility = if (mode == Mode.Master) View.GONE else View.VISIBLE
|
|
||||||
if (mode == Mode.Slave) {
|
|
||||||
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context).removing05PrefixIfNeeded()
|
|
||||||
mnemonicTextView.text = MnemonicCodec(languageFileDirectory).encode(hexEncodedPublicKey).split(" ").slice(0 until 3).joinToString(" ")
|
|
||||||
}
|
|
||||||
authorizeButton.visibility = View.GONE
|
|
||||||
authorizeButton.setOnClickListener { authorizeDeviceLink() }
|
|
||||||
cancelButton.setOnClickListener { cancel() }
|
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Device Linking
|
// region Dialog View Delegate
|
||||||
private fun requestUserAuthorization(authorisation: LokiPairingAuthorisation) {
|
override fun authorise(pairing: LokiPairingAuthorisation): Boolean {
|
||||||
// To be called by DeviceLinkingSession when a linking request has been received
|
val signedAuthorisation = pairing.sign(LokiPairingAuthorisation.Type.GRANT, userPrivateKey)
|
||||||
if (this.pairingAuthorisation != null) {
|
|
||||||
Log.e("Loki", "Received request for another pairing authorisation when one was active")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!authorisation.verify()) {
|
|
||||||
Log.w("Loki", "Received authorisation but it was not valid.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pairingAuthorisation = authorisation
|
|
||||||
|
|
||||||
// Stop listening to any more requests
|
|
||||||
LokiDeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
|
||||||
|
|
||||||
spinner.visibility = View.GONE
|
|
||||||
val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams
|
|
||||||
titleTextViewLayoutParams.topMargin = toPx(16, resources)
|
|
||||||
titleTextView.layoutParams = titleTextViewLayoutParams
|
|
||||||
titleTextView.text = resources.getString(R.string.view_device_linking_title_3)
|
|
||||||
explanationTextView.text = resources.getString(R.string.view_device_linking_explanation_2)
|
|
||||||
mnemonicTextView.visibility = View.VISIBLE
|
|
||||||
val hexEncodedPublicKey = authorisation.secondaryDevicePubKey.removing05PrefixIfNeeded()
|
|
||||||
mnemonicTextView.text = MnemonicCodec(languageFileDirectory).encode(hexEncodedPublicKey).split(" ").slice(0 until 3).joinToString(" ")
|
|
||||||
authorizeButton.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun authorizeDeviceLink() {
|
|
||||||
if (pairingAuthorisation == null) { return; }
|
|
||||||
|
|
||||||
val authorisation = pairingAuthorisation!!
|
|
||||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
|
||||||
val signedAuthorisation = authorisation.sign(LokiPairingAuthorisation.Type.GRANT, userPrivateKey)
|
|
||||||
if (signedAuthorisation == null) {
|
if (signedAuthorisation == null) {
|
||||||
Log.e("Loki", "Failed to sign grant authorisation")
|
Log.e("Loki", "Failed to sign grant authorisation")
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Send authorisation message
|
// Send authorisation message
|
||||||
|
sendAuthorisationMessage(context, pairing.secondaryDevicePubKey, signedAuthorisation)
|
||||||
|
|
||||||
// Add the auth to the database
|
// Add the auth to the database
|
||||||
DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(signedAuthorisation)
|
DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(signedAuthorisation)
|
||||||
@ -150,49 +63,38 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe
|
|||||||
// Update the api
|
// Update the api
|
||||||
LokiStorageAPI.shared?.updateOurDeviceMappings()
|
LokiStorageAPI.shared?.updateOurDeviceMappings()
|
||||||
|
|
||||||
dismiss()
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDeviceLinkAuthorized() { // TODO: deviceLink parameter
|
override fun handleDeviceLinkAuthorized() {
|
||||||
// To be called by DeviceLinkingSession when a device link has been authorized
|
|
||||||
// Pairings get automatically added to the database when we receive them
|
|
||||||
LokiDeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
|
||||||
|
|
||||||
spinner.visibility = View.GONE
|
|
||||||
val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams
|
|
||||||
titleTextViewLayoutParams.topMargin = toPx(8, resources)
|
|
||||||
titleTextView.layoutParams = titleTextViewLayoutParams
|
|
||||||
titleTextView.text = resources.getString(R.string.view_device_linking_title_4)
|
|
||||||
val explanationTextViewLayoutParams = explanationTextView.layoutParams as LayoutParams
|
|
||||||
explanationTextViewLayoutParams.bottomMargin = toPx(12, resources)
|
|
||||||
explanationTextView.layoutParams = explanationTextViewLayoutParams
|
|
||||||
explanationTextView.text = resources.getString(R.string.view_device_linking_explanation_3)
|
|
||||||
titleTextView.text = resources.getString(R.string.view_device_linking_title_4)
|
|
||||||
mnemonicTextView.visibility = View.GONE
|
|
||||||
buttonContainer.visibility = View.GONE
|
|
||||||
|
|
||||||
Handler().postDelayed({
|
|
||||||
delegate?.handleDeviceLinkAuthorized()
|
delegate?.handleDeviceLinkAuthorized()
|
||||||
dismiss()
|
|
||||||
}, 4000)
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Interaction
|
|
||||||
private fun dismiss() {
|
|
||||||
LokiDeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
|
||||||
dismiss?.invoke()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancel() {
|
override fun handleDeviceLinkingDialogDismissed() {
|
||||||
if (mode == Mode.Master && pairingAuthorisation != null) {
|
// If we cancelled while we were listening for requests on main device, we need to remove any pre key bundles
|
||||||
val authorisation = pairingAuthorisation!!
|
if (mode == DeviceLinkingView.Mode.Master && view.pairingAuthorisation != null) {
|
||||||
|
val authorisation = view.pairingAuthorisation!!
|
||||||
// Remove pre key bundle from the requesting device
|
// Remove pre key bundle from the requesting device
|
||||||
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(authorisation.secondaryDevicePubKey)
|
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(authorisation.secondaryDevicePubKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate?.handleDeviceLinkingDialogDismissed() // Only relevant in slave mode
|
delegate?.handleDeviceLinkingDialogDismissed()
|
||||||
dismiss()
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Loki Device Session Listener
|
||||||
|
override fun onDeviceLinkingRequestReceived(authorisation: LokiPairingAuthorisation) {
|
||||||
|
view.requestUserAuthorization(authorisation)
|
||||||
|
|
||||||
|
// Stop listening to any more requests
|
||||||
|
LokiDeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeviceLinkRequestAccepted(authorisation: LokiPairingAuthorisation) {
|
||||||
|
view.onDeviceLinkAuthorized(authorisation)
|
||||||
|
|
||||||
|
// Stop listening to any more requests
|
||||||
|
LokiDeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
@ -1,7 +1,12 @@
|
|||||||
package org.thoughtcrime.securesms.loki
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
interface DeviceLinkingDialogDelegate {
|
import org.whispersystems.signalservice.loki.api.LokiPairingAuthorisation
|
||||||
|
|
||||||
fun handleDeviceLinkAuthorized() // TODO: Device link
|
interface DeviceLinkingDialogDelegate {
|
||||||
fun handleDeviceLinkingDialogDismissed()
|
fun handleDeviceLinkAuthorized() {}
|
||||||
|
fun handleDeviceLinkingDialogDismissed() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeviceLinkingViewDelegate: DeviceLinkingDialogDelegate {
|
||||||
|
fun authorise(pairing: LokiPairingAuthorisation): Boolean { return false }
|
||||||
}
|
}
|
165
src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt
Normal file
165
src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import kotlinx.android.synthetic.main.view_device_linking.view.*
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional
|
||||||
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||||
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkingSession
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiPairingAuthorisation
|
||||||
|
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
||||||
|
import org.whispersystems.signalservice.loki.utilities.removing05PrefixIfNeeded
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
|
class DeviceLinkingView private constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, private val mode: Mode, private var delegate: DeviceLinkingViewDelegate) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
private lateinit var languageFileDirectory: File
|
||||||
|
var dismiss: (() -> Unit)? = null
|
||||||
|
var pairingAuthorisation: LokiPairingAuthorisation? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
// region Types
|
||||||
|
enum class Mode { Master, Slave }
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Lifecycle
|
||||||
|
constructor(context: Context, mode: Mode, delegate: DeviceLinkingViewDelegate) : this(context, null, 0, mode, delegate)
|
||||||
|
private constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0, Mode.Master, object : DeviceLinkingViewDelegate {}) // Just pass in a dummy mode
|
||||||
|
private constructor(context: Context) : this(context, null)
|
||||||
|
|
||||||
|
init {
|
||||||
|
setUpLanguageFileDirectory()
|
||||||
|
setUpViewHierarchy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUpLanguageFileDirectory() {
|
||||||
|
val languages = listOf( "english", "japanese", "portuguese", "spanish" )
|
||||||
|
val directory = File(context.applicationInfo.dataDir)
|
||||||
|
for (language in languages) {
|
||||||
|
val fileName = "$language.txt"
|
||||||
|
if (directory.list().contains(fileName)) { continue }
|
||||||
|
val inputStream = context.assets.open("mnemonic/$fileName")
|
||||||
|
val file = File(directory, fileName)
|
||||||
|
val outputStream = FileOutputStream(file)
|
||||||
|
val buffer = ByteArray(1024)
|
||||||
|
while (true) {
|
||||||
|
val count = inputStream.read(buffer)
|
||||||
|
if (count < 0) { break }
|
||||||
|
outputStream.write(buffer, 0, count)
|
||||||
|
}
|
||||||
|
inputStream.close()
|
||||||
|
outputStream.close()
|
||||||
|
}
|
||||||
|
languageFileDirectory = directory
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUpViewHierarchy() {
|
||||||
|
inflate(context, R.layout.view_device_linking, this)
|
||||||
|
spinner.indeterminateDrawable.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)
|
||||||
|
val titleID = when (mode) {
|
||||||
|
Mode.Master -> R.string.view_device_linking_title_1
|
||||||
|
Mode.Slave -> R.string.view_device_linking_title_2
|
||||||
|
}
|
||||||
|
titleTextView.text = resources.getString(titleID)
|
||||||
|
val explanationID = when (mode) {
|
||||||
|
Mode.Master -> R.string.view_device_linking_explanation_1
|
||||||
|
Mode.Slave -> R.string.view_device_linking_explanation_2
|
||||||
|
}
|
||||||
|
explanationTextView.text = resources.getString(explanationID)
|
||||||
|
mnemonicTextView.visibility = if (mode == Mode.Master) View.GONE else View.VISIBLE
|
||||||
|
if (mode == Mode.Slave) {
|
||||||
|
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context).removing05PrefixIfNeeded()
|
||||||
|
mnemonicTextView.text = MnemonicCodec(languageFileDirectory).encode(hexEncodedPublicKey).split(" ").slice(0 until 3).joinToString(" ")
|
||||||
|
}
|
||||||
|
authorizeButton.visibility = View.GONE
|
||||||
|
authorizeButton.setOnClickListener { authorize() }
|
||||||
|
cancelButton.setOnClickListener { cancel() }
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Device Linking
|
||||||
|
fun requestUserAuthorization(authorisation: LokiPairingAuthorisation) {
|
||||||
|
// To be called when a linking request has been received
|
||||||
|
if (mode != Mode.Master) {
|
||||||
|
Log.w("Loki", "Received request for pairing authorisation on a slave device")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authorisation.type != LokiPairingAuthorisation.Type.REQUEST) {
|
||||||
|
Log.w("Loki", "Received request for GRANT pairing authorisation! It shouldn't be possible!!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pairingAuthorisation != null) {
|
||||||
|
Log.e("Loki", "Received request for another pairing authorisation when one was active")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pairingAuthorisation = authorisation
|
||||||
|
|
||||||
|
spinner.visibility = View.GONE
|
||||||
|
val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams
|
||||||
|
titleTextViewLayoutParams.topMargin = toPx(16, resources)
|
||||||
|
titleTextView.layoutParams = titleTextViewLayoutParams
|
||||||
|
titleTextView.text = resources.getString(R.string.view_device_linking_title_3)
|
||||||
|
explanationTextView.text = resources.getString(R.string.view_device_linking_explanation_2)
|
||||||
|
mnemonicTextView.visibility = View.VISIBLE
|
||||||
|
val hexEncodedPublicKey = authorisation.secondaryDevicePubKey.removing05PrefixIfNeeded()
|
||||||
|
mnemonicTextView.text = MnemonicCodec(languageFileDirectory).encode(hexEncodedPublicKey).split(" ").slice(0 until 3).joinToString(" ")
|
||||||
|
authorizeButton.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun authorize() {
|
||||||
|
if (pairingAuthorisation == null || mode != Mode.Master ) { return; }
|
||||||
|
|
||||||
|
// Pass authorisation to delegate and only dismiss if it succeeded
|
||||||
|
if (delegate.authorise(pairingAuthorisation!!)) {
|
||||||
|
delegate.handleDeviceLinkAuthorized()
|
||||||
|
dismiss?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDeviceLinkAuthorized(authorisation: LokiPairingAuthorisation) {
|
||||||
|
// To be called when a device link was accepted by the primary device
|
||||||
|
if (mode == Mode.Master || authorisation != pairingAuthorisation) { return }
|
||||||
|
|
||||||
|
spinner.visibility = View.GONE
|
||||||
|
val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams
|
||||||
|
titleTextViewLayoutParams.topMargin = toPx(8, resources)
|
||||||
|
titleTextView.layoutParams = titleTextViewLayoutParams
|
||||||
|
titleTextView.text = resources.getString(R.string.view_device_linking_title_4)
|
||||||
|
val explanationTextViewLayoutParams = explanationTextView.layoutParams as LayoutParams
|
||||||
|
explanationTextViewLayoutParams.bottomMargin = toPx(12, resources)
|
||||||
|
explanationTextView.layoutParams = explanationTextViewLayoutParams
|
||||||
|
explanationTextView.text = resources.getString(R.string.view_device_linking_explanation_3)
|
||||||
|
titleTextView.text = resources.getString(R.string.view_device_linking_title_4)
|
||||||
|
mnemonicTextView.visibility = View.GONE
|
||||||
|
buttonContainer.visibility = View.GONE
|
||||||
|
|
||||||
|
Handler().postDelayed({
|
||||||
|
delegate.handleDeviceLinkAuthorized()
|
||||||
|
dismiss?.invoke()
|
||||||
|
}, 4000)
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Interaction
|
||||||
|
private fun cancel() {
|
||||||
|
delegate.handleDeviceLinkingDialogDismissed()
|
||||||
|
dismiss?.invoke()
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
}
|
@ -16,10 +16,12 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
|||||||
import org.thoughtcrime.securesms.database.Address
|
import org.thoughtcrime.securesms.database.Address
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||||
|
import org.thoughtcrime.securesms.logging.Log
|
||||||
import org.thoughtcrime.securesms.util.Hex
|
import org.thoughtcrime.securesms.util.Hex
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.curve25519.Curve25519
|
import org.whispersystems.curve25519.Curve25519
|
||||||
import org.whispersystems.libsignal.util.KeyHelper
|
import org.whispersystems.libsignal.util.KeyHelper
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiPairingAuthorisation
|
||||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
||||||
import org.whispersystems.signalservice.loki.utilities.Analytics
|
import org.whispersystems.signalservice.loki.utilities.Analytics
|
||||||
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
|
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
|
||||||
@ -36,8 +38,6 @@ class SeedActivity : BaseActionBarActivity() {
|
|||||||
private var mnemonic: String? = null
|
private var mnemonic: String? = null
|
||||||
set(newValue) { field = newValue; updateMnemonicTextView() }
|
set(newValue) { field = newValue; updateMnemonicTextView() }
|
||||||
|
|
||||||
private var dialog: ProgressDialog? = null
|
|
||||||
|
|
||||||
// region Types
|
// region Types
|
||||||
enum class Mode { Register, Restore, Link }
|
enum class Mode { Register, Restore, Link }
|
||||||
// endregion
|
// endregion
|
||||||
@ -177,7 +177,7 @@ class SeedActivity : BaseActionBarActivity() {
|
|||||||
}
|
}
|
||||||
val hexEncodedSeed = Hex.toStringCondensed(seed)
|
val hexEncodedSeed = Hex.toStringCondensed(seed)
|
||||||
IdentityKeyUtil.save(this, IdentityKeyUtil.lokiSeedKey, hexEncodedSeed)
|
IdentityKeyUtil.save(this, IdentityKeyUtil.lokiSeedKey, hexEncodedSeed)
|
||||||
if (seed.count() == 16) seed = seed + seed
|
if (seed.count() == 16) seed += seed
|
||||||
if (mode == Mode.Restore) {
|
if (mode == Mode.Restore) {
|
||||||
IdentityKeyUtil.generateIdentityKeyPair(this, seed)
|
IdentityKeyUtil.generateIdentityKeyPair(this, seed)
|
||||||
}
|
}
|
||||||
@ -197,18 +197,46 @@ class SeedActivity : BaseActionBarActivity() {
|
|||||||
if (mode == Mode.Link) {
|
if (mode == Mode.Link) {
|
||||||
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
|
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
|
||||||
TextSecurePreferences.setPromptedPushRegistration(this, true)
|
TextSecurePreferences.setPromptedPushRegistration(this, true)
|
||||||
|
|
||||||
|
// Build the pairing request
|
||||||
|
val primaryDevicePublicKey = publicKeyEditText.text.trim().toString()
|
||||||
|
val authorisation = LokiPairingAuthorisation(primaryDevicePublicKey, hexEncodedPublicKey).sign(LokiPairingAuthorisation.Type.REQUEST, keyPair.privateKey.serialize())
|
||||||
|
if (authorisation == null) {
|
||||||
|
Log.w("Loki", "Failed to sign outgoing pairing request :(")
|
||||||
|
resetRegistration()
|
||||||
|
return Toast.makeText(application, "Failed to initialise device pairing", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
val application = ApplicationContext.getInstance(this)
|
val application = ApplicationContext.getInstance(this)
|
||||||
application.startLongPollingIfNeeded()
|
application.startLongPollingIfNeeded()
|
||||||
application.setUpStorageAPIIfNeeded()
|
application.setUpStorageAPIIfNeeded()
|
||||||
|
|
||||||
// TODO: Show activity view here?
|
// Show the dialog
|
||||||
|
DeviceLinkingDialog.show(this, DeviceLinkingView.Mode.Slave, object: DeviceLinkingDialogDelegate {
|
||||||
|
override fun handleDeviceLinkAuthorized() {
|
||||||
|
showAccountDetailsView()
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Also need to reset on registration
|
override fun handleDeviceLinkingDialogDismissed() {
|
||||||
|
resetRegistration()
|
||||||
|
Toast.makeText(application, "Cancelled Device Linking", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Send the request to the other user
|
||||||
|
sendAuthorisationMessage(this, authorisation.primaryDevicePubKey, authorisation)
|
||||||
} else {
|
} else {
|
||||||
|
showAccountDetailsView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showAccountDetailsView() {
|
||||||
startActivity(Intent(this, AccountDetailsActivity::class.java))
|
startActivity(Intent(this, AccountDetailsActivity::class.java))
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resetRegistration() {
|
||||||
|
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
25
src/org/thoughtcrime/securesms/loki/Utilities.kt
Normal file
25
src/org/thoughtcrime/securesms/loki/Utilities.kt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.logging.Log
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional
|
||||||
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||||
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiPairingAuthorisation
|
||||||
|
|
||||||
|
fun sendAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: LokiPairingAuthorisation) {
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
|
||||||
|
val address = SignalServiceAddress(contactHexEncodedPublicKey)
|
||||||
|
val message = SignalServiceDataMessage.newBuilder().withBody("").withPairingAuthorisation(authorisation).build()
|
||||||
|
try {
|
||||||
|
messageSender.sendMessage(0, address, Optional.absent<UnidentifiedAccessPair>(), message) // The message ID doesn't matter
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d("Loki", "Failed to send authorisation message to: $contactHexEncodedPublicKey.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user