Implement AES backup algorithm (WIP need to check with valere the private key thing)
This commit is contained in:
parent
1d26207df7
commit
03272a9c8f
@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
|
|||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCurve25519AuthData
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCurve25519AuthData
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
@ -321,7 +322,8 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
put(cryptoTestData.roomId, roomKeysBackupData)
|
put(cryptoTestData.roomId, roomKeysBackupData)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
val sessionsData = algorithm.decryptSessions(keyBackupCreationInfo.recoveryKey, keysBackupData)
|
algorithm.setRecoveryKey(keyBackupCreationInfo.recoveryKey)
|
||||||
|
val sessionsData = algorithm.decryptSessions(keysBackupData)
|
||||||
val sessionData = sessionsData.firstOrNull()
|
val sessionData = sessionsData.firstOrNull()
|
||||||
assertNotNull(sessionData)
|
assertNotNull(sessionData)
|
||||||
// - Compare the decrypted megolm key with the original one
|
// - Compare the decrypted megolm key with the original one
|
||||||
|
@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.MatrixCallback
|
|||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP
|
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.failure.MatrixError
|
import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
@ -45,6 +46,7 @@ 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.MegolmBackupCreationInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
@ -630,8 +632,9 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
|
|
||||||
// Get backed up keys from the homeserver
|
// Get backed up keys from the homeserver
|
||||||
val data = getKeys(sessionId, roomId, keysVersionResult.version)
|
val data = getKeys(sessionId, roomId, keysVersionResult.version)
|
||||||
|
algorithm?.setRecoveryKey(recoveryKey)
|
||||||
val sessionsData = withContext(coroutineDispatchers.computation) {
|
val sessionsData = withContext(coroutineDispatchers.computation) {
|
||||||
algorithm?.decryptSessions(recoveryKey, data)
|
algorithm?.decryptSessions(data)
|
||||||
}.orEmpty()
|
}.orEmpty()
|
||||||
// Do not trigger a backup for them if they come from the backup version we are using
|
// Do not trigger a backup for them if they come from the backup version we are using
|
||||||
val backUp = keysVersionResult.version != keysBackupVersion?.version
|
val backUp = keysVersionResult.version != keysBackupVersion?.version
|
||||||
@ -998,7 +1001,8 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
private fun isValidRecoveryKeyForKeysBackupVersion(recoveryKey: String, keysBackupData: KeysVersionResult): Boolean {
|
private fun isValidRecoveryKeyForKeysBackupVersion(recoveryKey: String, keysBackupData: KeysVersionResult): Boolean {
|
||||||
return try {
|
return try {
|
||||||
val algorithm = algorithmFactory.create(keysBackupData)
|
val algorithm = algorithmFactory.create(keysBackupData)
|
||||||
val isValid = algorithm.keyMatches(recoveryKey)
|
val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) ?: return false
|
||||||
|
val isValid = algorithm.keyMatches(privateKey)
|
||||||
algorithm.release()
|
algorithm.release()
|
||||||
isValid
|
isValid
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
@ -1133,7 +1137,8 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
// Gather data to send to the homeserver
|
// Gather data to send to the homeserver
|
||||||
// roomId -> sessionId -> MXKeyBackupData
|
// roomId -> sessionId -> MXKeyBackupData
|
||||||
val keysBackupData = KeysBackupData()
|
val keysBackupData = KeysBackupData()
|
||||||
|
val recoveryKey = cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey
|
||||||
|
algorithm?.setRecoveryKey(recoveryKey)
|
||||||
olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
|
olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
|
||||||
val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach
|
val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach
|
||||||
val olmInboundGroupSession = olmInboundGroupSessionWrapper.session
|
val olmInboundGroupSession = olmInboundGroupSessionWrapper.session
|
||||||
@ -1242,7 +1247,9 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
}
|
}
|
||||||
?: return null
|
?: return null
|
||||||
|
|
||||||
val sessionBackupData = algorithm?.encryptSession(sessionData) ?: return null
|
val sessionBackupData = tryOrNull {
|
||||||
|
algorithm?.encryptSession(sessionData)
|
||||||
|
} ?: return null
|
||||||
return KeyBackupData(
|
return KeyBackupData(
|
||||||
firstMessageIndex = try {
|
firstMessageIndex = try {
|
||||||
olmInboundGroupSessionWrapper.session.firstKnownIndex
|
olmInboundGroupSessionWrapper.session.firstKnownIndex
|
||||||
@ -1265,6 +1272,10 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
return sessionData.sharedHistory
|
return sessionData.sharedHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getPrivateKey(): ByteArray {
|
||||||
|
return byteArrayOf()
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* For test only
|
* For test only
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
@ -20,15 +20,24 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_AES_256_BACKUP
|
|||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAes256AuthData
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAes256AuthData
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
import org.matrix.android.sdk.api.util.fromBase64
|
||||||
|
import org.matrix.android.sdk.api.util.toBase64NoPadding
|
||||||
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tools.AesHmacSha2
|
||||||
|
import org.matrix.olm.OlmException
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.security.InvalidParameterException
|
||||||
|
import java.util.Arrays
|
||||||
|
|
||||||
internal class KeysBackupAes256Algorithm(keysVersions: KeysVersionResult) : KeysBackupAlgorithm {
|
internal class KeysBackupAes256Algorithm(keysVersions: KeysVersionResult) : KeysBackupAlgorithm {
|
||||||
|
|
||||||
override val untrusted: Boolean = true
|
override val untrusted: Boolean = false
|
||||||
|
|
||||||
private val aesAuthData: MegolmBackupAes256AuthData
|
private val aesAuthData: MegolmBackupAes256AuthData
|
||||||
|
private var privateKey: ByteArray? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (keysVersions.algorithm != MXCRYPTO_ALGORITHM_AES_256_BACKUP) {
|
if (keysVersions.algorithm != MXCRYPTO_ALGORITHM_AES_256_BACKUP) {
|
||||||
@ -37,21 +46,92 @@ internal class KeysBackupAes256Algorithm(keysVersions: KeysVersionResult) : Keys
|
|||||||
aesAuthData = keysVersions.getAuthDataAsMegolmBackupAuthData() as MegolmBackupAes256AuthData
|
aesAuthData = keysVersions.getAuthDataAsMegolmBackupAuthData() as MegolmBackupAes256AuthData
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encryptSession(sessionData: MegolmSessionData): JsonDict? {
|
override fun setRecoveryKey(recoveryKey: String?) {
|
||||||
TODO("Not yet implemented")
|
privateKey = extractCurveKeyFromRecoveryKey(recoveryKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decryptSessions(recoveryKey: String, data: KeysBackupData): List<MegolmSessionData> {
|
override fun encryptSession(sessionData: MegolmSessionData): JsonDict? {
|
||||||
TODO("Not yet implemented")
|
val privateKey = privateKey
|
||||||
|
if (privateKey == null || !keyMatches(privateKey)) {
|
||||||
|
Timber.e("Key does not match")
|
||||||
|
throw IllegalStateException("Key does not match")
|
||||||
|
}
|
||||||
|
val encryptedSessionBackupData = try {
|
||||||
|
val sessionDataJson = sessionData.asBackupJson()
|
||||||
|
AesHmacSha2.encrypt(privateKey = privateKey, secretName = sessionData.sessionId ?: "", sessionDataJson)
|
||||||
|
} catch (e: OlmException) {
|
||||||
|
Timber.e(e, "Error while encrypting backup data.")
|
||||||
|
null
|
||||||
|
} ?: return null
|
||||||
|
|
||||||
|
return mapOf(
|
||||||
|
"ciphertext" to encryptedSessionBackupData.cipherRawBytes.toBase64NoPadding(),
|
||||||
|
"mac" to encryptedSessionBackupData.mac.toBase64NoPadding(),
|
||||||
|
"iv" to encryptedSessionBackupData.initializationVector.toBase64NoPadding()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decryptSessions(data: KeysBackupData): List<MegolmSessionData> {
|
||||||
|
val privateKey = privateKey
|
||||||
|
if (privateKey == null || !keyMatches(privateKey)) {
|
||||||
|
Timber.e("Invalid recovery key for this keys version")
|
||||||
|
throw InvalidParameterException("Invalid recovery key")
|
||||||
|
}
|
||||||
|
val sessionsData = ArrayList<MegolmSessionData>()
|
||||||
|
// Restore that data
|
||||||
|
var sessionsFromHsCount = 0
|
||||||
|
for ((roomIdLoop, backupData) in data.roomIdToRoomKeysBackupData) {
|
||||||
|
for ((sessionIdLoop, keyBackupData) in backupData.sessionIdToKeyBackupData) {
|
||||||
|
sessionsFromHsCount++
|
||||||
|
val sessionData = decryptSession(keyBackupData.sessionData, sessionIdLoop, roomIdLoop, privateKey)
|
||||||
|
sessionData?.let {
|
||||||
|
sessionsData.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Timber.v(
|
||||||
|
"Decrypted ${sessionsData.size} keys out of $sessionsFromHsCount from the backup store on the homeserver"
|
||||||
|
)
|
||||||
|
return sessionsData
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decryptSession(sessionData: JsonDict, sessionId: String, roomId: String, privateKey: ByteArray): MegolmSessionData? {
|
||||||
|
|
||||||
|
val cipherRawBytes = sessionData["ciphertext"]?.toString()?.fromBase64() ?: return null
|
||||||
|
val mac = sessionData["mac"]?.toString()?.fromBase64() ?: throw IllegalStateException("Bad mac")
|
||||||
|
val iv = sessionData["iv"]?.toString()?.fromBase64() ?: ByteArray(16)
|
||||||
|
|
||||||
|
val encryptionInfo = AesHmacSha2.EncryptionInfo(
|
||||||
|
cipherRawBytes = cipherRawBytes,
|
||||||
|
mac = mac,
|
||||||
|
initializationVector = iv
|
||||||
|
)
|
||||||
|
return try {
|
||||||
|
val decrypted = AesHmacSha2.decrypt(privateKey, sessionId, encryptionInfo)
|
||||||
|
createMegolmSessionData(decrypted, sessionId, roomId)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "Exception while decrypting")
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun release() {
|
override fun release() {
|
||||||
TODO("Not yet implemented")
|
privateKey?.apply {
|
||||||
|
Arrays.fill(this, 0)
|
||||||
|
}
|
||||||
|
privateKey = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override val authData: MegolmBackupAuthData = aesAuthData
|
override val authData: MegolmBackupAuthData = aesAuthData
|
||||||
|
|
||||||
override fun keyMatches(key: String): Boolean {
|
override fun keyMatches(privateKey: ByteArray): Boolean {
|
||||||
TODO("Not yet implemented")
|
return if (aesAuthData.mac != null) {
|
||||||
|
val keyCheckMac = AesHmacSha2.calculateKeyCheck(privateKey, aesAuthData.iv).mac
|
||||||
|
val authDataMac = aesAuthData.mac.fromBase64()
|
||||||
|
return keyCheckMac.contentEquals(authDataMac)
|
||||||
|
} else {
|
||||||
|
// if we have no information, we have to assume the key is right
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,43 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
|
|||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData
|
||||||
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
|
|
||||||
internal interface KeysBackupAlgorithm {
|
internal interface KeysBackupAlgorithm {
|
||||||
|
|
||||||
val authData: MegolmBackupAuthData
|
val authData: MegolmBackupAuthData
|
||||||
val untrusted: Boolean
|
val untrusted: Boolean
|
||||||
|
|
||||||
|
fun setRecoveryKey(recoveryKey: String?)
|
||||||
fun encryptSession(sessionData: MegolmSessionData): JsonDict?
|
fun encryptSession(sessionData: MegolmSessionData): JsonDict?
|
||||||
fun decryptSessions(recoveryKey: String, data: KeysBackupData): List<MegolmSessionData>
|
fun decryptSessions(data: KeysBackupData): List<MegolmSessionData>
|
||||||
fun keyMatches(key: String): Boolean
|
fun keyMatches(privateKey: ByteArray): Boolean
|
||||||
fun release()
|
fun release()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun KeysBackupAlgorithm.createMegolmSessionData(decrypted: String, sessionId: String, roomId: String): MegolmSessionData? {
|
||||||
|
val moshi = MoshiProvider.providesMoshi()
|
||||||
|
val adapter = moshi.adapter(MegolmSessionData::class.java)
|
||||||
|
val sessionBackupData: MegolmSessionData = adapter.fromJson(decrypted) ?: return null
|
||||||
|
return sessionBackupData.copy(
|
||||||
|
sessionId = sessionId,
|
||||||
|
roomId = roomId,
|
||||||
|
untrusted = untrusted
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun MegolmSessionData.asBackupJson(): String {
|
||||||
|
val sessionBackupData = mapOf(
|
||||||
|
"algorithm" to algorithm,
|
||||||
|
"sender_key" to senderKey,
|
||||||
|
"sender_claimed_keys" to senderClaimedKeys,
|
||||||
|
"forwarding_curve25519_key_chain" to (forwardingCurve25519KeyChain.orEmpty()),
|
||||||
|
"session_key" to sessionKey,
|
||||||
|
"org.matrix.msc3061.shared_history" to sharedHistory,
|
||||||
|
"untrusted" to untrusted
|
||||||
|
)
|
||||||
|
return MoshiProvider.providesMoshi()
|
||||||
|
.adapter(Map::class.java)
|
||||||
|
.toJson(sessionBackupData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromR
|
|||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
|
||||||
import org.matrix.olm.OlmException
|
import org.matrix.olm.OlmException
|
||||||
import org.matrix.olm.OlmPkDecryption
|
import org.matrix.olm.OlmPkDecryption
|
||||||
import org.matrix.olm.OlmPkEncryption
|
import org.matrix.olm.OlmPkEncryption
|
||||||
@ -40,6 +39,7 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) :
|
|||||||
private val publicKey: String
|
private val publicKey: String
|
||||||
|
|
||||||
private val encryptionKey: OlmPkEncryption
|
private val encryptionKey: OlmPkEncryption
|
||||||
|
private var privateKey: ByteArray? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (keysVersions.algorithm != MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP) {
|
if (keysVersions.algorithm != MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP) {
|
||||||
@ -52,26 +52,17 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setRecoveryKey(recoveryKey: String?) {
|
||||||
|
privateKey = extractCurveKeyFromRecoveryKey(recoveryKey)
|
||||||
|
}
|
||||||
|
|
||||||
override fun encryptSession(sessionData: MegolmSessionData): JsonDict? {
|
override fun encryptSession(sessionData: MegolmSessionData): JsonDict? {
|
||||||
val sessionBackupData = mapOf(
|
|
||||||
"algorithm" to sessionData.algorithm,
|
|
||||||
"sender_key" to sessionData.senderKey,
|
|
||||||
"sender_claimed_keys" to sessionData.senderClaimedKeys,
|
|
||||||
"forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain.orEmpty()),
|
|
||||||
"session_key" to sessionData.sessionKey,
|
|
||||||
"org.matrix.msc3061.shared_history" to sessionData.sharedHistory,
|
|
||||||
"untrusted" to sessionData.untrusted
|
|
||||||
)
|
|
||||||
|
|
||||||
val json = MoshiProvider.providesMoshi()
|
|
||||||
.adapter(Map::class.java)
|
|
||||||
.toJson(sessionBackupData)
|
|
||||||
|
|
||||||
val encryptedSessionBackupData = try {
|
val encryptedSessionBackupData = try {
|
||||||
encryptionKey.encrypt(json)
|
val sessionDataJson = sessionData.asBackupJson()
|
||||||
|
encryptionKey.encrypt(sessionDataJson)
|
||||||
} catch (e: OlmException) {
|
} catch (e: OlmException) {
|
||||||
Timber.e(e, "Error while encrypting backup data.")
|
Timber.e(e, "Error while encrypting backup data.")
|
||||||
null
|
throw e
|
||||||
} ?: return null
|
} ?: return null
|
||||||
|
|
||||||
return mapOf(
|
return mapOf(
|
||||||
@ -81,29 +72,25 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decryptSessions(recoveryKey: String, data: KeysBackupData): List<MegolmSessionData> {
|
override fun decryptSessions(data: KeysBackupData): List<MegolmSessionData> {
|
||||||
fun pkDecryptionFromRecoveryKey(recoveryKey: String): OlmPkDecryption? {
|
fun pkDecryptionFromPrivateKey(privateKey: ByteArray): OlmPkDecryption? {
|
||||||
// Extract the primary key
|
|
||||||
val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey)
|
|
||||||
// Built the PK decryption with it
|
|
||||||
var decryption: OlmPkDecryption? = null
|
var decryption: OlmPkDecryption? = null
|
||||||
if (privateKey != null) {
|
|
||||||
try {
|
try {
|
||||||
decryption = OlmPkDecryption()
|
decryption = OlmPkDecryption()
|
||||||
decryption.setPrivateKey(privateKey)
|
decryption.setPrivateKey(privateKey)
|
||||||
} catch (e: OlmException) {
|
} catch (e: OlmException) {
|
||||||
Timber.e(e, "OlmException")
|
Timber.e(e, "OlmException")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return decryption
|
return decryption
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!keyMatches(recoveryKey)) {
|
val privateKey = this.privateKey
|
||||||
|
if (privateKey == null || !keyMatches(privateKey)) {
|
||||||
Timber.e("Invalid recovery key for this keys version")
|
Timber.e("Invalid recovery key for this keys version")
|
||||||
throw InvalidParameterException("Invalid recovery key")
|
throw InvalidParameterException("Invalid recovery key")
|
||||||
}
|
}
|
||||||
// Get a PK decryption instance
|
// Get a PK decryption instance
|
||||||
val decryption = pkDecryptionFromRecoveryKey(recoveryKey)
|
val decryption = pkDecryptionFromPrivateKey(privateKey)
|
||||||
if (decryption == null) {
|
if (decryption == null) {
|
||||||
// This should not happen anymore
|
// This should not happen anymore
|
||||||
Timber.e("Invalid recovery key. Error")
|
Timber.e("Invalid recovery key. Error")
|
||||||
@ -121,6 +108,7 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
decryption.releaseDecryption()
|
||||||
Timber.v(
|
Timber.v(
|
||||||
"Decrypted ${sessionsData.size} keys out of $sessionsFromHsCount from the backup store on the homeserver"
|
"Decrypted ${sessionsData.size} keys out of $sessionsFromHsCount from the backup store on the homeserver"
|
||||||
)
|
)
|
||||||
@ -128,55 +116,36 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptSession(sessionData: JsonDict, sessionId: String, roomId: String, decryption: OlmPkDecryption): MegolmSessionData? {
|
private fun decryptSession(sessionData: JsonDict, sessionId: String, roomId: String, decryption: OlmPkDecryption): MegolmSessionData? {
|
||||||
var sessionBackupData: MegolmSessionData? = null
|
|
||||||
|
|
||||||
val ciphertext = sessionData["ciphertext"]?.toString()
|
val ciphertext = sessionData["ciphertext"]?.toString()
|
||||||
val mac = sessionData["mac"]?.toString()
|
val mac = sessionData["mac"]?.toString()
|
||||||
val ephemeralKey = sessionData["ephemeral"]?.toString()
|
val ephemeralKey = sessionData["ephemeral"]?.toString()
|
||||||
|
|
||||||
if (ciphertext != null && mac != null && ephemeralKey != null) {
|
return if (ciphertext != null && mac != null && ephemeralKey != null) {
|
||||||
val encrypted = OlmPkMessage()
|
val encrypted = OlmPkMessage()
|
||||||
encrypted.mCipherText = ciphertext
|
encrypted.mCipherText = ciphertext
|
||||||
encrypted.mMac = mac
|
encrypted.mMac = mac
|
||||||
encrypted.mEphemeralKey = ephemeralKey
|
encrypted.mEphemeralKey = ephemeralKey
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val decrypted = decryption.decrypt(encrypted)
|
val decrypted = decryption.decrypt(encrypted)
|
||||||
|
createMegolmSessionData(decrypted, sessionId, roomId)
|
||||||
val moshi = MoshiProvider.providesMoshi()
|
|
||||||
val adapter = moshi.adapter(MegolmSessionData::class.java)
|
|
||||||
|
|
||||||
sessionBackupData = adapter.fromJson(decrypted)
|
|
||||||
} catch (e: OlmException) {
|
} catch (e: OlmException) {
|
||||||
Timber.e(e, "OlmException")
|
Timber.e(e, "Exception while decrypting")
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if (sessionBackupData != null) {
|
null
|
||||||
sessionBackupData = sessionBackupData.copy(
|
|
||||||
sessionId = sessionId,
|
|
||||||
roomId = roomId,
|
|
||||||
untrusted = untrusted
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sessionBackupData
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun release() {
|
override fun release() {
|
||||||
encryptionKey.releaseEncryption()
|
encryptionKey.releaseEncryption()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val authData: MegolmBackupAuthData = curveAuthData
|
override val authData: MegolmBackupAuthData = curveAuthData
|
||||||
|
|
||||||
override fun keyMatches(key: String): Boolean {
|
override fun keyMatches(privateKey: ByteArray): Boolean {
|
||||||
fun pkPublicKeyFromRecoveryKey(recoveryKey: String): String? {
|
fun pkPublicKeyFromPrivateKey(privateKey: ByteArray): String? {
|
||||||
// Extract the primary key
|
|
||||||
val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey)
|
|
||||||
if (privateKey == null) {
|
|
||||||
Timber.w("pkPublicKeyFromRecoveryKey: private key is null")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
// Built the PK decryption with it
|
// Built the PK decryption with it
|
||||||
val decryption = OlmPkDecryption()
|
val decryption = OlmPkDecryption()
|
||||||
val pkPublicKey = try {
|
val pkPublicKey = try {
|
||||||
@ -189,7 +158,7 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) :
|
|||||||
return pkPublicKey
|
return pkPublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
val publicKey = pkPublicKeyFromRecoveryKey(key)
|
val publicKey = pkPublicKeyFromPrivateKey(privateKey)
|
||||||
if (publicKey == null) {
|
if (publicKey == null) {
|
||||||
Timber.w("Public key is null")
|
Timber.w("Public key is null")
|
||||||
return false
|
return false
|
||||||
|
@ -221,12 +221,12 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||||||
val iv = cipherContent.initializationVector?.fromBase64() ?: ByteArray(16)
|
val iv = cipherContent.initializationVector?.fromBase64() ?: ByteArray(16)
|
||||||
val cipher = cipherContent.ciphertext?.fromBase64() ?: throw SharedSecretStorageError.BadCipherText
|
val cipher = cipherContent.ciphertext?.fromBase64() ?: throw SharedSecretStorageError.BadCipherText
|
||||||
val mac = cipherContent.mac?.fromBase64() ?: throw SharedSecretStorageError.BadMac
|
val mac = cipherContent.mac?.fromBase64() ?: throw SharedSecretStorageError.BadMac
|
||||||
val encryptedResult = AesHmacSha2.Result(
|
val encryptionInfo = AesHmacSha2.EncryptionInfo(
|
||||||
cipherRawBytes = cipher,
|
cipherRawBytes = cipher,
|
||||||
initializationVector = iv,
|
initializationVector = iv,
|
||||||
mac = mac
|
mac = mac
|
||||||
)
|
)
|
||||||
return AesHmacSha2.decrypt(secretKey.privateKey, secretName, encryptedResult)
|
return AesHmacSha2.decrypt(secretKey.privateKey, secretName, encryptionInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAlgorithmsForSecret(name: String): List<KeyInfoResult> {
|
override fun getAlgorithmsForSecret(name: String): List<KeyInfoResult> {
|
||||||
|
@ -27,7 +27,7 @@ import kotlin.experimental.and
|
|||||||
|
|
||||||
internal object AesHmacSha2 {
|
internal object AesHmacSha2 {
|
||||||
|
|
||||||
class Result(
|
class EncryptionInfo(
|
||||||
val cipherRawBytes: ByteArray,
|
val cipherRawBytes: ByteArray,
|
||||||
val mac: ByteArray,
|
val mac: ByteArray,
|
||||||
val initializationVector: ByteArray
|
val initializationVector: ByteArray
|
||||||
@ -55,7 +55,7 @@ internal object AesHmacSha2 {
|
|||||||
* (We use AES-CTR to match file encryption and key exports.)
|
* (We use AES-CTR to match file encryption and key exports.)
|
||||||
*/
|
*/
|
||||||
@Throws
|
@Throws
|
||||||
fun encrypt(privateKey: ByteArray, secretName: String, clearDataBase64: String, ivString: String? = null): Result {
|
fun encrypt(privateKey: ByteArray, secretName: String, clearDataBase64: String, ivString: String? = null): EncryptionInfo {
|
||||||
val pseudoRandomKey = HkdfSha256.deriveSecret(
|
val pseudoRandomKey = HkdfSha256.deriveSecret(
|
||||||
privateKey,
|
privateKey,
|
||||||
ByteArray(32) { 0.toByte() },
|
ByteArray(32) { 0.toByte() },
|
||||||
@ -94,14 +94,14 @@ internal object AesHmacSha2 {
|
|||||||
mac.init(macKeySpec)
|
mac.init(macKeySpec)
|
||||||
val digest = mac.doFinal(cipherBytes)
|
val digest = mac.doFinal(cipherBytes)
|
||||||
|
|
||||||
return Result(
|
return EncryptionInfo(
|
||||||
cipherRawBytes = cipherBytes,
|
cipherRawBytes = cipherBytes,
|
||||||
initializationVector = iv,
|
initializationVector = iv,
|
||||||
mac = digest
|
mac = digest
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decrypt(privateKey: ByteArray, secretName: String, aesHmacSha2Result: Result): String {
|
fun decrypt(privateKey: ByteArray, secretName: String, aesHmacSha2Result: EncryptionInfo): String {
|
||||||
val pseudoRandomKey = HkdfSha256.deriveSecret(
|
val pseudoRandomKey = HkdfSha256.deriveSecret(
|
||||||
privateKey,
|
privateKey,
|
||||||
zeroByteArray(32),
|
zeroByteArray(32),
|
||||||
@ -140,9 +140,9 @@ internal object AesHmacSha2 {
|
|||||||
* @param {ByteArray} [key] the key to use
|
* @param {ByteArray} [key] the key to use
|
||||||
* @param {string} [iv] The initialization vector as a base64-encoded string.
|
* @param {string} [iv] The initialization vector as a base64-encoded string.
|
||||||
* If omitted, a random initialization vector will be created.
|
* If omitted, a random initialization vector will be created.
|
||||||
* @return [Result] An object that contains, `mac` and `iv` properties.
|
* @return [EncryptionInfo] An object that contains, `mac` and `iv` properties.
|
||||||
*/
|
*/
|
||||||
fun calculateKeyCheck(key: ByteArray, iv: String?): Result {
|
fun calculateKeyCheck(key: ByteArray, iv: String?): EncryptionInfo {
|
||||||
val zerosStr = String(zeroByteArray(32))
|
val zerosStr = String(zeroByteArray(32))
|
||||||
return encrypt(key, "", zerosStr, iv)
|
return encrypt(key, "", zerosStr, iv)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user