Skip to content

Commit 03272a9

Browse files
ganfraBillCarsonFr
authored andcommitted
Implement AES backup algorithm (WIP need to check with valere the private key thing)
1 parent 1d26207 commit 03272a9

File tree

7 files changed

+173
-81
lines changed

7 files changed

+173
-81
lines changed

matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
4242
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
4343
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
4444
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCurve25519AuthData
45+
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
4546
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
4647
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
4748
import org.matrix.android.sdk.api.session.getRoom
@@ -321,7 +322,8 @@ class KeysBackupTest : InstrumentedTest {
321322
put(cryptoTestData.roomId, roomKeysBackupData)
322323
}
323324
)
324-
val sessionsData = algorithm.decryptSessions(keyBackupCreationInfo.recoveryKey, keysBackupData)
325+
algorithm.setRecoveryKey(keyBackupCreationInfo.recoveryKey)
326+
val sessionsData = algorithm.decryptSessions(keysBackupData)
325327
val sessionData = sessionsData.firstOrNull()
326328
assertNotNull(sessionData)
327329
// - Compare the decrypted megolm key with the original one

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.MatrixCallback
2929
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
3030
import org.matrix.android.sdk.api.auth.data.Credentials
3131
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP
32+
import org.matrix.android.sdk.api.extensions.tryOrNull
3233
import org.matrix.android.sdk.api.failure.Failure
3334
import org.matrix.android.sdk.api.failure.MatrixError
3435
import org.matrix.android.sdk.api.listeners.ProgressListener
@@ -45,6 +46,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
4546
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
4647
import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
4748
import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
49+
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
4850
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
4951
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
5052
import org.matrix.android.sdk.api.util.JsonDict
@@ -630,8 +632,9 @@ internal class DefaultKeysBackupService @Inject constructor(
630632

631633
// Get backed up keys from the homeserver
632634
val data = getKeys(sessionId, roomId, keysVersionResult.version)
635+
algorithm?.setRecoveryKey(recoveryKey)
633636
val sessionsData = withContext(coroutineDispatchers.computation) {
634-
algorithm?.decryptSessions(recoveryKey, data)
637+
algorithm?.decryptSessions(data)
635638
}.orEmpty()
636639
// Do not trigger a backup for them if they come from the backup version we are using
637640
val backUp = keysVersionResult.version != keysBackupVersion?.version
@@ -998,7 +1001,8 @@ internal class DefaultKeysBackupService @Inject constructor(
9981001
private fun isValidRecoveryKeyForKeysBackupVersion(recoveryKey: String, keysBackupData: KeysVersionResult): Boolean {
9991002
return try {
10001003
val algorithm = algorithmFactory.create(keysBackupData)
1001-
val isValid = algorithm.keyMatches(recoveryKey)
1004+
val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) ?: return false
1005+
val isValid = algorithm.keyMatches(privateKey)
10021006
algorithm.release()
10031007
isValid
10041008
} catch (failure: Throwable) {
@@ -1133,7 +1137,8 @@ internal class DefaultKeysBackupService @Inject constructor(
11331137
// Gather data to send to the homeserver
11341138
// roomId -> sessionId -> MXKeyBackupData
11351139
val keysBackupData = KeysBackupData()
1136-
1140+
val recoveryKey = cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey
1141+
algorithm?.setRecoveryKey(recoveryKey)
11371142
olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
11381143
val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach
11391144
val olmInboundGroupSession = olmInboundGroupSessionWrapper.session
@@ -1242,7 +1247,9 @@ internal class DefaultKeysBackupService @Inject constructor(
12421247
}
12431248
?: return null
12441249

1245-
val sessionBackupData = algorithm?.encryptSession(sessionData) ?: return null
1250+
val sessionBackupData = tryOrNull {
1251+
algorithm?.encryptSession(sessionData)
1252+
} ?: return null
12461253
return KeyBackupData(
12471254
firstMessageIndex = try {
12481255
olmInboundGroupSessionWrapper.session.firstKnownIndex
@@ -1265,6 +1272,10 @@ internal class DefaultKeysBackupService @Inject constructor(
12651272
return sessionData.sharedHistory
12661273
}
12671274

1275+
private fun getPrivateKey(): ByteArray {
1276+
return byteArrayOf()
1277+
}
1278+
12681279
/* ==========================================================================================
12691280
* For test only
12701281
* ========================================================================================== */

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,24 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_AES_256_BACKUP
2020
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
2121
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAes256AuthData
2222
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
23+
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
2324
import org.matrix.android.sdk.api.util.JsonDict
25+
import org.matrix.android.sdk.api.util.fromBase64
26+
import org.matrix.android.sdk.api.util.toBase64NoPadding
2427
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
2528
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData
29+
import org.matrix.android.sdk.internal.crypto.tools.AesHmacSha2
30+
import org.matrix.olm.OlmException
31+
import timber.log.Timber
32+
import java.security.InvalidParameterException
33+
import java.util.Arrays
2634

2735
internal class KeysBackupAes256Algorithm(keysVersions: KeysVersionResult) : KeysBackupAlgorithm {
2836

29-
override val untrusted: Boolean = true
37+
override val untrusted: Boolean = false
3038

3139
private val aesAuthData: MegolmBackupAes256AuthData
40+
private var privateKey: ByteArray? = null
3241

3342
init {
3443
if (keysVersions.algorithm != MXCRYPTO_ALGORITHM_AES_256_BACKUP) {
@@ -37,21 +46,92 @@ internal class KeysBackupAes256Algorithm(keysVersions: KeysVersionResult) : Keys
3746
aesAuthData = keysVersions.getAuthDataAsMegolmBackupAuthData() as MegolmBackupAes256AuthData
3847
}
3948

49+
override fun setRecoveryKey(recoveryKey: String?) {
50+
privateKey = extractCurveKeyFromRecoveryKey(recoveryKey)
51+
}
52+
4053
override fun encryptSession(sessionData: MegolmSessionData): JsonDict? {
41-
TODO("Not yet implemented")
54+
val privateKey = privateKey
55+
if (privateKey == null || !keyMatches(privateKey)) {
56+
Timber.e("Key does not match")
57+
throw IllegalStateException("Key does not match")
58+
}
59+
val encryptedSessionBackupData = try {
60+
val sessionDataJson = sessionData.asBackupJson()
61+
AesHmacSha2.encrypt(privateKey = privateKey, secretName = sessionData.sessionId ?: "", sessionDataJson)
62+
} catch (e: OlmException) {
63+
Timber.e(e, "Error while encrypting backup data.")
64+
null
65+
} ?: return null
66+
67+
return mapOf(
68+
"ciphertext" to encryptedSessionBackupData.cipherRawBytes.toBase64NoPadding(),
69+
"mac" to encryptedSessionBackupData.mac.toBase64NoPadding(),
70+
"iv" to encryptedSessionBackupData.initializationVector.toBase64NoPadding()
71+
)
4272
}
4373

44-
override fun decryptSessions(recoveryKey: String, data: KeysBackupData): List<MegolmSessionData> {
45-
TODO("Not yet implemented")
74+
override fun decryptSessions(data: KeysBackupData): List<MegolmSessionData> {
75+
val privateKey = privateKey
76+
if (privateKey == null || !keyMatches(privateKey)) {
77+
Timber.e("Invalid recovery key for this keys version")
78+
throw InvalidParameterException("Invalid recovery key")
79+
}
80+
val sessionsData = ArrayList<MegolmSessionData>()
81+
// Restore that data
82+
var sessionsFromHsCount = 0
83+
for ((roomIdLoop, backupData) in data.roomIdToRoomKeysBackupData) {
84+
for ((sessionIdLoop, keyBackupData) in backupData.sessionIdToKeyBackupData) {
85+
sessionsFromHsCount++
86+
val sessionData = decryptSession(keyBackupData.sessionData, sessionIdLoop, roomIdLoop, privateKey)
87+
sessionData?.let {
88+
sessionsData.add(it)
89+
}
90+
}
91+
}
92+
Timber.v(
93+
"Decrypted ${sessionsData.size} keys out of $sessionsFromHsCount from the backup store on the homeserver"
94+
)
95+
return sessionsData
96+
}
97+
98+
private fun decryptSession(sessionData: JsonDict, sessionId: String, roomId: String, privateKey: ByteArray): MegolmSessionData? {
99+
100+
val cipherRawBytes = sessionData["ciphertext"]?.toString()?.fromBase64() ?: return null
101+
val mac = sessionData["mac"]?.toString()?.fromBase64() ?: throw IllegalStateException("Bad mac")
102+
val iv = sessionData["iv"]?.toString()?.fromBase64() ?: ByteArray(16)
103+
104+
val encryptionInfo = AesHmacSha2.EncryptionInfo(
105+
cipherRawBytes = cipherRawBytes,
106+
mac = mac,
107+
initializationVector = iv
108+
)
109+
return try {
110+
val decrypted = AesHmacSha2.decrypt(privateKey, sessionId, encryptionInfo)
111+
createMegolmSessionData(decrypted, sessionId, roomId)
112+
} catch (e: Exception) {
113+
Timber.e(e, "Exception while decrypting")
114+
null
115+
}
46116
}
47117

48118
override fun release() {
49-
TODO("Not yet implemented")
119+
privateKey?.apply {
120+
Arrays.fill(this, 0)
121+
}
122+
privateKey = null
50123
}
51124

52125
override val authData: MegolmBackupAuthData = aesAuthData
53126

54-
override fun keyMatches(key: String): Boolean {
55-
TODO("Not yet implemented")
127+
override fun keyMatches(privateKey: ByteArray): Boolean {
128+
return if (aesAuthData.mac != null) {
129+
val keyCheckMac = AesHmacSha2.calculateKeyCheck(privateKey, aesAuthData.iv).mac
130+
val authDataMac = aesAuthData.mac.fromBase64()
131+
return keyCheckMac.contentEquals(authDataMac)
132+
} else {
133+
// if we have no information, we have to assume the key is right
134+
true
135+
}
56136
}
57137
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAlgorithm.kt

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,43 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
2020
import org.matrix.android.sdk.api.util.JsonDict
2121
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
2222
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData
23+
import org.matrix.android.sdk.internal.di.MoshiProvider
2324

2425
internal interface KeysBackupAlgorithm {
26+
2527
val authData: MegolmBackupAuthData
2628
val untrusted: Boolean
2729

30+
fun setRecoveryKey(recoveryKey: String?)
2831
fun encryptSession(sessionData: MegolmSessionData): JsonDict?
29-
fun decryptSessions(recoveryKey: String, data: KeysBackupData): List<MegolmSessionData>
30-
fun keyMatches(key: String): Boolean
32+
fun decryptSessions(data: KeysBackupData): List<MegolmSessionData>
33+
fun keyMatches(privateKey: ByteArray): Boolean
3134
fun release()
3235
}
36+
37+
internal fun KeysBackupAlgorithm.createMegolmSessionData(decrypted: String, sessionId: String, roomId: String): MegolmSessionData? {
38+
val moshi = MoshiProvider.providesMoshi()
39+
val adapter = moshi.adapter(MegolmSessionData::class.java)
40+
val sessionBackupData: MegolmSessionData = adapter.fromJson(decrypted) ?: return null
41+
return sessionBackupData.copy(
42+
sessionId = sessionId,
43+
roomId = roomId,
44+
untrusted = untrusted
45+
)
46+
}
47+
48+
internal fun MegolmSessionData.asBackupJson(): String {
49+
val sessionBackupData = mapOf(
50+
"algorithm" to algorithm,
51+
"sender_key" to senderKey,
52+
"sender_claimed_keys" to senderClaimedKeys,
53+
"forwarding_curve25519_key_chain" to (forwardingCurve25519KeyChain.orEmpty()),
54+
"session_key" to sessionKey,
55+
"org.matrix.msc3061.shared_history" to sharedHistory,
56+
"untrusted" to untrusted
57+
)
58+
return MoshiProvider.providesMoshi()
59+
.adapter(Map::class.java)
60+
.toJson(sessionBackupData)
61+
}
62+

0 commit comments

Comments
 (0)