Pin code: add some tests

This commit is contained in:
ganfra 2023-10-18 11:26:02 +02:00
parent 8436806571
commit d6d553e8e0
14 changed files with 371 additions and 45 deletions

View file

@ -16,56 +16,31 @@
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.CryptoService
import io.element.android.libraries.cryptography.api.AESEncryptionSpecs
import io.element.android.libraries.cryptography.api.EncryptionDecryptionService
import io.element.android.libraries.cryptography.api.EncryptionResult
import io.element.android.libraries.di.AppScope
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
import javax.inject.Inject
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
private const val ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM
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"
/**
* Default implementation of [EncryptionDecryptionService] using AES encryption.
*/
@ContributesBinding(AppScope::class)
class DefaultCryptoService @Inject constructor() : CryptoService {
override fun getOrCreateSecretKey(alias: String): SecretKey {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry)
?.secretKey
return if (secretKeyEntry == null) {
val generator = KeyGenerator.getInstance(ENCRYPTION_ALGORITHM, ANDROID_KEYSTORE)
val keyGenSpec = KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(ENCRYPTION_BLOCK_MODE)
.setEncryptionPaddings(ENCRYPTION_PADDING)
.setKeySize(128)
.build()
generator.init(keyGenSpec)
generator.generateKey()
} else secretKeyEntry
}
class AESEncryptionDecryptionService @Inject constructor() : EncryptionDecryptionService {
override fun createEncryptionCipher(key: SecretKey): Cipher {
return Cipher.getInstance(ENCRYPTION_AES_TRANSFORMATION).apply {
return Cipher.getInstance(AESEncryptionSpecs.CIPHER_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 {
return Cipher.getInstance(AESEncryptionSpecs.CIPHER_TRANSFORMATION).apply {
init(Cipher.DECRYPT_MODE, key, spec)
}
}

View file

@ -0,0 +1,55 @@
/*
* 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.impl
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import io.element.android.libraries.cryptography.api.AESEncryptionSpecs
import io.element.android.libraries.cryptography.api.SecretKeyProvider
import java.security.KeyStore
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
/**
* Default implementation of [SecretKeyProvider] that uses the Android Keystore to store the keys.
* The generated key uses AES algorithm, with a key size of 128 bits, and the GCM block mode.
*/
class KeyStoreSecretKeyProvider : SecretKeyProvider {
override fun getOrCreateKey(alias: String): SecretKey {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry)
?.secretKey
return if (secretKeyEntry == null) {
val generator = KeyGenerator.getInstance(AESEncryptionSpecs.ALGORITHM, ANDROID_KEYSTORE)
val keyGenSpec = KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(AESEncryptionSpecs.BLOCK_MODE)
.setEncryptionPaddings(AESEncryptionSpecs.PADDINGS)
.setKeySize(AESEncryptionSpecs.KEY_SIZE)
.build()
generator.init(keyGenSpec)
generator.generateKey()
} else {
secretKeyEntry
}
}
}

View file

@ -0,0 +1,54 @@
/*
* 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.impl
import android.security.keystore.KeyProperties
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Test
import java.security.GeneralSecurityException
import javax.crypto.KeyGenerator
class AESEncryptionDecryptionServiceTest {
private val encryptionDecryptionService = AESEncryptionDecryptionService()
@Test
fun given_a_valid_key_then_encrypt_decrypt_work() {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES)
keyGenerator.init(128)
val key = keyGenerator.generateKey()
val input = "Hello World".toByteArray()
val encryptionResult = encryptionDecryptionService.encrypt(key, input)
val decrypted = encryptionDecryptionService.decrypt(key, encryptionResult)
assertThat(decrypted).isEqualTo(input)
}
@Test
fun given_a_wrong_key_then_decrypt_fail() {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES)
keyGenerator.init(128)
val encryptionKey = keyGenerator.generateKey()
val input = "Hello World".toByteArray()
val encryptionResult = encryptionDecryptionService.encrypt(encryptionKey, input)
val decryptionKey = keyGenerator.generateKey()
assertThrows(GeneralSecurityException::class.java) {
encryptionDecryptionService.decrypt(decryptionKey, encryptionResult)
}
}
}