Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update/Revise SDK to implement reference flowchart for key sharing/forwarding + use backup #5559

Merged
merged 29 commits into from
May 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9177cb1
Refactor key and secret request managers
BillCarsonFr Mar 14, 2022
9747eb2
Add share test + fix
BillCarsonFr Mar 16, 2022
b1db6ca
fix db migration
BillCarsonFr Mar 16, 2022
ae6df46
Add incoming key forward trail
BillCarsonFr Mar 16, 2022
6a509ce
fix unused var
BillCarsonFr Mar 17, 2022
1d948d6
Add option to disable key gossip, clear key request on trust change
BillCarsonFr Mar 18, 2022
81b114f
Add change log + quick quality fix
BillCarsonFr Mar 21, 2022
cc10749
Fix database migration
BillCarsonFr Mar 21, 2022
88cf1a5
Fix unneeded re-uploade of key got from backup
BillCarsonFr Mar 29, 2022
54fb4ae
proper initial withheld support
BillCarsonFr Apr 12, 2022
f9dd3b9
Stop using workers for interactive verification
BillCarsonFr Apr 15, 2022
a60171c
Reactivate withheld and verification tests
BillCarsonFr Apr 15, 2022
3f5f2dc
Fix tests
BillCarsonFr Apr 15, 2022
631ea50
Move some crypto classes to API + cleaning
BillCarsonFr Apr 15, 2022
effbc47
FIx unit test compilation
BillCarsonFr Apr 20, 2022
885f836
Cleaning, review
BillCarsonFr Apr 20, 2022
058d2e6
Fix: ignore key request form self devices
BillCarsonFr Apr 22, 2022
9385d19
Fix trail display (from instead of to for incoming types)
BillCarsonFr Apr 22, 2022
eaf1044
Cleaning, code review
BillCarsonFr Apr 25, 2022
012dfeb
Change log for SDK apis
BillCarsonFr Apr 25, 2022
728faae
Fix missing mapper for incoming key forward trail
BillCarsonFr Apr 26, 2022
8920ed3
Code review
BillCarsonFr Apr 27, 2022
23f32aa
Merge branch 'develop' into feature/bca/crypto_better_key_share
BillCarsonFr Apr 27, 2022
0f06368
Code review
BillCarsonFr Apr 28, 2022
de580cc
Fix make verif scope as a child of crypto scope
BillCarsonFr Apr 29, 2022
12ab478
Merge branch 'develop' into feature/bca/crypto_better_key_share
BillCarsonFr May 10, 2022
546d6fe
post merge fix
BillCarsonFr May 11, 2022
992f477
use clock time instead of system
BillCarsonFr May 11, 2022
3948f26
fix method visibility
BillCarsonFr May 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/5494.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use key backup before requesting keys + refactor & improvement of key request/forward
4 changes: 4 additions & 0 deletions changelog.d/5559.sdk
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- New API to enable/disable key forwarding CryptoService#enableKeyGossiping()
- New API to limit room key request only to own devices MXCryptoConfig#limitRoomKeyRequestsToMyDevices
- Event Trail API has changed, now using AuditTrail events
- New API to manually accept an incoming key request CryptoService#manuallyAcceptRoomKeyRequest()
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package org.matrix.android.sdk.common
import android.os.SystemClock
import android.util.Log
import androidx.lifecycle.Observer
import org.amshove.kluent.fail
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
Expand All @@ -31,22 +32,36 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.awaitCallback
import org.matrix.android.sdk.api.util.toBase64NoPadding
import java.util.UUID
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
Expand Down Expand Up @@ -296,51 +311,123 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
}
}

/**
* Initialize cross-signing, set up megolm backup and save all in 4S
*/
fun bootstrapSecurity(session: Session) {
initializeCrossSigning(session)
val ssssService = session.sharedSecretStorageService()
testHelper.runBlockingTest {
val keyInfo = ssssService.generateKey(
UUID.randomUUID().toString(),
null,
"ssss_key",
EmptyKeySigner()
)
ssssService.setDefaultKey(keyInfo.keyId)

ssssService.storeSecret(
MASTER_KEY_SSSS_NAME,
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master!!,
listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec))
)

ssssService.storeSecret(
SELF_SIGNING_KEY_SSSS_NAME,
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned!!,
listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec))
)

ssssService.storeSecret(
USER_SIGNING_KEY_SSSS_NAME,
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user!!,
listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec))
)

