Skip to content

Commit e996462

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

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
@@ -320,7 +321,8 @@ class KeysBackupTest : InstrumentedTest {
320321
put(cryptoTestData.roomId, roomKeysBackupData)
321322
}
322323
)
323-
val sessionsData = algorithm.decryptSessions(keyBackupCreationInfo.recoveryKey, keysBackupData)
324+
algorithm.setRecoveryKey(keyBackupCreationInfo.recoveryKey)
325+
val sessionsData = algorithm.decryptSessions(keysBackupData)
324326
val sessionData = sessionsData.firstOrNull()
325327
assertNotNull(sessionData)
326328
// - 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
@@ -637,8 +639,9 @@ internal class DefaultKeysBackupService @Inject constructor(
637639

638640
// Get backed up keys from the homeserver
639641
val data = getKeys(sessionId, roomId, keysVersionResult.version)
642+
algorithm?.setRecoveryKey(recoveryKey)
640643
val sessionsData = withContext(coroutineDispatchers.computation) {
641-
algorithm?.decryptSessions(recoveryKey, data)
644+
algorithm?.decryptSessions(data)
642645
}.orEmpty()
643646
// Do not trigger a backup for them if they come from the backup version we are using
644647
val backUp = keysVersionResult.version != keysBackupVersion?.version
@@ -1005,7 +1008,8 @@ internal class DefaultKeysBackupService @Inject constructor(
10051008
private fun isValidRecoveryKeyForKeysBackupVersion(recoveryKey: String, keysBackupData: KeysVersionResult): Boolean {
10061009
return try {
10071010
val algorithm = algorithmFactory.create(keysBackupData)
1008-
val isValid = algorithm.keyMatches(recoveryKey)
1011+
val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) ?: return false
1012+
val isValid = algorithm.keyMatches(privateKey)
10091013
algorithm.release()
10101014
isValid
10111015
} catch (failure: Throwable) {
@@ -1140,7 +1144,8 @@ internal class DefaultKeysBackupService @Inject constructor(
11401144
// Gather data to send to the homeserver
11411145
// roomId -> sessionId -> MXKeyBackupData
11421146
val keysBackupData = KeysBackupData()
1143-
1147+
val recoveryKey = cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey
1148+
algorithm?.setRecoveryKey(recoveryKey)
11441149
olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
11451150
val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach
11461151
val olmInboundGroupSession = olmInboundGroupSessionWrapper.session
@@ -1249,7 +1254,9 @@ internal class DefaultKeysBackupService @Inject constructor(
12491254
}
12501255
?: return null
12511256

1252-
val sessionBackupData = algorithm?.encryptSession(sessionData) ?: return null
1257+
val sessionBackupData = tryOrNull {
1258+
algorithm?.encryptSession(sessionData)
1259+
} ?: return null
12531260
return KeyBackupData(
12541261
firstMessageIndex = try {
12551262
olmInboundGroupSessionWrapper.session.firstKnownIndex
@@ -1272,6 +1279,10 @@ internal class DefaultKeysBackupService @Inject constructor(
12721279
return sessionData.sharedHistory
12731280
}
12741281

1282+
private fun getPrivateKey(): ByteArray {
1283+
return byteArrayOf()
1284+
}
1285+
12751286
/* ==========================================================================================
12761287
* For test only
12771288
* ========================================================================================== */

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)