From 69414c1649c30f6e4a6e8cb5b8e37a4a85baf038 Mon Sep 17 00:00:00 2001 From: Andrii Iakovenko Date: Mon, 3 May 2021 16:56:39 +0300 Subject: [PATCH] Change private key backup password with key name --- build.gradle | 4 +- .../common/snippet/SnippetsJavaTest.java | 313 +++++++++++ .../android/common/snippet/SnippetsTest.kt | 216 +++++++- .../android/common/worker/GroupTests.kt | 10 +- .../android/common/EThreeCore.kt | 33 +- .../common/storage/cloud/CloudKeyManager.kt | 64 ++- .../android/common/worker/BackupWorker.kt | 16 +- .../interaction/async/EThreeBackupTest.kt | 153 +----- .../async/EThreeBackupWithKeyNameTest.kt | 506 ++++++++++++++++++ .../interaction/sync/EThreeGroupsTest.kt | 46 +- .../android/ethree/utils/TestUtils.kt | 2 +- 11 files changed, 1180 insertions(+), 183 deletions(-) create mode 100644 ethree-common/src/androidTest/java/com/virgilsecurity/android/common/snippet/SnippetsJavaTest.java create mode 100644 tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/async/EThreeBackupWithKeyNameTest.kt diff --git a/build.gradle b/build.gradle index a9050e39..d9bd8e4e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020, Virgil Security, Inc. + * Copyright (c) 2015-2021, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * @@ -141,7 +141,7 @@ def getGradleOrSystemProperty(String name, Project project) { final String BASE_VIRGIL_PACKAGE = 'com.virgilsecurity' // Packages versions -final String SDK_VERSION = '2.0.9' +final String SDK_VERSION = '2.0.10' subprojects { group BASE_VIRGIL_PACKAGE diff --git a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/snippet/SnippetsJavaTest.java b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/snippet/SnippetsJavaTest.java new file mode 100644 index 00000000..0570aecc --- /dev/null +++ b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/snippet/SnippetsJavaTest.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2015-2021, Virgil Security, Inc. + * + * Lead Maintainer: Virgil Security Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * (1) Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * (2) Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * (3) Neither the name of virgil nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.virgilsecurity.android.common.snippet; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.virgilsecurity.android.common.callback.OnGetTokenCallback; +import com.virgilsecurity.android.common.model.DerivedPasswords; +import com.virgilsecurity.android.common.model.java.EThreeParams; +import com.virgilsecurity.android.common.utils.TestConfig; +import com.virgilsecurity.android.common.utils.TestUtils; +import com.virgilsecurity.android.ethree.interaction.EThree; +import com.virgilsecurity.common.callback.OnCompleteListener; +import com.virgilsecurity.sdk.crypto.VirgilCrypto; + +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.UUID; + +import static org.junit.Assert.assertNotNull; + +/** + * This test covers snippets that used in documentation. + */ +@RunWith(AndroidJUnit4.class) +public class SnippetsJavaTest { + private String aliceIdentity; + private String bobIdentity; + private EThree aliceEthree; + private EThree bobEthree; + + @Before + public void setup() { + this.aliceIdentity = UUID.randomUUID().toString(); + OnGetTokenCallback aliceCallback = new OnGetTokenCallback() { + + @NotNull + @Override + public String onGetToken() { + return TestUtils.Companion.generateTokenString(aliceIdentity); + } + }; + this.aliceEthree = new EThree(aliceIdentity, aliceCallback, TestConfig.Companion.getContext()); + assertNotNull(this.aliceEthree); + this.aliceEthree.register().execute(); + + this.bobIdentity = UUID.randomUUID().toString(); + OnGetTokenCallback bobCallback = new OnGetTokenCallback() { + + @NotNull + @Override + public String onGetToken() { + return TestUtils.Companion.generateTokenString(bobIdentity); + } + }; + this.bobEthree = new EThree(bobIdentity, bobCallback, TestConfig.Companion.getContext()); + assertNotNull(this.bobEthree); + this.bobEthree.register().execute(); + } + + @Test + public void backup_restore_key() { + String identity = UUID.randomUUID().toString(); + String keyPassword = UUID.randomUUID().toString(); + String userPassword = UUID.randomUUID().toString(); + OnGetTokenCallback getTokenCallback = new OnGetTokenCallback() { + + @NotNull + @Override + public String onGetToken() { + return TestUtils.Companion.generateTokenString(bobIdentity); + } + }; + EThreeParams params = new EThreeParams(identity, getTokenCallback, TestConfig.Companion.getContext()); + EThree eThree = new EThree(params); + + + // Kotlin (Back up key) >> + + OnCompleteListener backupListener = new OnCompleteListener() { + @Override public void onSuccess() { + // private key backup success + } + + @Override public void onError(@NotNull Throwable throwable) { + // Error handling + } + }; + + // Backup user's private key to the cloud (encrypted using her password). + // This will enable your user to log in from another device and have access + // to the same private key there. + eThree.backupPrivateKey(keyPassword).addCallback(backupListener); + + // << Kotlin (Back up key) + + + // Kotlin (Make user's password the backup password) >> + + DerivedPasswords derivedPasswords = EThree.derivePasswords(userPassword); + + // This password should be used for backup/restore PrivateKey + String backupPassword = derivedPasswords.getBackupPassword(); + + // This password should be used for other purposes, e.g user authorization + String loginPassword = derivedPasswords.getLoginPassword(); + + // << Kotlin (Make user's password the backup password) + + + assertNotNull(backupPassword); + assertNotNull(loginPassword); + + + // Kotlin (Restore key) >> + + OnCompleteListener restoreListener = new OnCompleteListener() { + @Override public void onSuccess() { + // You're done + } + + @Override public void onError(@NotNull Throwable throwable) { + // Error handling + } + }; + + // If user wants to restore her private key from backup in Virgil Cloud. + // While user in session - key can be removed and restore multiply times (via cleanup/restorePrivateKey functions). + // To know whether private key is present on device now use hasLocalPrivateKey() function: + if (!eThree.hasLocalPrivateKey()) { + eThree.restorePrivateKey(keyPassword).addCallback(restoreListener); + } + + // << Kotlin (Restore key) + + + String oldPassword = keyPassword; + String newPassword = UUID.randomUUID().toString(); + + + // Kotlin (Change backup password) >> + + OnCompleteListener changeListener = new OnCompleteListener() { + @Override public void onSuccess() { + // You're done + } + + @Override public void onError(@NotNull Throwable throwable) { + // Error handling + } + }; + + // If the user wants to change his password for private key backup + eThree.changePassword(oldPassword, newPassword).addCallback(changeListener); + + // << Kotlin (Change backup password) + + + // Kotlin (Delete backup) >> + + OnCompleteListener resetListener = new OnCompleteListener() { + @Override public void onSuccess() { + // You're done + } + + @Override public void onError(@NotNull Throwable throwable) { + // Error handling + } + }; + + // If user wants to delete their account, use the following function + // to delete their private key + eThree.resetPrivateKeyBackup().addCallback(resetListener); + + // << Kotlin (Delete backup) + } + + @Test + public void backup_restore_key_with_keyName() { + String identity = UUID.randomUUID().toString(); + String keyName = UUID.randomUUID().toString(); + String keyPassword = UUID.randomUUID().toString(); + String userPassword = UUID.randomUUID().toString(); + OnGetTokenCallback getTokenCallback = new OnGetTokenCallback() { + + @NotNull + @Override + public String onGetToken() { + return TestUtils.Companion.generateTokenString(bobIdentity); + } + }; + EThreeParams params = new EThreeParams(identity, getTokenCallback, TestConfig.Companion.getContext()); + EThree eThree = new EThree(params); + + + // Kotlin (Back up key) >> + + OnCompleteListener backupListener = new OnCompleteListener() { + @Override public void onSuccess() { + // private key backup success + } + + @Override public void onError(@NotNull Throwable throwable) { + // Error handling + } + }; + + // Backup user's private key to the cloud (encrypted using her password). + // This will enable your user to log in from another device and have access + // to the same private key there. + eThree.backupPrivateKey(keyName, keyPassword).addCallback(backupListener); + + // << Kotlin (Back up key) + + + // Kotlin (Restore key) >> + + OnCompleteListener restoreListener = new OnCompleteListener() { + @Override public void onSuccess() { + // You're done + } + + @Override public void onError(@NotNull Throwable throwable) { + // Error handling + } + }; + + // If user wants to restore her private key from backup in Virgil Cloud. + // While user in session - key can be removed and restore multiply times (via cleanup/restorePrivateKey functions). + // To know whether private key is present on device now use hasLocalPrivateKey() function: + if (!eThree.hasLocalPrivateKey()) { + eThree.restorePrivateKey(keyName, keyPassword).addCallback(restoreListener); + } + + // << Kotlin (Restore key) + + + String oldPassword = keyPassword; + String newPassword = UUID.randomUUID().toString(); + + + // Kotlin (Change backup password) >> + + OnCompleteListener changeListener = new OnCompleteListener() { + @Override public void onSuccess() { + // You're done + } + + @Override public void onError(@NotNull Throwable throwable) { + // Error handling + } + }; + + // If the user wants to change his password for private key backup + eThree.changePassword(oldPassword, newPassword).addCallback(changeListener); + + // << Kotlin (Change backup password) + + + // Kotlin (Delete backup) >> + + OnCompleteListener resetListener = new OnCompleteListener() { + @Override public void onSuccess() { + // You're done + } + + @Override public void onError(@NotNull Throwable throwable) { + // Error handling + } + }; + + // If user wants to delete their account, use the following function + // to delete their private key + eThree.resetPrivateKeyBackup().addCallback(resetListener); + + // << Kotlin (Delete backup) + } + +} diff --git a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/snippet/SnippetsTest.kt b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/snippet/SnippetsTest.kt index 281d3b69..386419fd 100644 --- a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/snippet/SnippetsTest.kt +++ b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/snippet/SnippetsTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020, Virgil Security, Inc. + * Copyright (c) 2015-2021, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * @@ -35,9 +35,11 @@ package com.virgilsecurity.android.common.snippet import androidx.test.ext.junit.runners.AndroidJUnit4 import com.virgilsecurity.android.common.callback.OnGetTokenCallback +import com.virgilsecurity.android.common.model.EThreeParams import com.virgilsecurity.android.common.utils.TestConfig import com.virgilsecurity.android.common.utils.TestUtils import com.virgilsecurity.android.ethree.interaction.EThree +import com.virgilsecurity.common.callback.OnCompleteListener import com.virgilsecurity.common.model.Data import com.virgilsecurity.sdk.crypto.VirgilCrypto import org.junit.Assert.assertArrayEquals @@ -57,13 +59,11 @@ class SnippetsTest { private lateinit var aliceIdentity: String private lateinit var bobIdentity: String - private lateinit var crypto: VirgilCrypto private lateinit var aliceEthree: EThree private lateinit var bobEthree: EThree @Before fun setup() { - this.crypto = VirgilCrypto() this.aliceIdentity = UUID.randomUUID().toString() this.aliceEthree = EThree(aliceIdentity, object : OnGetTokenCallback { @@ -99,6 +99,216 @@ class SnippetsTest { assertArrayEquals("Hello".toByteArray(), decryptedData) } + @Test + fun backup_restore_key() { + val identity = UUID.randomUUID().toString() + val keyPassword = UUID.randomUUID().toString() + val userPassword = UUID.randomUUID().toString() + val params = EThreeParams(identity, { TestUtils.generateTokenString(identity) }, TestConfig.context) + val eThree = EThree(params) + + + // Kotlin (Back up key) >> + + val backupListener = + object : OnCompleteListener { + override fun onSuccess() { + // private key backup success + } + + override fun onError(throwable: Throwable) { + // Error handling + } + } + + // Backup user's private key to the cloud (encrypted using her password). + // This will enable your user to log in from another device and have access + // to the same private key there. + eThree.backupPrivateKey(keyPassword).addCallback(backupListener) + + // << Kotlin (Back up key) + + + // Kotlin (Make user's password the backup password) >> + + val derivedPasswords = EThree.derivePasswords(userPassword) + + // This password should be used for backup/restore PrivateKey + val backupPassword = derivedPasswords.backupPassword + + // This password should be used for other purposes, e.g user authorization + val loginPassword = derivedPasswords.loginPassword + + // << Kotlin (Make user's password the backup password) + + + assertNotNull(backupPassword) + assertNotNull(loginPassword) + + + // Kotlin (Restore key) >> + + val restoreListener = + object : OnCompleteListener { + override fun onSuccess() { + // You're done + } + + override fun onError(throwable: Throwable) { + // Error handling + } + } + + // If user wants to restore her private key from backup in Virgil Cloud. + // While user in session - key can be removed and restore multiply times (via cleanup/restorePrivateKey functions). + // To know whether private key is present on device now use hasLocalPrivateKey() function: + if (!eThree.hasLocalPrivateKey()) { + eThree.restorePrivateKey(keyPassword).addCallback(restoreListener) + } + + // << Kotlin (Restore key) + + + val oldPassword = keyPassword + val newPassword = UUID.randomUUID().toString() + + + // Kotlin (Change backup password) >> + + val changeListener = + object : OnCompleteListener { + override fun onSuccess() { + // You're done + } + + override fun onError(throwable: Throwable) { + // Error handling + } + } + + // If the user wants to change his password for private key backup + eThree.changePassword(oldPassword, newPassword).addCallback(changeListener) + + // << Kotlin (Change backup password) + + + // Kotlin (Delete backup) >> + + val resetListener = + object : OnCompleteListener { + override fun onSuccess() { + // You're done + } + + override fun onError(throwable: Throwable) { + // Error handling + } + } + + // If user wants to delete their account, use the following function + // to delete their private key + eThree.resetPrivateKeyBackup().addCallback(resetListener) + + // << Kotlin (Delete backup) + } + + @Test + fun backup_restore_key_with_keyName() { + val identity = UUID.randomUUID().toString() + val keyName = UUID.randomUUID().toString() + val keyPassword = UUID.randomUUID().toString() + val userPassword = UUID.randomUUID().toString() + val params = EThreeParams(identity, { TestUtils.generateTokenString(identity) }, TestConfig.context) + val eThree = EThree(params) + + + // Kotlin (Back up key) >> + + val backupListener = + object : OnCompleteListener { + override fun onSuccess() { + // private key backup success + } + + override fun onError(throwable: Throwable) { + // Error handling + } + } + + // Backup user's private key to the cloud (encrypted using her password). + // This will enable your user to log in from another device and have access + // to the same private key there. + eThree.backupPrivateKey(keyName, keyPassword).addCallback(backupListener) + + // << Kotlin (Back up key) + + + // Kotlin (Restore key) >> + + val restoreListener = + object : OnCompleteListener { + override fun onSuccess() { + // You're done + } + + override fun onError(throwable: Throwable) { + // Error handling + } + } + + // If user wants to restore her private key from backup in Virgil Cloud. + // While user in session - key can be removed and restore multiply times (via cleanup/restorePrivateKey functions). + // To know whether private key is present on device now use hasLocalPrivateKey() function: + if (!eThree.hasLocalPrivateKey()) { + eThree.restorePrivateKey(keyName, keyPassword).addCallback(restoreListener) + } + + // << Kotlin (Restore key) + + + val oldPassword = keyPassword + val newPassword = UUID.randomUUID().toString() + + + // Kotlin (Change backup password) >> + + val changeListener = + object : OnCompleteListener { + override fun onSuccess() { + // You're done + } + + override fun onError(throwable: Throwable) { + // Error handling + } + } + + // If the user wants to change his password for private key backup + eThree.changePassword(oldPassword, newPassword).addCallback(changeListener) + + // << Kotlin (Change backup password) + + + // Kotlin (Delete backup) >> + + val resetListener = + object : OnCompleteListener { + override fun onSuccess() { + // You're done + } + + override fun onError(throwable: Throwable) { + // Error handling + } + } + + // If user wants to delete their account, use the following function + // to delete their private key + eThree.resetPrivateKeyBackup().addCallback(resetListener) + + // << Kotlin (Delete backup) + } + private fun encryptShared(): Triple { // encryptShared >> diff --git a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/GroupTests.kt b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/GroupTests.kt index a81857a0..56f8d9bf 100644 --- a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/GroupTests.kt +++ b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/GroupTests.kt @@ -58,6 +58,7 @@ import com.virgilsecurity.sdk.cards.validation.VirgilCardVerifier import com.virgilsecurity.sdk.client.HttpClient import com.virgilsecurity.sdk.client.VirgilCardClient import com.virgilsecurity.sdk.common.TimeSpan +import com.virgilsecurity.sdk.crypto.KeyPairType import com.virgilsecurity.sdk.crypto.VirgilAccessTokenSigner import com.virgilsecurity.sdk.crypto.VirgilCardCrypto import com.virgilsecurity.sdk.crypto.VirgilCrypto @@ -788,13 +789,10 @@ class GroupTests { private fun createEThree(identity: String? = null): EThree { val ethreeIdentity = identity ?: UUID.randomUUID().toString() - val tokenCallback = object : OnGetTokenCallback { - override fun onGetToken(): String { - return TestUtils.generateTokenString(ethreeIdentity) - } - } - val ethree = EThree(ethreeIdentity, tokenCallback, TestConfig.context) + val etheeParams = EThreeParams(ethreeIdentity, {TestUtils.generateTokenString(ethreeIdentity) } , TestConfig.context) + etheeParams.keyPairType = KeyPairType.ED25519 + val ethree = EThree(etheeParams) if (identity == null) { ethree.register().execute() } diff --git a/ethree-common/src/main/java/com/virgilsecurity/android/common/EThreeCore.kt b/ethree-common/src/main/java/com/virgilsecurity/android/common/EThreeCore.kt index 22b22e1d..43532586 100644 --- a/ethree-common/src/main/java/com/virgilsecurity/android/common/EThreeCore.kt +++ b/ethree-common/src/main/java/com/virgilsecurity/android/common/EThreeCore.kt @@ -480,7 +480,26 @@ abstract class EThreeCore { */ fun changePassword(oldPassword: String, newPassword: String): Completable = - backupWorker.changePassword(oldPassword, newPassword) + backupWorker.changePassword(null, oldPassword, newPassword) + + /** + * Changes the password on a backed-up private key. + * + * Pulls user's private key from the Virgil's cloud storage, decrypts it with *Private key* + * that is generated based on provided [oldPassword] after that encrypts user's *Private key* + * using *Public key* that is generated based on provided [newPassword] and pushes encrypted + * user's *Private key* to the Virgil's cloud storage. + * + * To start execution of the current function, please see [Completable] description. + * + * @throws EThreeException(EThreeException.Description.WRONG_PASSWORD) If [oldPassword] is + * wrong. + * @throws EThreeException(EThreeException.Description.SAME_PASSWORD) If [newPassword] is the + * same as [oldPassword]. + */ + fun changePassword(keyName: String, oldPassword: String, + newPassword: String): Completable = + backupWorker.changePassword(keyName, oldPassword, newPassword) /** * Deletes Private Key stored on Virgil's cloud. This will disable user to log in from @@ -494,6 +513,18 @@ abstract class EThreeCore { fun resetPrivateKeyBackup(): Completable = backupWorker.resetPrivateKeyBackup() + /** + * Deletes Private Key stored on Virgil's cloud. This will disable user to log in from + * other devices. + * + * To start execution of the current function, please see [Completable] description. + * + * @throws EThreeException(EThreeException.Description.WRONG_PASSWORD) If [password] is wrong. + * @throws EThreeException.Description.MISSING_PRIVATE_KEY + */ + fun resetPrivateKeyBackupWithKeyName(keyName: String): Completable = + backupWorker.resetPrivateKeyBackupWithKeyName(keyName) + /** * Deletes Private Key stored on Virgil's cloud. This will disable user to log in from * other devices. diff --git a/ethree-common/src/main/java/com/virgilsecurity/android/common/storage/cloud/CloudKeyManager.kt b/ethree-common/src/main/java/com/virgilsecurity/android/common/storage/cloud/CloudKeyManager.kt index fbe1cdda..84bc184f 100644 --- a/ethree-common/src/main/java/com/virgilsecurity/android/common/storage/cloud/CloudKeyManager.kt +++ b/ethree-common/src/main/java/com/virgilsecurity/android/common/storage/cloud/CloudKeyManager.kt @@ -38,15 +38,10 @@ import com.virgilsecurity.android.common.exception.EThreeException import com.virgilsecurity.android.common.util.Const import com.virgilsecurity.android.common.util.Const.VIRGIL_BASE_URL import com.virgilsecurity.keyknox.KeyknoxManager -import com.virgilsecurity.keyknox.client.HttpClient -import com.virgilsecurity.keyknox.client.KeyknoxClient -import com.virgilsecurity.keyknox.client.KeyknoxPullParams -import com.virgilsecurity.keyknox.client.KeyknoxPushParams +import com.virgilsecurity.keyknox.client.* import com.virgilsecurity.keyknox.cloud.CloudEntrySerializer import com.virgilsecurity.keyknox.cloud.CloudKeyStorage -import com.virgilsecurity.keyknox.exception.DecryptionFailedException -import com.virgilsecurity.keyknox.exception.EntrySavingException -import com.virgilsecurity.keyknox.exception.KeyknoxCryptoException +import com.virgilsecurity.keyknox.exception.* import com.virgilsecurity.keyknox.model.CloudEntries import com.virgilsecurity.keyknox.model.CloudEntry import com.virgilsecurity.keyknox.utils.Serializer @@ -82,7 +77,7 @@ internal class CloudKeyManager internal constructor( // TODO change VirgilPythiaClient to have tokenProvider inside val pythiaClient = VirgilPythiaClient(baseUrl, Const.ETHREE_NAME, Const.ETHREE_NAME, - VirgilInfo.VERSION) + VirgilInfo.VERSION) val brainKeyContext = BrainKeyContext.Builder() .setAccessTokenProvider(tokenProvider) .setPythiaClient(pythiaClient) @@ -105,6 +100,10 @@ internal class CloudKeyManager internal constructor( val pullParams = KeyknoxPullParams(this.identity, "e3kit", "backup", keyName) val keyknoxValue = this.keyknoxManager.pullValue(pullParams, listOf(brainKeyPair.publicKey), brainKeyPair.privateKey) + if (keyknoxValue.value.isNotEmpty() || keyknoxValue.meta.isNotEmpty()) { + throw EntryAlreadyExistsException() + } + val params = KeyknoxPushParams(listOf(this.identity), "e3kit", "backup", keyName) val now = Date() val entry = CloudEntry(this.identity, exportedIdentityKey, now, now, mapOf()) @@ -120,7 +119,11 @@ internal class CloudKeyManager internal constructor( // Retrieve key from keyknox v2 val brainKeyPair = this.brainKey.generateKeyPair(password) val pullParams = KeyknoxPullParams(this.identity, "e3kit", "backup", keyName) - val keyknoxValue = this.keyknoxManager.pullValue(pullParams, listOf(brainKeyPair.publicKey), brainKeyPair.privateKey) + val keyknoxValue = try { + this.keyknoxManager.pullValue(pullParams, listOf(brainKeyPair.publicKey), brainKeyPair.privateKey) + } catch (e: DecryptionFailedException) { + throw EThreeException(EThreeException.Description.WRONG_PASSWORD) + } val entry = Serializer.gson.fromJson(ConvertionUtils.toString(keyknoxValue.value), CloudEntry::class.java) return entry } @@ -130,20 +133,45 @@ internal class CloudKeyManager internal constructor( setupCloudKeyStorage(password).delete(identity) } + internal fun deleteByKeyName(keyName: String) { + val params = KeyknoxResetParams("e3kit", "backup", keyName) + this.keyknoxManager.resetValue(params) + } + internal fun deleteAll() { this.keyknoxManager.resetValue() } - internal fun changePassword(oldPassword: String, newPassword: String) { - val cloudKeyStorage = setupCloudKeyStorage(oldPassword) + internal fun changePassword(keyName: String?, oldPassword: String, newPassword: String) { + if (keyName == null) { + // Change password for key from keyknox v1 + val cloudKeyStorage = setupCloudKeyStorage(oldPassword) - val brainKeyPair = this.brainKey.generateKeyPair(newPassword) + val brainKeyPair = this.brainKey.generateKeyPair(newPassword) - try { - cloudKeyStorage.updateRecipients(listOf(brainKeyPair.publicKey), - brainKeyPair.privateKey) - } catch (e: KeyknoxCryptoException) { - throw EThreeException(EThreeException.Description.WRONG_PASSWORD) + try { + cloudKeyStorage.updateRecipients(listOf(brainKeyPair.publicKey), + brainKeyPair.privateKey) + } catch (e: KeyknoxCryptoException) { + throw EThreeException(EThreeException.Description.WRONG_PASSWORD) + } + } else { + // Change password for key from keyknox v2 + val brainKeyPair = this.brainKey.generateKeyPair(oldPassword) + val pullParams = KeyknoxPullParams(this.identity, "e3kit", "backup", keyName) + val keyknoxValue = try { + this.keyknoxManager.pullValue(pullParams, listOf(brainKeyPair.publicKey), brainKeyPair.privateKey) + } catch (e: DecryptionFailedException) { + throw EThreeException(EThreeException.Description.WRONG_PASSWORD) + } + + if (keyknoxValue.value.isEmpty() || keyknoxValue.meta.isEmpty()) { + return + } + + val newBrainKeyPair = this.brainKey.generateKeyPair(newPassword) + val params = KeyknoxPushParams(listOf(this.identity), "e3kit", "backup", keyName) + this.keyknoxManager.pushValue(params, keyknoxValue.value, keyknoxValue.keyknoxHash, listOf(newBrainKeyPair.publicKey), newBrainKeyPair.privateKey) } } @@ -155,7 +183,7 @@ internal class CloudKeyManager internal constructor( val brainKeyPair = this.brainKey.generateKeyPair(password) val cloudKeyStorage = CloudKeyStorage(this.keyknoxManager, listOf(brainKeyPair.publicKey), - brainKeyPair.privateKey) + brainKeyPair.privateKey) try { cloudKeyStorage.retrieveCloudEntries() diff --git a/ethree-common/src/main/java/com/virgilsecurity/android/common/worker/BackupWorker.kt b/ethree-common/src/main/java/com/virgilsecurity/android/common/worker/BackupWorker.kt index f943219c..d0f03893 100644 --- a/ethree-common/src/main/java/com/virgilsecurity/android/common/worker/BackupWorker.kt +++ b/ethree-common/src/main/java/com/virgilsecurity/android/common/worker/BackupWorker.kt @@ -103,7 +103,7 @@ internal class BackupWorker internal constructor( } } - internal fun changePassword(oldPassword: String, + internal fun changePassword(keyName: String?, oldPassword: String, newPassword: String): Completable = object : Completable { override fun execute() { logger.fine("Change password") @@ -112,7 +112,7 @@ internal class BackupWorker internal constructor( if (oldPassword == newPassword) throw EThreeException(EThreeException.Description.SAME_PASSWORD) - keyManagerCloud.changePassword(oldPassword, newPassword) + keyManagerCloud.changePassword(keyName, oldPassword, newPassword) } } @@ -128,6 +128,18 @@ internal class BackupWorker internal constructor( } + internal fun resetPrivateKeyBackupWithKeyName(keyName: String): Completable = object : Completable { + override fun execute() { + logger.fine("Reset private key '${keyName}' backup") + try { + keyManagerCloud.deleteByKeyName(keyName) + } catch (exception: EntryNotFoundException) { + throw EThreeException(EThreeException.Description.MISSING_PRIVATE_KEY, exception) + } + } + + } + @Deprecated("Check 'replace with' section.", ReplaceWith("Please, use resetPrivateKeyBackup without password instead.")) internal fun resetPrivateKeyBackup(password: String): Completable = object : Completable { diff --git a/tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/async/EThreeBackupTest.kt b/tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/async/EThreeBackupTest.kt index fa09e8b0..898850c4 100644 --- a/tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/async/EThreeBackupTest.kt +++ b/tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/async/EThreeBackupTest.kt @@ -77,11 +77,15 @@ import java.util.concurrent.TimeUnit @RunWith(AndroidJUnit4::class) class EThreeBackupTest { + private lateinit var identity: String + private lateinit var password: String private lateinit var jwtGenerator: JwtGenerator private lateinit var keyStorage: KeyStorage @Before fun setup() { + identity = "identity-" + UUID.randomUUID().toString() + password = "pwd-" + UUID.randomUUID().toString() jwtGenerator = JwtGenerator( TestConfig.appId, TestConfig.appKey, @@ -179,9 +183,6 @@ class EThreeBackupTest { // STE-15_1 @Test fun backup_key_before_register() { - val identity = UUID.randomUUID().toString() - val password = UUID.randomUUID().toString() - val eThree = initEThree(identity) val waiter = CountDownLatch(1) @@ -206,8 +207,6 @@ class EThreeBackupTest { // STE-15_2-4 @Test fun backup_key_after_register() { - val identity = UUID.randomUUID().toString() - val password = UUID.randomUUID().toString() val eThree = initAndRegisterEThree(identity) val waiter = CountDownLatch(1) @@ -255,9 +254,6 @@ class EThreeBackupTest { // STE-16 @Test fun restore_private_key() { - val identity = UUID.randomUUID().toString() - val password = UUID.randomUUID().toString() - val eThreeWithPass = initAndRegisterEThree(identity) val waiter = CountDownLatch(1) @@ -315,141 +311,8 @@ class EThreeBackupTest { assertTrue(failedToRestore) } - @Test fun restore_private_key_dublicate() { - val identity = UUID.randomUUID().toString() - val password = UUID.randomUUID().toString() - val keyName = UUID.randomUUID().toString() - val keyPassword = UUID.randomUUID().toString() - - val eThreeWithPass = initAndRegisterEThree(identity) - - var waiter = CountDownLatch(1) - eThreeWithPass.backupPrivateKey(password).addCallback(object : OnCompleteListener { - override fun onSuccess() { - Log.d(TAG, "Private key backup success") - waiter.countDown() - } - - override fun onError(throwable: Throwable) { - Log.e(TAG, throwable.message) - fail(throwable.message) - } - }) - waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) - - waiter = CountDownLatch(1) - eThreeWithPass.backupPrivateKey(keyName, keyPassword).addCallback(object : OnCompleteListener { - override fun onSuccess() { - Log.d(TAG, "Private key '${keyName}' backup success") - waiter.countDown() - } - - override fun onError(throwable: Throwable) { - Log.e(TAG, "Private key '${keyName}' backup failed", throwable) - fail(throwable.message) - } - }) - waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) - - eThreeWithPass.cleanup() - waiter = CountDownLatch(1) - var restoreSuccessful = false - eThreeWithPass.restorePrivateKey(keyName, keyPassword).addCallback(object : OnCompleteListener { - override fun onSuccess() { - Log.d(TAG, "Private key '${keyName}' restored") - restoreSuccessful = true - waiter.countDown() - } - - override fun onError(throwable: Throwable) { - Log.e(TAG, throwable.message) - fail(throwable.message) - } - }) - waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) - assertTrue(restoreSuccessful) - - eThreeWithPass.cleanup() - waiter = CountDownLatch(1) - restoreSuccessful = false - eThreeWithPass.restorePrivateKey(password).addCallback(object : OnCompleteListener { - override fun onSuccess() { - Log.d(TAG, "Private key restored") - restoreSuccessful = true - waiter.countDown() - } - - override fun onError(throwable: Throwable) { - Log.e(TAG, throwable.message) - fail(throwable.message) - } - }) - waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) - assertTrue(restoreSuccessful) - } - - @Test fun restore_private_key_with_key_name() { - val identity = UUID.randomUUID().toString() - val password = UUID.randomUUID().toString() - val keyName = UUID.randomUUID().toString() - - val eThreeWithPass = initAndRegisterEThree(identity) - - val waiter = CountDownLatch(1) - eThreeWithPass.backupPrivateKey(keyName, password).addCallback(object : OnCompleteListener { - override fun onSuccess() { - Log.d(TAG, "Backup '${keyName}' private key success") - waiter.countDown() - } - - override fun onError(throwable: Throwable) { - fail(throwable.message) - } - }) - waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) - - eThreeWithPass.cleanup() - val waiterTwo = CountDownLatch(1) - var restoreSuccessful = false - eThreeWithPass.restorePrivateKey(keyName, password).addCallback(object : OnCompleteListener { - override fun onSuccess() { - restoreSuccessful = true - Log.d(TAG, "Private key '${keyName}' restored") - waiterTwo.countDown() - } - - override fun onError(throwable: Throwable) { - fail(throwable.message) - } - }) - waiterTwo.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) - assertTrue(restoreSuccessful) - - val waiterThree = CountDownLatch(1) - var failedToRestore = false - eThreeWithPass.restorePrivateKey(keyName, password).addCallback(object : OnCompleteListener { - override fun onSuccess() { - fail("Illegal state") - } - - override fun onError(throwable: Throwable) { - Log.d(TAG, "Exception type is: " + throwable.javaClass.canonicalName) - if (throwable is EThreeException - && throwable.description == EThreeException.Description.PRIVATE_KEY_EXISTS) { - failedToRestore = true - } - - waiterThree.countDown() - } - }) - waiterThree.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) - assertTrue(failedToRestore) - } - // STE-17 @Test fun change_password() { - val identity = UUID.randomUUID().toString() - val password = UUID.randomUUID().toString() val passwordNew = UUID.randomUUID().toString() val eThreeWithPass = initAndRegisterEThree(identity) @@ -522,8 +385,6 @@ class EThreeBackupTest { // STE-18_1 @Test fun reset_key_backup_before_backup() { - val identity = UUID.randomUUID().toString() - val password = UUID.randomUUID().toString() val eThreeWithPass = initEThree(identity) val waiter = CountDownLatch(1) @@ -551,8 +412,6 @@ class EThreeBackupTest { // STE-18_2 @Test fun reset_key_backup_after_backup() { - val identity = UUID.randomUUID().toString() - val password = UUID.randomUUID().toString() val eThreeWithPass = initAndRegisterEThree(identity) val waiter = CountDownLatch(1) @@ -588,8 +447,6 @@ class EThreeBackupTest { // Reset without password @Test fun reset_key_backup_after_backup_no_password() { - val identity = UUID.randomUUID().toString() - val password = UUID.randomUUID().toString() val eThreeWithPass = initAndRegisterEThree(identity) val waiter = CountDownLatch(1) @@ -624,8 +481,6 @@ class EThreeBackupTest { } @Test fun reset_backed_key_wrong_pass() { - val identity = UUID.randomUUID().toString() - val password = UUID.randomUUID().toString() val eThreeWithPass = initAndRegisterEThree(identity) val waiter = CountDownLatch(1) diff --git a/tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/async/EThreeBackupWithKeyNameTest.kt b/tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/async/EThreeBackupWithKeyNameTest.kt new file mode 100644 index 00000000..f5b57127 --- /dev/null +++ b/tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/async/EThreeBackupWithKeyNameTest.kt @@ -0,0 +1,506 @@ +/* + * Copyright (c) 2015-2020, Virgil Security, Inc. + * + * Lead Maintainer: Virgil Security Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * (1) Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * (2) Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * (3) Neither the name of virgil nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.virgilsecurity.android.ethree.interaction.async + +import android.util.Log +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.virgilsecurity.android.common.exception.EThreeException +import com.virgilsecurity.android.common.model.EThreeParams +import com.virgilsecurity.android.ethree.interaction.EThree +import com.virgilsecurity.android.ethree.utils.TestConfig +import com.virgilsecurity.android.ethree.utils.TestConfig.Companion.virgilServiceAddress +import com.virgilsecurity.android.ethree.utils.TestUtils +import com.virgilsecurity.common.callback.OnCompleteListener +import com.virgilsecurity.keyknox.KeyknoxManager +import com.virgilsecurity.keyknox.client.KeyknoxClient +import com.virgilsecurity.keyknox.cloud.CloudKeyStorage +import com.virgilsecurity.keyknox.crypto.KeyknoxCrypto +import com.virgilsecurity.keyknox.storage.SyncKeyStorage +import com.virgilsecurity.pythia.brainkey.BrainKey +import com.virgilsecurity.pythia.brainkey.BrainKeyContext +import com.virgilsecurity.pythia.client.VirgilPythiaClient +import com.virgilsecurity.pythia.crypto.VirgilPythiaCrypto +import com.virgilsecurity.sdk.common.TimeSpan +import com.virgilsecurity.sdk.crypto.VirgilAccessTokenSigner +import com.virgilsecurity.sdk.jwt.JwtGenerator +import com.virgilsecurity.sdk.jwt.accessProviders.CachingJwtProvider +import com.virgilsecurity.sdk.storage.DefaultKeyStorage +import com.virgilsecurity.sdk.storage.KeyStorage +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.net.URL +import java.util.* +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +@RunWith(AndroidJUnit4::class) +class EThreeBackupWithKeyNameTest { + + private lateinit var identity: String + private lateinit var keyName: String + private lateinit var password: String + private lateinit var jwtGenerator: JwtGenerator + private lateinit var keyStorage: KeyStorage + + @Before + fun setup() { + identity = "identity-" + UUID.randomUUID().toString() + keyName = "key-" + UUID.randomUUID().toString() + password = "pwd-" + UUID.randomUUID().toString() + + jwtGenerator = JwtGenerator( + TestConfig.appId, + TestConfig.appKey, + TestConfig.appPublicKeyId, + TimeSpan.fromTime(600, TimeUnit.SECONDS), + VirgilAccessTokenSigner(TestConfig.virgilCrypto) + ) + + keyStorage = DefaultKeyStorage(TestConfig.DIRECTORY_PATH, TestConfig.KEYSTORE_NAME) + } + + private fun initAndRegisterEThree(identity: String): EThree { + val eThree = initEThree(identity) + registerEThree(eThree) + return eThree + } + + private fun initEThree(identity: String): EThree { + val params = EThreeParams(identity, {jwtGenerator.generateToken(identity).stringRepresentation()}, TestConfig.context) + + return EThree(params) + } + + private fun registerEThree(eThree: EThree) { + eThree.register().execute() + } + + private fun initSyncKeyStorage(identity: String, passwordBrainKey: String): SyncKeyStorage { + val tokenProvider = CachingJwtProvider(CachingJwtProvider.RenewJwtCallback { + jwtGenerator.generateToken(identity) + }) + val brainKeyContext = BrainKeyContext.Builder() + .setAccessTokenProvider(tokenProvider) + .setPythiaClient(VirgilPythiaClient(virgilServiceAddress)) + .setPythiaCrypto(VirgilPythiaCrypto()) + .build() + val keyPair = BrainKey(brainKeyContext).generateKeyPair(passwordBrainKey) + + val syncKeyStorage = SyncKeyStorage( + identity, + keyStorage, + CloudKeyStorage( + KeyknoxManager( + KeyknoxClient(tokenProvider, URL(virgilServiceAddress)), + KeyknoxCrypto() + ), + listOf(keyPair.publicKey), + keyPair.privateKey + ) + ) + + syncKeyStorage.sync() + + return syncKeyStorage + } + + // STE-15_1 + @Test fun backup_key_before_register() { + val eThree = initEThree(identity) + + val waiter = CountDownLatch(1) + var failedToBackup = false + eThree.backupPrivateKey(keyName, password).addCallback(object : OnCompleteListener { + override fun onSuccess() { + Log.e(TAG, "No private key. Backup should fail") + waiter.countDown() + } + + override fun onError(throwable: Throwable) { + if (throwable is EThreeException + && throwable.description == EThreeException.Description.MISSING_PRIVATE_KEY) { + failedToBackup = true + } + + waiter.countDown() + } + }) + waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(failedToBackup) + } + + // STE-15_2-4 + @Test fun backup_key_after_register() { + val eThree = initAndRegisterEThree(identity) + + val waiter = CountDownLatch(1) + var successfullyBackuped = false + eThree.backupPrivateKey(keyName, password).addCallback(object : OnCompleteListener { + override fun onSuccess() { + successfullyBackuped = true + waiter.countDown() + } + + override fun onError(throwable: Throwable) { + Log.e(TAG, "Backup private key failed", throwable) + waiter.countDown() + } + }) + waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(successfullyBackuped) + + val waiterTwo = CountDownLatch(1) + var failedToBackup = false + eThree.backupPrivateKey(keyName, password).addCallback(object : OnCompleteListener { + override fun onSuccess() { + Log.e(TAG, "Private key backup exists. Backup with the same key should fail") + waiterTwo.countDown() + } + + override fun onError(throwable: Throwable) { + if (throwable is EThreeException + && throwable.description + == EThreeException.Description.PRIVATE_KEY_BACKUP_EXISTS) { + + failedToBackup = true + } else { + Log.e(TAG, "Backup failed with unpredictable exception", throwable) + } + + waiterTwo.countDown() + } + }) + waiterTwo.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(failedToBackup) + } + + // STE-16 + @Test fun restore_private_key() { + val eThreeWithPass = initAndRegisterEThree(identity) + + val waiter = CountDownLatch(1) + var backupSuccessful = false + eThreeWithPass.backupPrivateKey(keyName, password).addCallback(object : OnCompleteListener { + override fun onSuccess() { + backupSuccessful = true + waiter.countDown() + } + + override fun onError(throwable: Throwable) { + Log.e(TAG, "Backup failed", throwable) + waiter.countDown() + } + }) + waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(backupSuccessful) + + eThreeWithPass.cleanup() + val waiterTwo = CountDownLatch(1) + var restoreSuccessful = false + eThreeWithPass.restorePrivateKey(keyName, password).addCallback(object : OnCompleteListener { + override fun onSuccess() { + restoreSuccessful = true + waiterTwo.countDown() + } + + override fun onError(throwable: Throwable) { + Log.e(TAG, "Restoring failed", throwable) + waiterTwo.countDown() + } + }) + waiterTwo.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(restoreSuccessful) + + val waiterThree = CountDownLatch(1) + var failedToRestore = false + eThreeWithPass.restorePrivateKey(keyName, password).addCallback(object : OnCompleteListener { + override fun onSuccess() { + Log.e(TAG, "Private key is already exists. Restore should fail") + waiterThree.countDown() + } + + override fun onError(throwable: Throwable) { + Log.d(TAG, "Exception type is: " + throwable.javaClass.canonicalName) + if (throwable is EThreeException + && throwable.description == EThreeException.Description.PRIVATE_KEY_EXISTS) { + failedToRestore = true + } + + waiterThree.countDown() + } + }) + waiterThree.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(failedToRestore) + } + + @Test fun restore_private_key_dublicate() { + val keyPassword = UUID.randomUUID().toString() + + val eThreeWithPass = initAndRegisterEThree(identity) + + var waiter = CountDownLatch(1) + var backupSuccessful = false + eThreeWithPass.backupPrivateKey(password).addCallback(object : OnCompleteListener { + override fun onSuccess() { + Log.d(TAG, "Private key backup success") + backupSuccessful = true + waiter.countDown() + } + + override fun onError(throwable: Throwable) { + Log.e(TAG, "Backup failed", throwable) + waiter.countDown() + } + }) + waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(backupSuccessful) + + waiter = CountDownLatch(1) + backupSuccessful = false + eThreeWithPass.backupPrivateKey(keyName, keyPassword).addCallback(object : OnCompleteListener { + override fun onSuccess() { + Log.d(TAG, "Private key '${keyName}' backup success") + backupSuccessful = true + waiter.countDown() + } + + override fun onError(throwable: Throwable) { + Log.e(TAG, "Private key '${keyName}' backup failed", throwable) + waiter.countDown() + } + }) + waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(backupSuccessful) + + eThreeWithPass.cleanup() + waiter = CountDownLatch(1) + var restoreSuccessful = false + eThreeWithPass.restorePrivateKey(keyName, keyPassword).addCallback(object : OnCompleteListener { + override fun onSuccess() { + Log.d(TAG, "Private key '${keyName}' restored") + restoreSuccessful = true + waiter.countDown() + } + + override fun onError(throwable: Throwable) { + Log.e(TAG, "Restore privatekey failed", throwable) + waiter.countDown() + } + }) + waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(restoreSuccessful) + + eThreeWithPass.cleanup() + waiter = CountDownLatch(1) + restoreSuccessful = false + eThreeWithPass.restorePrivateKey(password).addCallback(object : OnCompleteListener { + override fun onSuccess() { + Log.d(TAG, "Private key restored") + restoreSuccessful = true + waiter.countDown() + } + + override fun onError(throwable: Throwable) { + Log.e(TAG, "Restore key failed", throwable) + waiter.countDown() + } + }) + waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(restoreSuccessful) + } + + // STE-17 + @Test fun change_password() { + Log.d(TAG, "Change password") + val passwordNew = UUID.randomUUID().toString() + + val eThreeWithPass = initAndRegisterEThree(identity) + + var waiter = CountDownLatch(1) + var success = false + eThreeWithPass.backupPrivateKey(keyName, password).addCallback(object : OnCompleteListener { + override fun onSuccess() { + success = true + waiter.countDown() + } + + override fun onError(throwable: Throwable) { + Log.e(TAG, "Backup failed", throwable) + waiter.countDown() + } + }) + waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(success) + + waiter = CountDownLatch(1) + var passwordNotChanged = true + eThreeWithPass.changePassword(keyName, UUID.randomUUID().toString(), passwordNew) + .addCallback(object : OnCompleteListener { + override fun onSuccess() { + Log.e(TAG, "Change password with wrong password should fail, but successful") + waiter.countDown() + } + + override fun onError(throwable: Throwable) { + if (throwable is EThreeException + && throwable.description == EThreeException.Description.WRONG_PASSWORD) { + passwordNotChanged = true + } else { + Log.e(TAG, "Change password with wrong password failed with unpredictable exception", throwable) + } + waiter.countDown() + } + }) + waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(passwordNotChanged) + + val waiterOne = CountDownLatch(1) + var passwordChanged = false + eThreeWithPass.changePassword(keyName, password, passwordNew) + .addCallback(object : OnCompleteListener { + override fun onSuccess() { + passwordChanged = true + waiterOne.countDown() + } + + override fun onError(throwable: Throwable) { + Log.e(TAG, "Change password failed", throwable) + waiterOne.countDown() + } + }) + waiterOne.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(passwordChanged) + + eThreeWithPass.cleanup() + val waiterTwo = CountDownLatch(1) + var failedWithOldPassword = false + eThreeWithPass.restorePrivateKey(keyName, password).addCallback(object : OnCompleteListener { + + override fun onSuccess() { + Log.e(TAG, "Restore key with old password should fail") + waiterTwo.countDown() + } + + override fun onError(throwable: Throwable) { + if (throwable is EThreeException + && throwable.description == EThreeException.Description.WRONG_PASSWORD) { + failedWithOldPassword = true + } else { + Log.e(TAG, "Restore key with old password failed with unpredictable exception", throwable) + } + + waiterTwo.countDown() + } + }) + waiterTwo.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(failedWithOldPassword) + + val waiterThree = CountDownLatch(1) + var successWithNewPassword = false + eThreeWithPass.restorePrivateKey(keyName, passwordNew).addCallback(object : OnCompleteListener { + + override fun onSuccess() { + successWithNewPassword = true + waiterThree.countDown() + } + + override fun onError(throwable: Throwable) { + Log.e(TAG, "Restore key with new password failed", throwable) + waiterThree.countDown() + } + }) + waiterThree.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(successWithNewPassword) + } + + // STE-18_2 + @Test fun reset_key_backup_after_backup() { + val eThreeWithPass = initAndRegisterEThree(identity) + + val waiter = CountDownLatch(1) + var success = false + eThreeWithPass.backupPrivateKey(keyName, password).addCallback(object : OnCompleteListener { + override fun onSuccess() { + success = true + waiter.countDown() + } + + override fun onError(throwable: Throwable) { + Log.e(TAG, "Backup failed", throwable) + waiter.countDown() + } + }) + waiter.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(success) + + val waiterTwo = CountDownLatch(1) + var successfulKeyReset = false + eThreeWithPass.resetPrivateKeyBackupWithKeyName(keyName).addCallback(object : OnCompleteListener { + override fun onSuccess() { + successfulKeyReset = true + waiterTwo.countDown() + } + + override fun onError(throwable: Throwable) { + Log.e(TAG, "Reset private key failed", throwable) + waiterTwo.countDown() + } + }) + waiterTwo.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(successfulKeyReset) + + eThreeWithPass.cleanup() + val waiterTree = CountDownLatch(1) + var restoreFailed = false + eThreeWithPass.restorePrivateKey(keyName, password).addCallback(object : OnCompleteListener { + override fun onSuccess() { + Log.e(TAG, "Restoring succeful, but should fail") + waiterTree.countDown() + } + + override fun onError(throwable: Throwable) { + restoreFailed = true + waiterTree.countDown() + } + }) + waiterTree.await(TestUtils.REQUEST_TIMEOUT, TimeUnit.SECONDS) + assertTrue(restoreFailed) + } + + companion object { + const val TAG = "EThreeBackupTest" + } +} diff --git a/tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/sync/EThreeGroupsTest.kt b/tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/sync/EThreeGroupsTest.kt index 0b43380d..811fcebd 100644 --- a/tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/sync/EThreeGroupsTest.kt +++ b/tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/sync/EThreeGroupsTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020, Virgil Security, Inc. + * Copyright (c) 2015-2021, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * @@ -57,6 +57,7 @@ import com.virgilsecurity.sdk.storage.KeyStorage import com.virgilsecurity.sdk.utils.Tuple import org.junit.Assert import org.junit.Before +import org.junit.Ignore import org.junit.Test import java.util.* import java.util.concurrent.TimeUnit @@ -161,6 +162,49 @@ class EThreeGroupsTest { } } + @Test + @Ignore + //FIXME enable when server side ready + fun createGroup_alreadyExists_byAnotherUser_shouldFail() { + // Register a set of identities + val groupId = UUID.randomUUID().toString() + val identity2 = UUID.randomUUID().toString() + + // Initialize eThree + val params = EThreeParams(identity, {jwtGenerator.generateToken(identity).stringRepresentation()}, TestConfig.context) + val eThree = EThree(params) + eThree.register().execute() + + val params2 = EThreeParams(identity2, {jwtGenerator.generateToken(identity2).stringRepresentation()}, TestConfig.context) + val eThree2 = EThree(params2) + eThree2.register().execute() + + val findUsersResult1 = eThree.findUsers(listOf(identity2)).get() + val createdGroup1 = eThree.createGroup(groupId, findUsersResult1).get() + Assert.assertNotNull(createdGroup1) + + // Remove ->> + val findUsersResult2 = eThree.findUsers(listOf(identity)).get() + val createdGroup2 = eThree2.createGroup(groupId, findUsersResult2).get() + + val loadedGroup1 = eThree2.loadGroup(groupId, findUsersResult1.values.first()).get() + Assert.assertEquals(createdGroup1.initiator, loadedGroup1.initiator) + + val loadedGroup12 = eThree2.getGroup(groupId) + Assert.assertNotNull(loadedGroup12) + Assert.assertEquals(createdGroup1.initiator, loadedGroup12?.initiator) + + // <<- Remove + + try { + val createdGroup2 = eThree2.createGroup(groupId, eThree.findUsers(listOf(identity)).get()).get() + Assert.fail("Group with the same Id shouldn't be created"); + } + catch (e: GroupException) { + Assert.assertEquals(GroupException.Description.GROUP_ALREADY_EXISTS, e.description); + } + } + private fun initCardManager(identity: String): CardManager { val cardCrypto = VirgilCardCrypto() return CardManager( diff --git a/tests/src/androidTest/java/com/virgilsecurity/android/ethree/utils/TestUtils.kt b/tests/src/androidTest/java/com/virgilsecurity/android/ethree/utils/TestUtils.kt index bb7d603f..abe87d82 100644 --- a/tests/src/androidTest/java/com/virgilsecurity/android/ethree/utils/TestUtils.kt +++ b/tests/src/androidTest/java/com/virgilsecurity/android/ethree/utils/TestUtils.kt @@ -52,7 +52,7 @@ import java.util.concurrent.TimeUnit class TestUtils { companion object { - const val REQUEST_TIMEOUT = 5 * 1000L // 5 seconds + const val REQUEST_TIMEOUT = 5L // 5 seconds fun generateTokenString(identity: String): String = JwtGenerator(