// set up megolm backup
val creationInfo = awaitCallback<MegolmBackupCreationInfo> {
session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
}
val version = awaitCallback<KeysVersion> {
session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
}
// Save it for gossiping
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)

extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret ->
ssssService.storeSecret(
KEYBACKUP_SECRET_SSSS_NAME,
secret,
listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec))
)
}
}
}

fun verifySASCrossSign(alice: Session, bob: Session, roomId: String) {
assertTrue(alice.cryptoService().crossSigningService().canCrossSign())
assertTrue(bob.cryptoService().crossSigningService().canCrossSign())

val requestID = UUID.randomUUID().toString()
val aliceVerificationService = alice.cryptoService().verificationService()
val bobVerificationService = bob.cryptoService().verificationService()

aliceVerificationService.beginKeyVerificationInDMs(
VerificationMethod.SAS,
requestID,
roomId,
bob.myUserId,
bob.sessionParams.credentials.deviceId!!
)
val localId = UUID.randomUUID().toString()
aliceVerificationService.requestKeyVerificationInDMs(
localId = localId,
methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
otherUserId = bob.myUserId,
roomId = roomId
).transactionId

// we should reach SHOW SAS on both
var alicePovTx: OutgoingSasVerificationTransaction? = null
var bobPovTx: IncomingSasVerificationTransaction? = null
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull {
it.requestInfo?.fromDevice == alice.sessionParams.deviceId
} != null
}
}
val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first {
it.requestInfo?.fromDevice == alice.sessionParams.deviceId
}
bobVerificationService.readyPendingVerification(listOf(VerificationMethod.SAS), alice.myUserId, incomingRequest.transactionId!!)

// wait for alice to get the ready
var requestID: String? = null
// wait for it to be readied
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
if (bobPovTx?.state == VerificationTxState.OnStarted) {
bobPovTx?.performAccept()
val outgoingRequest = aliceVerificationService.getExistingVerificationRequests(bob.myUserId)
.firstOrNull { it.localId == localId }
if (outgoingRequest?.isReady == true) {
requestID = outgoingRequest.transactionId!!
true
} else {
false
}
}
}

aliceVerificationService.beginKeyVerificationInDMs(
VerificationMethod.SAS,
requestID!!,
roomId,
bob.myUserId,
bob.sessionParams.credentials.deviceId!!)

// we should reach SHOW SAS on both
var alicePovTx: OutgoingSasVerificationTransaction? = null
var bobPovTx: IncomingSasVerificationTransaction? = null

testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? OutgoingSasVerificationTransaction
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID!!) as? OutgoingSasVerificationTransaction
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}")
alicePovTx?.state == VerificationTxState.ShortCodeReady
}
}
// wait for alice to get the ready
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID!!) as? IncomingSasVerificationTransaction
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
if (bobPovTx?.state == VerificationTxState.OnStarted) {
bobPovTx?.performAccept()
Expand Down Expand Up @@ -392,4 +479,50 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {

return CryptoTestData(roomId, sessions)
}

fun ensureCanDecrypt(sentEventIds: List<String>, session: Session, e2eRoomID: String, messagesText: List<String>) {
sentEventIds.forEachIndexed { index, sentEventId ->
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val event = session.getRoom(e2eRoomID)!!.timelineService().getTimelineEvent(sentEventId)!!.root
testHelper.runBlockingTest {
try {
session.cryptoService().decryptEvent(event, "").let { result ->
event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
)
}
} catch (error: MXCryptoError) {
// nop
}
}
Log.v("TEST", "ensureCanDecrypt ${event.getClearType()} is ${event.getClearContent()}")
event.getClearType() == EventType.MESSAGE &&
messagesText[index] == event.getClearContent()?.toModel<MessageContent>()?.body
}
}
}
}

fun ensureCannotDecrypt(sentEventIds: List<String>, session: Session, e2eRoomID: String, expectedError: MXCryptoError.ErrorType? = null) {
sentEventIds.forEach { sentEventId ->
val event = session.getRoom(e2eRoomID)!!.timelineService().getTimelineEvent(sentEventId)!!.root
testHelper.runBlockingTest {
try {
session.cryptoService().decryptEvent(event, "")
fail("Should not be able to decrypt event")
} catch (error: MXCryptoError) {
val errorType = (error as? MXCryptoError.Base)?.errorType
if (expectedError == null) {
assertNotNull(errorType)
} else {
assertEquals("Unexpected reason", expectedError, errorType)
}
}
}
}
}
}
Loading