Changed Encryption algorithm of 4S
This commit is contained in:
parent
e2e4ddf5ba
commit
0064934db9
@ -16,26 +16,25 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.ssss
|
package im.vector.matrix.android.internal.crypto.ssss
|
||||||
|
|
||||||
import android.util.Base64
|
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import im.vector.matrix.android.InstrumentedTest
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
|
|
||||||
import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
|
import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
|
||||||
import im.vector.matrix.android.api.session.securestorage.KeySigner
|
import im.vector.matrix.android.api.session.securestorage.KeySigner
|
||||||
|
import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec
|
||||||
import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent
|
import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent
|
||||||
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
|
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
|
||||||
|
import im.vector.matrix.android.api.session.securestorage.SsssKeySpec
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.common.CommonTestHelper
|
import im.vector.matrix.android.common.CommonTestHelper
|
||||||
import im.vector.matrix.android.common.SessionTestParams
|
import im.vector.matrix.android.common.SessionTestParams
|
||||||
import im.vector.matrix.android.common.TestConstants
|
import im.vector.matrix.android.common.TestConstants
|
||||||
import im.vector.matrix.android.common.TestMatrixCallback
|
import im.vector.matrix.android.common.TestMatrixCallback
|
||||||
import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2
|
import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorageService
|
import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorageService
|
||||||
import im.vector.matrix.android.internal.crypto.tools.withOlmDecryption
|
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
@ -71,7 +70,7 @@ class QuadSTests : InstrumentedTest {
|
|||||||
|
|
||||||
val TEST_KEY_ID = "my.test.Key"
|
val TEST_KEY_ID = "my.test.Key"
|
||||||
|
|
||||||
val ssssKeyCreationInfo = mTestHelper.doSync<SsssKeyCreationInfo> {
|
mTestHelper.doSync<SsssKeyCreationInfo> {
|
||||||
quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it)
|
quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,16 +94,9 @@ class QuadSTests : InstrumentedTest {
|
|||||||
assertNotNull("Key should be stored in account data", accountData)
|
assertNotNull("Key should be stored in account data", accountData)
|
||||||
val parsed = SecretStorageKeyContent.fromJson(accountData!!.content)
|
val parsed = SecretStorageKeyContent.fromJson(accountData!!.content)
|
||||||
assertNotNull("Key Content cannot be parsed", parsed)
|
assertNotNull("Key Content cannot be parsed", parsed)
|
||||||
assertEquals("Unexpected Algorithm", SSSS_ALGORITHM_CURVE25519_AES_SHA2, parsed!!.algorithm)
|
assertEquals("Unexpected Algorithm", SSSS_ALGORITHM_AES_HMAC_SHA2, parsed!!.algorithm)
|
||||||
assertEquals("Unexpected key name", "Test Key", parsed.name)
|
assertEquals("Unexpected key name", "Test Key", parsed.name)
|
||||||
assertNull("Key was not generated from passphrase", parsed.passphrase)
|
assertNull("Key was not generated from passphrase", parsed.passphrase)
|
||||||
assertNotNull("Pubkey should be defined", parsed.publicKey)
|
|
||||||
|
|
||||||
val privateKeySpec = Curve25519AesSha2KeySpec.fromRecoveryKey(ssssKeyCreationInfo.recoveryKey)
|
|
||||||
val pubKey = withOlmDecryption { olmPkDecryption ->
|
|
||||||
olmPkDecryption.setPrivateKey(privateKeySpec!!.privateKey)
|
|
||||||
}
|
|
||||||
assertEquals("Unexpected Public Key", pubKey, parsed.publicKey)
|
|
||||||
|
|
||||||
// Set as default key
|
// Set as default key
|
||||||
quadS.setDefaultKey(TEST_KEY_ID, object : MatrixCallback<Unit> {})
|
quadS.setDefaultKey(TEST_KEY_ID, object : MatrixCallback<Unit> {})
|
||||||
@ -137,13 +129,15 @@ class QuadSTests : InstrumentedTest {
|
|||||||
val keyId = "My.Key"
|
val keyId = "My.Key"
|
||||||
val info = generatedSecret(aliceSession, keyId, true)
|
val info = generatedSecret(aliceSession, keyId, true)
|
||||||
|
|
||||||
|
val keySpec = RawBytesKeySpec.fromRecoveryKey(info.recoveryKey)
|
||||||
|
|
||||||
// Store a secret
|
// Store a secret
|
||||||
val clearSecret = Base64.encodeToString("42".toByteArray(), Base64.NO_PADDING or Base64.NO_WRAP)
|
val clearSecret = "42".toByteArray().toBase64NoPadding()
|
||||||
mTestHelper.doSync<Unit> {
|
mTestHelper.doSync<Unit> {
|
||||||
aliceSession.sharedSecretStorageService.storeSecret(
|
aliceSession.sharedSecretStorageService.storeSecret(
|
||||||
"secret.of.life",
|
"secret.of.life",
|
||||||
clearSecret,
|
clearSecret,
|
||||||
null, // default key
|
listOf(Pair<String?, SsssKeySpec?>(null, keySpec)), // default key
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -157,14 +151,13 @@ class QuadSTests : InstrumentedTest {
|
|||||||
val secret = EncryptedSecretContent.fromJson(encryptedContent?.get(keyId))
|
val secret = EncryptedSecretContent.fromJson(encryptedContent?.get(keyId))
|
||||||
assertNotNull(secret?.ciphertext)
|
assertNotNull(secret?.ciphertext)
|
||||||
assertNotNull(secret?.mac)
|
assertNotNull(secret?.mac)
|
||||||
assertNotNull(secret?.ephemeral)
|
assertNotNull(secret?.initializationVector)
|
||||||
|
|
||||||
// Try to decrypt??
|
// Try to decrypt??
|
||||||
|
|
||||||
val keySpec = Curve25519AesSha2KeySpec.fromRecoveryKey(info.recoveryKey)
|
|
||||||
|
|
||||||
val decryptedSecret = mTestHelper.doSync<String> {
|
val decryptedSecret = mTestHelper.doSync<String> {
|
||||||
aliceSession.sharedSecretStorageService.getSecret("secret.of.life",
|
aliceSession.sharedSecretStorageService.getSecret(
|
||||||
|
"secret.of.life",
|
||||||
null, // default key
|
null, // default key
|
||||||
keySpec!!,
|
keySpec!!,
|
||||||
it
|
it
|
||||||
@ -209,7 +202,10 @@ class QuadSTests : InstrumentedTest {
|
|||||||
aliceSession.sharedSecretStorageService.storeSecret(
|
aliceSession.sharedSecretStorageService.storeSecret(
|
||||||
"my.secret",
|
"my.secret",
|
||||||
mySecretText.toByteArray().toBase64NoPadding(),
|
mySecretText.toByteArray().toBase64NoPadding(),
|
||||||
listOf(keyId1, keyId2),
|
listOf(
|
||||||
|
keyId1 to RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey),
|
||||||
|
keyId2 to RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)
|
||||||
|
),
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -226,7 +222,7 @@ class QuadSTests : InstrumentedTest {
|
|||||||
mTestHelper.doSync<String> {
|
mTestHelper.doSync<String> {
|
||||||
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||||
keyId1,
|
keyId1,
|
||||||
Curve25519AesSha2KeySpec.fromRecoveryKey(key1Info.recoveryKey)!!,
|
RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!,
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -234,7 +230,7 @@ class QuadSTests : InstrumentedTest {
|
|||||||
mTestHelper.doSync<String> {
|
mTestHelper.doSync<String> {
|
||||||
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||||
keyId2,
|
keyId2,
|
||||||
Curve25519AesSha2KeySpec.fromRecoveryKey(key2Info.recoveryKey)!!,
|
RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!,
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -255,7 +251,7 @@ class QuadSTests : InstrumentedTest {
|
|||||||
aliceSession.sharedSecretStorageService.storeSecret(
|
aliceSession.sharedSecretStorageService.storeSecret(
|
||||||
"my.secret",
|
"my.secret",
|
||||||
mySecretText.toByteArray().toBase64NoPadding(),
|
mySecretText.toByteArray().toBase64NoPadding(),
|
||||||
listOf(keyId1),
|
listOf(keyId1 to RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)),
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -264,7 +260,7 @@ class QuadSTests : InstrumentedTest {
|
|||||||
var error = false
|
var error = false
|
||||||
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||||
keyId1,
|
keyId1,
|
||||||
Curve25519AesSha2KeySpec.fromPassphrase(
|
RawBytesKeySpec.fromPassphrase(
|
||||||
"A bad passphrase",
|
"A bad passphrase",
|
||||||
key1Info.content?.passphrase?.salt ?: "",
|
key1Info.content?.passphrase?.salt ?: "",
|
||||||
key1Info.content?.passphrase?.iterations ?: 0,
|
key1Info.content?.passphrase?.iterations ?: 0,
|
||||||
@ -289,7 +285,7 @@ class QuadSTests : InstrumentedTest {
|
|||||||
mTestHelper.doSync<String> {
|
mTestHelper.doSync<String> {
|
||||||
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||||
keyId1,
|
keyId1,
|
||||||
Curve25519AesSha2KeySpec.fromPassphrase(
|
RawBytesKeySpec.fromPassphrase(
|
||||||
passphrase,
|
passphrase,
|
||||||
key1Info.content?.passphrase?.salt ?: "",
|
key1Info.content?.passphrase?.salt ?: "",
|
||||||
key1Info.content?.passphrase?.iterations ?: 0,
|
key1Info.content?.passphrase?.iterations ?: 0,
|
||||||
|
@ -32,7 +32,8 @@ data class EncryptedSecretContent(
|
|||||||
/** unpadded base64-encoded ciphertext */
|
/** unpadded base64-encoded ciphertext */
|
||||||
@Json(name = "ciphertext") val ciphertext: String? = null,
|
@Json(name = "ciphertext") val ciphertext: String? = null,
|
||||||
@Json(name = "mac") val mac: String? = null,
|
@Json(name = "mac") val mac: String? = null,
|
||||||
@Json(name = "ephemeral") val ephemeral: String? = null
|
@Json(name = "ephemeral") val ephemeral: String? = null,
|
||||||
|
@Json(name = "iv") val initializationVector: String? = null
|
||||||
) : AccountDataContent {
|
) : AccountDataContent {
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
|
@ -27,5 +27,6 @@ sealed class SharedSecretStorageError(message: String?) : Throwable(message) {
|
|||||||
|
|
||||||
object BadKeyFormat : SharedSecretStorageError("Bad Key Format")
|
object BadKeyFormat : SharedSecretStorageError("Bad Key Format")
|
||||||
object ParsingError : SharedSecretStorageError("parsing Error")
|
object ParsingError : SharedSecretStorageError("parsing Error")
|
||||||
|
object BadMac : SharedSecretStorageError("Bad mac")
|
||||||
data class OtherError(val reason: Throwable) : SharedSecretStorageError(reason.localizedMessage)
|
data class OtherError(val reason: Throwable) : SharedSecretStorageError(reason.localizedMessage)
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ interface SharedSecretStorageService {
|
|||||||
*/
|
*/
|
||||||
fun generateKey(keyId: String,
|
fun generateKey(keyId: String,
|
||||||
keyName: String,
|
keyName: String,
|
||||||
keySigner: KeySigner,
|
keySigner: KeySigner?,
|
||||||
callback: MatrixCallback<SsssKeyCreationInfo>)
|
callback: MatrixCallback<SsssKeyCreationInfo>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,7 +92,7 @@ interface SharedSecretStorageService {
|
|||||||
* @param secret The secret contents.
|
* @param secret The secret contents.
|
||||||
* @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret.
|
* @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret.
|
||||||
*/
|
*/
|
||||||
fun storeSecret(name: String, secretBase64: String, keys: List<String>?, callback: MatrixCallback<Unit>)
|
fun storeSecret(name: String, secretBase64: String, keys: List<Pair<String?, SsssKeySpec?>>, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this call to determine which SSSSKeySpec to use for requesting secret
|
* Use this call to determine which SSSSKeySpec to use for requesting secret
|
||||||
@ -104,7 +104,7 @@ interface SharedSecretStorageService {
|
|||||||
*
|
*
|
||||||
* @param name The name of the secret
|
* @param name The name of the secret
|
||||||
* @param keyId The id of the key that should be used to decrypt (null for default key)
|
* @param keyId The id of the key that should be used to decrypt (null for default key)
|
||||||
* @param secretKey the secret key to use (@see #Curve25519AesSha2KeySpec)
|
* @param secretKey the secret key to use (@see #RawBytesKeySpec)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback<String>)
|
fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback<String>)
|
||||||
|
@ -23,14 +23,14 @@ import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyF
|
|||||||
/** Tag class */
|
/** Tag class */
|
||||||
interface SsssKeySpec
|
interface SsssKeySpec
|
||||||
|
|
||||||
data class Curve25519AesSha2KeySpec(
|
data class RawBytesKeySpec(
|
||||||
val privateKey: ByteArray
|
val privateKey: ByteArray
|
||||||
) : SsssKeySpec {
|
) : SsssKeySpec {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun fromPassphrase(passphrase: String, salt: String, iterations: Int, progressListener: ProgressListener?): Curve25519AesSha2KeySpec {
|
fun fromPassphrase(passphrase: String, salt: String, iterations: Int, progressListener: ProgressListener?): RawBytesKeySpec {
|
||||||
return Curve25519AesSha2KeySpec(
|
return RawBytesKeySpec(
|
||||||
privateKey = deriveKey(
|
privateKey = deriveKey(
|
||||||
passphrase,
|
passphrase,
|
||||||
salt,
|
salt,
|
||||||
@ -40,9 +40,9 @@ data class Curve25519AesSha2KeySpec(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromRecoveryKey(recoveryKey: String): Curve25519AesSha2KeySpec? {
|
fun fromRecoveryKey(recoveryKey: String): RawBytesKeySpec? {
|
||||||
return extractCurveKeyFromRecoveryKey(recoveryKey)?.let {
|
return extractCurveKeyFromRecoveryKey(recoveryKey)?.let {
|
||||||
Curve25519AesSha2KeySpec(
|
RawBytesKeySpec(
|
||||||
privateKey = it
|
privateKey = it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -53,7 +53,7 @@ data class Curve25519AesSha2KeySpec(
|
|||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
other as Curve25519AesSha2KeySpec
|
other as RawBytesKeySpec
|
||||||
|
|
||||||
if (!privateKey.contentEquals(other.privateKey)) return false
|
if (!privateKey.contentEquals(other.privateKey)) return false
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@ const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-
|
|||||||
* Secured Shared Storage algorithm constant
|
* Secured Shared Storage algorithm constant
|
||||||
*/
|
*/
|
||||||
const val SSSS_ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2"
|
const val SSSS_ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2"
|
||||||
|
/* Secrets are encrypted using AES-CTR-256 and MACed using HMAC-SHA-256. **/
|
||||||
|
const val SSSS_ALGORITHM_AES_HMAC_SHA2 = "m.secret_storage.v1.aes-hmac-sha2"
|
||||||
|
|
||||||
// TODO Refacto: use this constants everywhere
|
// TODO Refacto: use this constants everywhere
|
||||||
const val ed25519 = "ed25519"
|
const val ed25519 = "ed25519"
|
||||||
|
@ -17,38 +17,42 @@
|
|||||||
package im.vector.matrix.android.internal.crypto.secrets
|
package im.vector.matrix.android.internal.crypto.secrets
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.extensions.orFalse
|
||||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||||
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
||||||
import im.vector.matrix.android.api.session.events.model.toContent
|
import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
|
|
||||||
import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
|
import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
|
||||||
import im.vector.matrix.android.api.session.securestorage.IntegrityResult
|
import im.vector.matrix.android.api.session.securestorage.IntegrityResult
|
||||||
import im.vector.matrix.android.api.session.securestorage.KeyInfo
|
import im.vector.matrix.android.api.session.securestorage.KeyInfo
|
||||||
import im.vector.matrix.android.api.session.securestorage.KeyInfoResult
|
import im.vector.matrix.android.api.session.securestorage.KeyInfoResult
|
||||||
import im.vector.matrix.android.api.session.securestorage.KeySigner
|
import im.vector.matrix.android.api.session.securestorage.KeySigner
|
||||||
import im.vector.matrix.android.api.session.securestorage.SsssKeySpec
|
import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec
|
||||||
import im.vector.matrix.android.api.session.securestorage.SsssPassphrase
|
|
||||||
import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent
|
import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent
|
||||||
import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageError
|
import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageError
|
||||||
import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
|
import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
|
||||||
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
|
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
|
||||||
|
import im.vector.matrix.android.api.session.securestorage.SsssKeySpec
|
||||||
|
import im.vector.matrix.android.api.session.securestorage.SsssPassphrase
|
||||||
|
import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
|
||||||
import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2
|
import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64NoPadding
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.generatePrivateKeyWithPassword
|
import im.vector.matrix.android.internal.crypto.keysbackup.generatePrivateKeyWithPassword
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
|
import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
|
||||||
|
import im.vector.matrix.android.internal.crypto.tools.HkdfSha256
|
||||||
import im.vector.matrix.android.internal.crypto.tools.withOlmDecryption
|
import im.vector.matrix.android.internal.crypto.tools.withOlmDecryption
|
||||||
import im.vector.matrix.android.internal.crypto.tools.withOlmEncryption
|
|
||||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.olm.OlmPkMessage
|
import org.matrix.olm.OlmPkMessage
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.spec.IvParameterSpec
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.experimental.and
|
||||||
private data class Key(
|
|
||||||
val publicKey: String,
|
|
||||||
@Suppress("ArrayInDataClass")
|
|
||||||
val privateKey: ByteArray
|
|
||||||
)
|
|
||||||
|
|
||||||
internal class DefaultSharedSecretStorageService @Inject constructor(
|
internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||||
private val accountDataService: AccountDataService,
|
private val accountDataService: AccountDataService,
|
||||||
@ -58,14 +62,12 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||||||
|
|
||||||
override fun generateKey(keyId: String,
|
override fun generateKey(keyId: String,
|
||||||
keyName: String,
|
keyName: String,
|
||||||
keySigner: KeySigner,
|
keySigner: KeySigner?,
|
||||||
callback: MatrixCallback<SsssKeyCreationInfo>) {
|
callback: MatrixCallback<SsssKeyCreationInfo>) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
val key = try {
|
val key = try {
|
||||||
withOlmDecryption { olmPkDecryption ->
|
ByteArray(32).also {
|
||||||
val pubKey = olmPkDecryption.generateKey()
|
SecureRandom().nextBytes(it)
|
||||||
val privateKey = olmPkDecryption.privateKey()
|
|
||||||
Key(pubKey, privateKey)
|
|
||||||
}
|
}
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
callback.onFailure(failure)
|
callback.onFailure(failure)
|
||||||
@ -74,12 +76,11 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||||||
|
|
||||||
val storageKeyContent = SecretStorageKeyContent(
|
val storageKeyContent = SecretStorageKeyContent(
|
||||||
name = keyName,
|
name = keyName,
|
||||||
algorithm = SSSS_ALGORITHM_CURVE25519_AES_SHA2,
|
algorithm = SSSS_ALGORITHM_AES_HMAC_SHA2,
|
||||||
passphrase = null,
|
passphrase = null
|
||||||
publicKey = key.publicKey
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let {
|
val signedContent = keySigner?.sign(storageKeyContent.canonicalSignable())?.let {
|
||||||
storageKeyContent.copy(
|
storageKeyContent.copy(
|
||||||
signatures = it
|
signatures = it
|
||||||
)
|
)
|
||||||
@ -97,7 +98,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||||||
callback.onSuccess(SsssKeyCreationInfo(
|
callback.onSuccess(SsssKeyCreationInfo(
|
||||||
keyId = keyId,
|
keyId = keyId,
|
||||||
content = storageKeyContent,
|
content = storageKeyContent,
|
||||||
recoveryKey = computeRecoveryKey(key.privateKey)
|
recoveryKey = computeRecoveryKey(key)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,19 +115,9 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener)
|
val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener)
|
||||||
|
|
||||||
val pubKey = try {
|
|
||||||
withOlmDecryption { olmPkDecryption ->
|
|
||||||
olmPkDecryption.setPrivateKey(privatePart.privateKey)
|
|
||||||
}
|
|
||||||
} catch (failure: Throwable) {
|
|
||||||
callback.onFailure(failure)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val storageKeyContent = SecretStorageKeyContent(
|
val storageKeyContent = SecretStorageKeyContent(
|
||||||
algorithm = SSSS_ALGORITHM_CURVE25519_AES_SHA2,
|
algorithm = SSSS_ALGORITHM_AES_HMAC_SHA2,
|
||||||
passphrase = SsssPassphrase(algorithm = "m.pbkdf2", iterations = privatePart.iterations, salt = privatePart.salt),
|
passphrase = SsssPassphrase(algorithm = "m.pbkdf2", iterations = privatePart.iterations, salt = privatePart.salt)
|
||||||
publicKey = pubKey
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let {
|
val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let {
|
||||||
@ -189,51 +180,19 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||||||
return getKey(keyId)
|
return getKey(keyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun storeSecret(name: String, secretBase64: String, keys: List<String>?, callback: MatrixCallback<Unit>) {
|
override fun storeSecret(name: String, secretBase64: String, keys: List<Pair<String?, SsssKeySpec?>>, callback: MatrixCallback<Unit>) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
val encryptedContents = HashMap<String, EncryptedSecretContent>()
|
val encryptedContents = HashMap<String, EncryptedSecretContent>()
|
||||||
try {
|
try {
|
||||||
if (keys.isNullOrEmpty()) {
|
|
||||||
// use default key
|
|
||||||
when (val key = getDefaultKey()) {
|
|
||||||
is KeyInfoResult.Success -> {
|
|
||||||
if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_CURVE25519_AES_SHA2) {
|
|
||||||
val encryptedResult = withOlmEncryption { olmEncrypt ->
|
|
||||||
olmEncrypt.setRecipientKey(key.keyInfo.content.publicKey)
|
|
||||||
olmEncrypt.encrypt(secretBase64)
|
|
||||||
}
|
|
||||||
encryptedContents[key.keyInfo.id] = EncryptedSecretContent(
|
|
||||||
ciphertext = encryptedResult.mCipherText,
|
|
||||||
ephemeral = encryptedResult.mEphemeralKey,
|
|
||||||
mac = encryptedResult.mMac
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// Unknown algorithm
|
|
||||||
callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: ""))
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is KeyInfoResult.Error -> {
|
|
||||||
callback.onFailure(key.error)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
keys.forEach {
|
keys.forEach {
|
||||||
val keyId = it
|
val keyId = it.first
|
||||||
// encrypt the content
|
// encrypt the content
|
||||||
when (val key = getKey(keyId)) {
|
when (val key = keyId?.let { getKey(keyId) } ?: getDefaultKey()) {
|
||||||
is KeyInfoResult.Success -> {
|
is KeyInfoResult.Success -> {
|
||||||
if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_CURVE25519_AES_SHA2) {
|
if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_AES_HMAC_SHA2) {
|
||||||
val encryptedResult = withOlmEncryption { olmEncrypt ->
|
encryptAesHmacSha2(it.second!!, name, secretBase64).let {
|
||||||
olmEncrypt.setRecipientKey(key.keyInfo.content.publicKey)
|
encryptedContents[key.keyInfo.id] = it
|
||||||
olmEncrypt.encrypt(secretBase64)
|
|
||||||
}
|
}
|
||||||
encryptedContents[keyId] = EncryptedSecretContent(
|
|
||||||
ciphertext = encryptedResult.mCipherText,
|
|
||||||
ephemeral = encryptedResult.mEphemeralKey,
|
|
||||||
mac = encryptedResult.mMac
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
// Unknown algorithm
|
// Unknown algorithm
|
||||||
callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: ""))
|
callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: ""))
|
||||||
@ -246,7 +205,6 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
accountDataService.updateAccountData(
|
accountDataService.updateAccountData(
|
||||||
type = name,
|
type = name,
|
||||||
@ -259,8 +217,107 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||||||
callback.onFailure(failure)
|
callback.onFailure(failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add default key
|
/**
|
||||||
|
* Encrytion algorithm m.secret_storage.v1.aes-hmac-sha2
|
||||||
|
* Secrets are encrypted using AES-CTR-256 and MACed using HMAC-SHA-256. The data is encrypted and MACed as follows:
|
||||||
|
*
|
||||||
|
* Given the secret storage key, generate 64 bytes by performing an HKDF with SHA-256 as the hash, a salt of 32 bytes of 0, and with the secret name as the info.
|
||||||
|
*
|
||||||
|
* The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key
|
||||||
|
*
|
||||||
|
* Generate 16 random bytes, set bit 63 to 0 (in order to work around differences in AES-CTR implementations), and use this as the AES initialization vector.
|
||||||
|
* This becomes the iv property, encoded using base64.
|
||||||
|
*
|
||||||
|
* Encrypt the data using AES-CTR-256 using the AES key generated above.
|
||||||
|
*
|
||||||
|
* This encrypted data, encoded using base64, becomes the ciphertext property.
|
||||||
|
*
|
||||||
|
* Pass the raw encrypted data (prior to base64 encoding) through HMAC-SHA-256 using the MAC key generated above.
|
||||||
|
* The resulting MAC is base64-encoded and becomes the mac property.
|
||||||
|
* (We use AES-CTR to match file encryption and key exports.)
|
||||||
|
*/
|
||||||
|
@Throws
|
||||||
|
private fun encryptAesHmacSha2(secretKey: SsssKeySpec, secretName: String, clearDataBase64: String): EncryptedSecretContent {
|
||||||
|
secretKey as RawBytesKeySpec
|
||||||
|
val pseudoRandomKey = HkdfSha256.deriveSecret(
|
||||||
|
secretKey.privateKey,
|
||||||
|
ByteArray(32) { 0.toByte() },
|
||||||
|
secretName.toByteArray(),
|
||||||
|
64)
|
||||||
|
|
||||||
|
// The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key
|
||||||
|
val aesKey = pseudoRandomKey.copyOfRange(0, 32)
|
||||||
|
val macKey = pseudoRandomKey.copyOfRange(32, 64)
|
||||||
|
|
||||||
|
val secureRandom = SecureRandom()
|
||||||
|
val iv = ByteArray(16)
|
||||||
|
secureRandom.nextBytes(iv)
|
||||||
|
|
||||||
|
// clear bit 63 of the salt to stop us hitting the 64-bit counter boundary
|
||||||
|
// (which would mean we wouldn't be able to decrypt on Android). The loss
|
||||||
|
// of a single bit of salt is a price we have to pay.
|
||||||
|
iv[9] = iv[9] and 0x7f
|
||||||
|
|
||||||
|
val cipher = Cipher.getInstance("AES/CTR/NoPadding")
|
||||||
|
|
||||||
|
val secretKeySpec = SecretKeySpec(aesKey, "AES")
|
||||||
|
val ivParameterSpec = IvParameterSpec(iv)
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
|
||||||
|
// secret are not that big, just do Final
|
||||||
|
val cipherBytes = cipher.doFinal(clearDataBase64.fromBase64NoPadding())
|
||||||
|
require(cipherBytes.isNotEmpty())
|
||||||
|
|
||||||
|
val macKeySpec = SecretKeySpec(macKey, "HmacSHA256")
|
||||||
|
val mac = Mac.getInstance("HmacSHA256")
|
||||||
|
mac.init(macKeySpec)
|
||||||
|
val digest = mac.doFinal(cipherBytes)
|
||||||
|
|
||||||
|
return EncryptedSecretContent(
|
||||||
|
ciphertext = cipherBytes.toBase64NoPadding(),
|
||||||
|
initializationVector = iv.toBase64NoPadding(),
|
||||||
|
mac = digest.toBase64NoPadding()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decryptAesHmacSha2(secretKey: SsssKeySpec, secretName: String, cipherContent: EncryptedSecretContent): String {
|
||||||
|
secretKey as RawBytesKeySpec
|
||||||
|
val pseudoRandomKey = HkdfSha256.deriveSecret(
|
||||||
|
secretKey.privateKey,
|
||||||
|
ByteArray(32) { 0.toByte() },
|
||||||
|
secretName.toByteArray(),
|
||||||
|
64)
|
||||||
|
|
||||||
|
// The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key
|
||||||
|
val aesKey = pseudoRandomKey.copyOfRange(0, 32)
|
||||||
|
val macKey = pseudoRandomKey.copyOfRange(32, 64)
|
||||||
|
|
||||||
|
val iv = cipherContent.initializationVector?.fromBase64NoPadding() ?: ByteArray(16)
|
||||||
|
|
||||||
|
val cipherRawBytes = cipherContent.ciphertext!!.fromBase64NoPadding()
|
||||||
|
|
||||||
|
val cipher = Cipher.getInstance("AES/CTR/NoPadding")
|
||||||
|
|
||||||
|
val secretKeySpec = SecretKeySpec(aesKey, "AES")
|
||||||
|
val ivParameterSpec = IvParameterSpec(iv)
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
|
||||||
|
// secret are not that big, just do Final
|
||||||
|
val decryptedSecret = cipher.doFinal(cipherRawBytes)
|
||||||
|
|
||||||
|
require(decryptedSecret.isNotEmpty())
|
||||||
|
|
||||||
|
// Check Signature
|
||||||
|
val macKeySpec = SecretKeySpec(macKey, "HmacSHA256")
|
||||||
|
val mac = Mac.getInstance("HmacSHA256").apply { init(macKeySpec) }
|
||||||
|
val digest = mac.doFinal(cipherRawBytes)
|
||||||
|
|
||||||
|
if (!cipherContent.mac?.fromBase64NoPadding()?.contentEquals(digest).orFalse()) {
|
||||||
|
throw SharedSecretStorageError.BadMac
|
||||||
|
} else {
|
||||||
|
// we are good
|
||||||
|
return decryptedSecret.toBase64NoPadding()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAlgorithmsForSecret(name: String): List<KeyInfoResult> {
|
override fun getAlgorithmsForSecret(name: String): List<KeyInfoResult> {
|
||||||
@ -300,7 +357,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||||||
|
|
||||||
val algorithm = key.keyInfo.content
|
val algorithm = key.keyInfo.content
|
||||||
if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) {
|
if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) {
|
||||||
val keySpec = secretKey as? Curve25519AesSha2KeySpec ?: return Unit.also {
|
val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also {
|
||||||
callback.onFailure(SharedSecretStorageError.BadKeyFormat)
|
callback.onFailure(SharedSecretStorageError.BadKeyFormat)
|
||||||
}
|
}
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
@ -318,6 +375,15 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}.foldToCallback(callback)
|
}.foldToCallback(callback)
|
||||||
}
|
}
|
||||||
|
} else if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) {
|
||||||
|
val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also {
|
||||||
|
callback.onFailure(SharedSecretStorageError.BadKeyFormat)
|
||||||
|
}
|
||||||
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
|
kotlin.runCatching {
|
||||||
|
decryptAesHmacSha2(keySpec, name, secretContent)
|
||||||
|
}.foldToCallback(callback)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
callback.onFailure(SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: ""))
|
callback.onFailure(SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: ""))
|
||||||
}
|
}
|
||||||
@ -343,7 +409,8 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||||||
val keyInfo = (keyInfoResult as? KeyInfoResult.Success)?.keyInfo
|
val keyInfo = (keyInfoResult as? KeyInfoResult.Success)?.keyInfo
|
||||||
?: return IntegrityResult.Error(SharedSecretStorageError.UnknownKey(keyId ?: ""))
|
?: return IntegrityResult.Error(SharedSecretStorageError.UnknownKey(keyId ?: ""))
|
||||||
|
|
||||||
if (keyInfo.content.algorithm != SSSS_ALGORITHM_CURVE25519_AES_SHA2) {
|
if (keyInfo.content.algorithm != SSSS_ALGORITHM_AES_HMAC_SHA2
|
||||||
|
|| keyInfo.content.algorithm != SSSS_ALGORITHM_CURVE25519_AES_SHA2) {
|
||||||
// Unsupported algorithm
|
// Unsupported algorithm
|
||||||
return IntegrityResult.Error(
|
return IntegrityResult.Error(
|
||||||
SharedSecretStorageError.UnsupportedAlgorithm(keyInfo.content.algorithm ?: "")
|
SharedSecretStorageError.UnsupportedAlgorithm(keyInfo.content.algorithm ?: "")
|
||||||
|
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Square, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.internal.crypto.tools
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
import kotlin.math.ceil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HMAC-based Extract-and-Expand Key Derivation Function (HkdfSha256)
|
||||||
|
* [RFC-5869] https://tools.ietf.org/html/rfc5869
|
||||||
|
*/
|
||||||
|
object HkdfSha256 {
|
||||||
|
|
||||||
|
public fun deriveSecret(inputKeyMaterial: ByteArray, salt: ByteArray?, info: ByteArray, outputLength: Int): ByteArray {
|
||||||
|
return expand(extract(salt, inputKeyMaterial), info, outputLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HkdfSha256-Extract(salt, IKM) -> PRK
|
||||||
|
*
|
||||||
|
* @param salt optional salt value (a non-secret random value);
|
||||||
|
* if not provided, it is set to a string of HashLen (size in octets) zeros.
|
||||||
|
* @param ikm input keying material
|
||||||
|
*/
|
||||||
|
private fun extract(salt: ByteArray?, ikm: ByteArray): ByteArray {
|
||||||
|
val mac = initMac(salt ?: ByteArray(HASH_LEN) { 0.toByte() })
|
||||||
|
return mac.doFinal(ikm)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HkdfSha256-Expand(PRK, info, L) -> OKM
|
||||||
|
*
|
||||||
|
* @param prk a pseudorandom key of at least HashLen bytes (usually, the output from the extract step)
|
||||||
|
* @param info optional context and application specific information (can be empty)
|
||||||
|
* @param outputLength length of output keying material in bytes (<= 255*HashLen)
|
||||||
|
* @return OKM output keying material
|
||||||
|
*/
|
||||||
|
private fun expand(prk: ByteArray, info: ByteArray = ByteArray(0), outputLength: Int): ByteArray {
|
||||||
|
require(outputLength <= 255 * HASH_LEN) { "outputLength must be less than or equal to 255*HashLen" }
|
||||||
|
|
||||||
|
/*
|
||||||
|
The output OKM is calculated as follows:
|
||||||
|
Notation | -> When the message is composed of several elements we use concatenation (denoted |) in the second argument;
|
||||||
|
|
||||||
|
|
||||||
|
N = ceil(L/HashLen)
|
||||||
|
T = T(1) | T(2) | T(3) | ... | T(N)
|
||||||
|
OKM = first L octets of T
|
||||||
|
|
||||||
|
where:
|
||||||
|
T(0) = empty string (zero length)
|
||||||
|
T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
|
||||||
|
T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
|
||||||
|
T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
|
||||||
|
...
|
||||||
|
*/
|
||||||
|
val n = ceil(outputLength.toDouble() / HASH_LEN.toDouble()).toInt()
|
||||||
|
|
||||||
|
var stepHash = ByteArray(0) // T(0) empty string (zero length)
|
||||||
|
|
||||||
|
val generatedBytes = ByteArrayOutputStream() // ByteBuffer.allocate(Math.multiplyExact(n, HASH_LEN))
|
||||||
|
val mac = initMac(prk)
|
||||||
|
for (roundNum in 1..n) {
|
||||||
|
mac.reset()
|
||||||
|
val t = ByteBuffer.allocate(stepHash.size + info.size + 1).apply {
|
||||||
|
put(stepHash)
|
||||||
|
put(info)
|
||||||
|
put(roundNum.toByte())
|
||||||
|
}
|
||||||
|
stepHash = mac.doFinal(t.array())
|
||||||
|
generatedBytes.write(stepHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return generatedBytes.toByteArray().sliceArray(0 until outputLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initMac(secret: ByteArray): Mac {
|
||||||
|
val mac = Mac.getInstance(HASH_ALG)
|
||||||
|
mac.init(SecretKeySpec(secret, HASH_ALG))
|
||||||
|
return mac
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val HASH_LEN = 32
|
||||||
|
private const val HASH_ALG = "HmacSHA256"
|
||||||
|
}
|
@ -24,7 +24,7 @@ import com.squareup.inject.assisted.Assisted
|
|||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
|
import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec
|
||||||
import im.vector.matrix.android.api.session.securestorage.IntegrityResult
|
import im.vector.matrix.android.api.session.securestorage.IntegrityResult
|
||||||
import im.vector.matrix.android.api.session.securestorage.KeyInfoResult
|
import im.vector.matrix.android.api.session.securestorage.KeyInfoResult
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
@ -99,7 +99,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
|||||||
isIndeterminate = true
|
isIndeterminate = true
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
val keySpec = Curve25519AesSha2KeySpec.fromPassphrase(
|
val keySpec = RawBytesKeySpec.fromPassphrase(
|
||||||
passphrase,
|
passphrase,
|
||||||
keyInfo.content.passphrase?.salt ?: "",
|
keyInfo.content.passphrase?.salt ?: "",
|
||||||
keyInfo.content.passphrase?.iterations ?: 0,
|
keyInfo.content.passphrase?.iterations ?: 0,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user