QrCode: WIP

This commit is contained in:
Benoit Marty 2020-01-27 19:41:40 +01:00
parent 39e746413a
commit 8659216955
6 changed files with 167 additions and 76 deletions

View File

@ -23,6 +23,7 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.QrCodeVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
@ -669,59 +670,14 @@ internal class DefaultVerificationService @Inject constructor(
return
}
var myGeneratedSharedSecret: String? = null
val qrCodeText = readyReq.methods
val qrCodeData = readyReq.methods
// Check if other user is able to scan QR code
?.takeIf { it.contains(VERIFICATION_METHOD_QR_CODE_SCAN) }
?.let {
// Build the QR code URL
val requestEventId = existingRequest.transactionId ?: run {
Timber.w("Unknown requestEventId")
return@let null
}
val myMasterKey = crossSigningService.getMyCrossSigningKeys()
?.masterKey()
?.unpaddedBase64PublicKey
?: run {
Timber.w("Unable to get my master key")
return@let null
}
// TODO Force download?
val otherUserMasterKey = crossSigningService.getUserCrossSigningKeys(existingRequest.otherUserId)
?.masterKey()
?.unpaddedBase64PublicKey
?: run {
Timber.w("Unable to get other user master key")
return@let null
}
val myDeviceId = deviceId
?: run {
Timber.w("Unable to get my deviceId")
return@let null
}
// TODO I'm pretty sure it's the correct key to put here
val myDeviceKey = myDeviceInfoHolder.get().myDevice.fingerprint()!!
val generatedSharedSecret = generateSharedSecret()
.also { myGeneratedSharedSecret = it }
QrCodeData(
userId = userId,
requestEventId = requestEventId,
action = QrCodeData.ACTION_VERIFY,
keys = hashMapOf(
myMasterKey to myMasterKey,
myDeviceId to myDeviceKey
),
sharedSecret = generatedSharedSecret,
otherUserKey = otherUserMasterKey
).toUrl()
createQrCodeData(existingRequest.transactionId, existingRequest.otherUserId)
}
if (qrCodeText != null) {
if (qrCodeData != null) {
// Create the pending transaction
val tx = DefaultQrCodeVerificationTransaction(
readyReq.transactionID!!,
@ -729,8 +685,8 @@ internal class DefaultVerificationService @Inject constructor(
readyReq.fromDevice,
crossSigningService,
cryptoStore,
myGeneratedSharedSecret!!,
qrCodeText,
qrCodeData.sharedSecret,
qrCodeData.toUrl(),
deviceId ?: "",
false)
@ -744,6 +700,51 @@ internal class DefaultVerificationService @Inject constructor(
))
}
private fun createQrCodeData(transactionId: String?, otherUserId: String): QrCodeData? {
// Build the QR code URL
val requestEventId = transactionId ?: run {
Timber.w("## Unknown requestEventId")
return null
}
val myMasterKey = crossSigningService.getMyCrossSigningKeys()
?.masterKey()
?.unpaddedBase64PublicKey
?: run {
Timber.w("## Unable to get my master key")
return null
}
val otherUserMasterKey = crossSigningService.getUserCrossSigningKeys(otherUserId)
?.masterKey()
?.unpaddedBase64PublicKey
?: run {
Timber.w("## Unable to get other user master key")
return null
}
val myDeviceId = deviceId
?: run {
Timber.w("## Unable to get my deviceId")
return null
}
val myDeviceKey = myDeviceInfoHolder.get().myDevice.fingerprint()!!
val generatedSharedSecret = generateSharedSecret()
return QrCodeData(
userId = userId,
requestEventId = requestEventId,
action = QrCodeData.ACTION_VERIFY,
keys = hashMapOf(
myMasterKey to myMasterKey,
myDeviceId to myDeviceKey
),
sharedSecret = generatedSharedSecret,
otherUserKey = otherUserMasterKey
)
}
private fun handleDoneReceived(senderId: String, doneInfo: VerificationInfo) {
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionID }
if (existingRequest == null) {
@ -953,7 +954,13 @@ internal class DefaultVerificationService @Inject constructor(
if (existingRequest != null) {
// we need to send a ready event, with matching methods
val transport = verificationTransportRoomMessageFactory.createTransport(roomId, null)
val computedMethods = computeReadyMethods(existingRequest.requestInfo?.methods, methods)
val computedMethods = computeReadyMethods(
transactionId,
otherUserId,
existingRequest.requestInfo?.fromDevice ?: "",
roomId,
existingRequest.requestInfo?.methods,
methods)
if (methods.isNullOrEmpty()) {
Timber.i("Cannot ready this request, no common methods found txId:$transactionId")
// TODO buttons should not be shown in this case?
@ -976,7 +983,13 @@ internal class DefaultVerificationService @Inject constructor(
}
}
private fun computeReadyMethods(otherUserMethods: List<String>?, methods: List<VerificationMethod>): List<String> {
private fun computeReadyMethods(
transactionId: String,
otherUserId: String,
otherDeviceId: String,
roomId: String,
otherUserMethods: List<String>?,
methods: List<VerificationMethod>): List<String> {
if (otherUserMethods.isNullOrEmpty()) {
return emptyList()
}
@ -985,17 +998,42 @@ internal class DefaultVerificationService @Inject constructor(
if (VERIFICATION_METHOD_SAS in otherUserMethods && VerificationMethod.SAS in methods) {
// Other can do SAS and so do I
result + VERIFICATION_METHOD_SAS
result.add(VERIFICATION_METHOD_SAS)
}
if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods && VerificationMethod.QR_CODE_SHOW in methods) {
// Other can Scan and I can show QR code
result + VERIFICATION_METHOD_QR_CODE_SHOW
result + VERIFICATION_METHOD_RECIPROCATE
}
if (VERIFICATION_METHOD_QR_CODE_SHOW in otherUserMethods && VerificationMethod.QR_CODE_SCAN in methods) {
// Other can show and I can scan QR code
result + VERIFICATION_METHOD_QR_CODE_SCAN
result + VERIFICATION_METHOD_RECIPROCATE
if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods || VERIFICATION_METHOD_QR_CODE_SHOW in otherUserMethods) {
// Other user want to verify using QR code. Cross-signing has to be setup
val qrCodeData = createQrCodeData(transactionId, otherUserId)
if (qrCodeData != null) {
if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods && VerificationMethod.QR_CODE_SHOW in methods) {
// Other can Scan and I can show QR code
result.add(VERIFICATION_METHOD_QR_CODE_SHOW)
result.add(VERIFICATION_METHOD_RECIPROCATE)
// Create the pending request, to display the QR code
// Create the pending transaction
val tx = DefaultQrCodeVerificationTransaction(
transactionId,
otherUserId,
otherDeviceId,
crossSigningService,
cryptoStore,
qrCodeData.sharedSecret,
qrCodeData.toUrl(),
deviceId ?: "",
false)
tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx)
addTransaction(tx)
}
if (VERIFICATION_METHOD_QR_CODE_SHOW in otherUserMethods && VerificationMethod.QR_CODE_SCAN in methods) {
// Other can show and I can scan QR code
result.add(VERIFICATION_METHOD_QR_CODE_SCAN)
result.add(VERIFICATION_METHOD_RECIPROCATE)
}
}
}
return result.toList()
@ -1024,5 +1062,13 @@ internal class DefaultVerificationService @Inject constructor(
// remove
this.removeTransaction(tx.otherUserId, tx.transactionId)
}
if (tx is QrCodeVerificationTransaction
&& (tx.state == VerificationTxState.Cancelled
|| tx.state == VerificationTxState.OnCancelled
|| tx.state == VerificationTxState.Verified)
) {
// remove
this.removeTransaction(tx.otherUserId, tx.transactionId)
}
}
}

View File

@ -152,5 +152,10 @@ internal class DefaultQrCodeVerificationTransaction(
Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId")
}
})
// TODO Sign devices
}
// TODO Send the done event
}

