@@ -20,15 +20,24 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_AES_256_BACKUP
20
20
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
21
21
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAes256AuthData
22
22
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
23
+ import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
23
24
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
24
27
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
25
28
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
26
34
27
35
internal class KeysBackupAes256Algorithm (keysVersions : KeysVersionResult ) : KeysBackupAlgorithm {
28
36
29
- override val untrusted: Boolean = true
37
+ override val untrusted: Boolean = false
30
38
31
39
private val aesAuthData: MegolmBackupAes256AuthData
40
+ private var privateKey: ByteArray? = null
32
41
33
42
init {
34
43
if (keysVersions.algorithm != MXCRYPTO_ALGORITHM_AES_256_BACKUP ) {
@@ -37,21 +46,92 @@ internal class KeysBackupAes256Algorithm(keysVersions: KeysVersionResult) : Keys
37
46
aesAuthData = keysVersions.getAuthDataAsMegolmBackupAuthData() as MegolmBackupAes256AuthData
38
47
}
39
48
49
+ override fun setRecoveryKey (recoveryKey : String? ) {
50
+ privateKey = extractCurveKeyFromRecoveryKey(recoveryKey)
51
+ }
52
+
40
53
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
+ )
42
72
}
43
73
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
+ }
46
116
}
47
117
48
118
override fun release () {
49
- TODO (" Not yet implemented" )
119
+ privateKey?.apply {
120
+ Arrays .fill(this , 0 )
121
+ }
122
+ privateKey = null
50
123
}
51
124
52
125
override val authData: MegolmBackupAuthData = aesAuthData
53
126
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
+ }
56
136
}
57
137
}
0 commit comments