diff --git a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/CipherFactory.kt b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/CipherFactory.kt deleted file mode 100644 index 0eec6e9fa9..0000000000 --- a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/CipherFactory.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2023 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. - */ - -package io.element.android.libraries.cryptography.api - -import javax.crypto.Cipher - -/** - * Factory to create [Cipher] instances for encryption and decryption. - * The implementation should use a secure way to store the keys. - */ -interface CipherFactory { - /** - * Create a [Cipher] instance for encryption. - * @param alias the alias of the key used for encryption. - * @return the [Cipher] instance. - */ - fun createEncryptionCipher(alias: String): Cipher - - /** - * Create a [Cipher] instance for decryption. - * @param alias the alias of the key used for encryption. - * @param initializationVector the initialization vector used for encryption. - * @return the [Cipher] instance. - */ - fun createDecryptionCipher(alias: String, initializationVector: ByteArray): Cipher -} diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationStateProvider.kt b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/CryptoService.kt similarity index 52% rename from features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationStateProvider.kt rename to libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/CryptoService.kt index 8e3f45ac07..a05e11da2a 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationStateProvider.kt +++ b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/CryptoService.kt @@ -14,17 +14,18 @@ * limitations under the License. */ -package io.element.android.features.pin.impl.auth +package io.element.android.libraries.cryptography.api -import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import javax.crypto.Cipher +import javax.crypto.SecretKey -open class PinAuthenticationStateProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - aPinAuthenticationState(), - ) +/** + * Simple service to provide cryptographic operations. + */ +interface CryptoService { + fun getOrCreateSecretKey(alias: String): SecretKey + fun createEncryptionCipher(key: SecretKey): Cipher + fun createDecryptionCipher(key: SecretKey, initializationVector: ByteArray): Cipher + fun encrypt(key: SecretKey, input: ByteArray): EncryptionResult + fun decrypt(key: SecretKey, encryptionResult: EncryptionResult): ByteArray } - -fun aPinAuthenticationState() = PinAuthenticationState( - eventSink = {} -) diff --git a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionResult.kt b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionResult.kt new file mode 100644 index 0000000000..10affcfdcb --- /dev/null +++ b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionResult.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 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. + */ + +package io.element.android.libraries.cryptography.api + +import android.util.Base64 +import java.nio.ByteBuffer + +/** + * Holds the result of an encryption operation. + */ +class EncryptionResult( + val encryptedByteArray: ByteArray, + val initializationVector: ByteArray +) { + fun toBase64(): String { + val initializationVectorSize = ByteBuffer.allocate(Int.SIZE_BYTES).putInt(initializationVector.size).array() + val cipherTextWithIv: ByteArray = + ByteBuffer.allocate(Int.SIZE_BYTES + initializationVector.size + encryptedByteArray.size) + .put(initializationVectorSize) + .put(initializationVector) + .put(encryptedByteArray) + .array() + return Base64.encodeToString(cipherTextWithIv, Base64.NO_WRAP) + } + + companion object { + /** + * @param base64 the base64 representation of the encrypted data. + * @return the [EncryptionResult] from the base64 representation. + */ + fun fromBase64(base64: String): EncryptionResult { + val cipherTextWithIv = Base64.decode(base64, Base64.NO_WRAP) + val buffer = ByteBuffer.wrap(cipherTextWithIv) + val initializationVectorSize = buffer.int + val initializationVector = ByteArray(initializationVectorSize) + buffer.get(initializationVector) + val encryptedByteArray = ByteArray(buffer.remaining()) + buffer.get(encryptedByteArray) + return EncryptionResult(encryptedByteArray, initializationVector) + } + } +} diff --git a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreCipherFactory.kt b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/DefaultCryptoService.kt similarity index 66% rename from libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreCipherFactory.kt rename to libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/DefaultCryptoService.kt index eb88b66239..dc4f771acf 100644 --- a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreCipherFactory.kt +++ b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/DefaultCryptoService.kt @@ -19,7 +19,8 @@ package io.element.android.libraries.cryptography.impl import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.libraries.cryptography.api.CipherFactory +import io.element.android.libraries.cryptography.api.CryptoService +import io.element.android.libraries.cryptography.api.EncryptionResult import io.element.android.libraries.di.AppScope import java.security.KeyStore import javax.crypto.Cipher @@ -34,28 +35,10 @@ private const val ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE private const val ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES private const val ENCRYPTION_AES_TRANSFORMATION = "$ENCRYPTION_ALGORITHM/$ENCRYPTION_BLOCK_MODE/$ENCRYPTION_PADDING" -/** - * Implementation of [CipherFactory] that uses the Android Keystore to store the keys. - */ @ContributesBinding(AppScope::class) -class KeyStoreCipherFactory @Inject constructor() : CipherFactory { - - override fun createEncryptionCipher(alias: String): Cipher { - val cipher = Cipher.getInstance(ENCRYPTION_AES_TRANSFORMATION) - val secretKey = getOrGenerateKeyForAlias(alias) - cipher.init(Cipher.ENCRYPT_MODE, secretKey) - return cipher - } - - override fun createDecryptionCipher(alias: String, initializationVector: ByteArray): Cipher { - val cipher = Cipher.getInstance(ENCRYPTION_AES_TRANSFORMATION) - val secretKey = getOrGenerateKeyForAlias(alias) - val spec = GCMParameterSpec(128, initializationVector) - cipher.init(Cipher.DECRYPT_MODE, secretKey, spec) - return cipher - } +class DefaultCryptoService @Inject constructor() : CryptoService { - private fun getOrGenerateKeyForAlias(alias: String): SecretKey { + override fun getOrCreateSecretKey(alias: String): SecretKey { val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE) val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry) ?.secretKey @@ -73,4 +56,28 @@ class KeyStoreCipherFactory @Inject constructor() : CipherFactory { generator.generateKey() } else secretKeyEntry } + + override fun createEncryptionCipher(key: SecretKey): Cipher { + return Cipher.getInstance(ENCRYPTION_AES_TRANSFORMATION).apply { + init(Cipher.ENCRYPT_MODE, key) + } + } + + override fun createDecryptionCipher(key: SecretKey, initializationVector: ByteArray): Cipher { + val spec = GCMParameterSpec(128, initializationVector) + return Cipher.getInstance(ENCRYPTION_AES_TRANSFORMATION).apply { + init(Cipher.DECRYPT_MODE, key, spec) + } + } + + override fun encrypt(key: SecretKey, input: ByteArray): EncryptionResult { + val cipher = createEncryptionCipher(key) + val encryptedData = cipher.doFinal(input) + return EncryptionResult(encryptedData, cipher.iv) + } + + override fun decrypt(key: SecretKey, encryptionResult: EncryptionResult): ByteArray { + val cipher = createDecryptionCipher(key, encryptionResult.initializationVector) + return cipher.doFinal(encryptionResult.encryptedByteArray) + } }