View File

@ -98,7 +98,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
it.otherUserMxItem?.let { matrixItem ->
avatarRenderer.render(matrixItem, otherUserAvatarImageView)
if (it.transactionState == VerificationTxState.Verified) {
if (it.sasTransactionState == VerificationTxState.Verified || it.qrTransactionState == VerificationTxState.Verified) {
otherUserNameText.text = getString(R.string.verification_verified_user, matrixItem.getBestName())
otherUserShield.isVisible = true
} else {
@ -108,8 +108,8 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
}
// Did the request result in a SAS transaction?
if (it.transactionState != null) {
when (it.transactionState) {
if (it.sasTransactionState != null) {
when (it.sasTransactionState) {
VerificationTxState.None,
VerificationTxState.SendingStart,
VerificationTxState.Started,
@ -138,7 +138,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
VerificationTxState.OnCancelled -> {
showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(
it.transactionState == VerificationTxState.Verified,
it.sasTransactionState == VerificationTxState.Verified,
it.cancelCode?.value))
})
}
@ -147,6 +147,19 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
return@withState
}
when (it.qrTransactionState) {
VerificationTxState.Verified,
VerificationTxState.Cancelled,
VerificationTxState.OnCancelled -> {
showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(
it.qrTransactionState == VerificationTxState.Verified,
it.cancelCode?.value))
})
}
else -> Unit
}
// At this point there is no transaction for this request
// Transaction has not yet started

View File

@ -52,7 +52,8 @@ data class VerificationBottomSheetViewState(
val roomId: String? = null,
val pendingRequest: Async<PendingVerificationRequest> = Uninitialized,
val pendingLocalId: String? = null,
val transactionState: VerificationTxState? = null,
val sasTransactionState: VerificationTxState? = null,
val qrTransactionState: VerificationTxState? = null,
val transactionId: String? = null,
val cancelCode: CancelCode? = null
) : MvRxState
@ -94,12 +95,17 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
val pr = session.getVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId)
val sasTx = (pr?.transactionId ?: args.verificationId)?.let {
session.getVerificationService().getExistingTransaction(args.otherUserId, it)
session.getVerificationService().getExistingTransaction(args.otherUserId, it) as? SasVerificationTransaction
}
val qrTx = (pr?.transactionId ?: args.verificationId)?.let {
session.getVerificationService().getExistingTransaction(args.otherUserId, it) as? QrCodeVerificationTransaction
}
return fragment.verificationViewModelFactory.create(VerificationBottomSheetViewState(
otherUserMxItem = userItem?.toMatrixItem(),
transactionState = sasTx?.state,
sasTransactionState = sasTx?.state,
qrTransactionState = qrTx?.state,
transactionId = args.verificationId,
pendingRequest = if (pr != null) Success(pr) else Uninitialized,
roomId = args.roomId)
@ -192,13 +198,28 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
}
override fun transactionUpdated(tx: VerificationTransaction) = withState { state ->
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
// A SAS tx has been started following this request
setState {
copy(
transactionState = tx.state,
cancelCode = tx.cancelledReason
)
when (tx) {
is SasVerificationTransaction -> {
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
// A SAS tx has been started following this request
setState {
copy(
sasTransactionState = tx.state,
cancelCode = tx.cancelledReason
)
}
}
}
is QrCodeVerificationTransaction -> {
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
// A SAS tx has been started following this request
setState {
copy(
qrTransactionState = tx.state,
cancelCode = tx.cancelledReason
)
}
}
}
}
}

View File

@ -98,10 +98,14 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession()
val pvr = session.getVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId)
// Get the QR code now, because transaction is already created, so transactionCreated() will not be called
val qrCodeVerificationTransaction = session.getVerificationService().getExistingTransaction(args.otherUserId, args.verificationId ?: "")
return VerificationChooseMethodViewState(otherUserId = args.otherUserId,
transactionId = args.verificationId ?: "",
otherCanShowQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SHOW) ?: false,
otherCanScanQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SCAN) ?: false,
qrCodeText = (qrCodeVerificationTransaction as? QrCodeVerificationTransaction)?.qrCodeText,
SASModeAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false
)
}

View File

@ -815,6 +815,8 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
room.roomId,
action.transactionId)) {
_requestLiveData.postValue(LiveEvent(Success(action)))
} else {
// TODO
}
}