Skip to content

Commit c13c576

Browse files
Merge pull request #58 from VirgilSecurity/dev
Backup/Restore key with key name
2 parents 0a5aaa8 + 9367919 commit c13c576

File tree

27 files changed

+470
-161
lines changed

27 files changed

+470
-161
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def getGradleOrSystemProperty(String name, Project project) {
141141
final String BASE_VIRGIL_PACKAGE = 'com.virgilsecurity'
142142

143143
// Packages versions
144-
final String SDK_VERSION = '2.0.8'
144+
final String SDK_VERSION = '2.0.9'
145145

146146
subprojects {
147147
group BASE_VIRGIL_PACKAGE

ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/GroupTests.kt

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -751,16 +751,53 @@ class GroupTests {
751751
}
752752
}
753753

754-
private fun createEThree(): EThree {
755-
val identity = UUID.randomUUID().toString()
754+
@Test fun restored_ethree_encryption() {
755+
val ethree2 = createEThree()
756+
757+
val identities = listOf(ethree2.identity)
758+
759+
val card1 = ethree2.findUser(this.ethree.identity).get()
760+
761+
// User1 creates group, encrypts
762+
val lookup = this.ethree.findUsers(identities).get()
763+
val group1 = this.ethree.createGroup(this.groupId, lookup).get()
764+
765+
val message1 = UUID.randomUUID().toString()
766+
val encrypted1 = group1.encrypt(message1)
767+
val selfDecrypted1 = group1.decrypt(encrypted1, card1)
768+
assertEquals(message1, selfDecrypted1)
769+
770+
// User2 updates group, decrypts
771+
val group2 = ethree2.loadGroup(this.groupId, card1).get()
772+
val decrypted1 = group2.decrypt(encrypted1, card1)
773+
assertEquals(message1, decrypted1)
774+
775+
val message2 = UUID.randomUUID().toString()
776+
val encrypted2 = group2.encrypt(message2)
777+
778+
// User1 restarts ethree, decrypts
779+
val ethreeRestored = createEThree(this.ethree.identity)
780+
val restoredGroup1 = ethreeRestored.getGroup(this.groupId)
781+
val restoredDecrypted1 = restoredGroup1?.decrypt(encrypted1, card1)
782+
assertEquals(message1, restoredDecrypted1)
783+
784+
val card2 = ethreeRestored.findUser(ethree2.identity).get()
785+
val decrypted2 = restoredGroup1?.decrypt(encrypted2, card2)
786+
assertEquals(message2, decrypted2)
787+
}
788+
789+
private fun createEThree(identity: String? = null): EThree {
790+
val ethreeIdentity = identity ?: UUID.randomUUID().toString()
756791
val tokenCallback = object : OnGetTokenCallback {
757792
override fun onGetToken(): String {
758-
return TestUtils.generateTokenString(identity)
793+
return TestUtils.generateTokenString(ethreeIdentity)
759794
}
760795
}
761796

762-
val ethree = EThree(identity, tokenCallback, TestConfig.context)
763-
ethree.register().execute()
797+
val ethree = EThree(ethreeIdentity, tokenCallback, TestConfig.context)
798+
if (identity == null) {
799+
ethree.register().execute()
800+
}
764801
return ethree
765802
}
766803
}

ethree-common/src/main/java/com/virgilsecurity/android/common/EThreeCore.kt

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015-2020, Virgil Security, Inc.
2+
* Copyright (c) 2015-2021, Virgil Security, Inc.
33
*
44
* Lead Maintainer: Virgil Security Inc. <support@virgilsecurity.com>
55
*
@@ -134,6 +134,7 @@ abstract class EThreeCore {
134134
enableRatchet: Boolean,
135135
keyRotationInterval: TimeSpan,
136136
context: Context) {
137+
logger.fine("Create new EThree instance for $identity")
137138

138139
this.identity = identity
139140

@@ -169,6 +170,8 @@ abstract class EThreeCore {
169170
* is available only after child object of `EThreeCore` is constructed.
170171
*/
171172
protected fun initializeCore() {
173+
logger.finer("Initialize EThree core")
174+
172175
this.localKeyStorage = LocalKeyStorage(identity, keyStorage, crypto)
173176
this.cloudRatchetStorage = CloudRatchetStorage(accessTokenProvider, localKeyStorage)
174177

@@ -401,7 +404,30 @@ abstract class EThreeCore {
401404
* user's identity is already present in Virgil cloud.
402405
*/
403406
fun backupPrivateKey(password: String): Completable =
404-
backupWorker.backupPrivateKey(password)
407+
backupWorker.backupPrivateKey(null, password)
408+
409+
/**
410+
* Encrypts the user's private key using the user's [password] and backs up the encrypted
411+
* private key to Virgil's cloud. This enables users to log in from other devices and have
412+
* access to their private key to decrypt data.
413+
*
414+
* Encrypts loaded from private keys local storage user's *Private key* using *Public key*
415+
* that is generated based on provided [password] after that backs up encrypted user's
416+
* *Private key* to the Virgil's cloud storage.
417+
*
418+
* Can be called only if private key is on the device otherwise
419+
* [EThreeException.Description.MISSING_PRIVATE_KEY] exception will be thrown.
420+
*
421+
* To start execution of the current function, please see [Completable] description.
422+
*
423+
* @param keyName Is a name that would be used to store backup in the cloud.
424+
*
425+
* @throws EThreeException.Description.MISSING_PRIVATE_KEY
426+
* @throws EThreeException.Description.PRIVATE_KEY_BACKUP_EXISTS If private key with current
427+
* user's identity is already present in Virgil cloud.
428+
*/
429+
fun backupPrivateKey(keyName: String, password: String): Completable =
430+
backupWorker.backupPrivateKey(keyName, password)
405431

406432
/**
407433
* Pulls user's private key from the Virgil's cloud, decrypts it with *Private key* that
@@ -417,7 +443,25 @@ abstract class EThreeCore {
417443
* already present on the device locally.
418444
*/
419445
fun restorePrivateKey(password: String): Completable =
420-
backupWorker.restorePrivateKey(password)
446+
backupWorker.restorePrivateKey(null, password)
447+
448+
/**
449+
* Pulls user's private key from the Virgil's cloud, decrypts it with *Private key* that
450+
* is generated based on provided [password] and saves it to the current private keys
451+
* local storage.
452+
*
453+
* To start execution of the current function, please see [Completable] description.
454+
*
455+
* @param keyName Is a name that been used to store backup in the cloud.
456+
*
457+
* @throws EThreeException.Description.NO_PRIVATE_KEY_BACKUP If private key backup was not
458+
* found.
459+
* @throws EThreeException(EThreeException.Description.WRONG_PASSWORD) If [password] is wrong.
460+
* @throws EThreeException(EThreeException.Description.PRIVATE_KEY_EXISTS) If private key
461+
* already present on the device locally.
462+
*/
463+
fun restorePrivateKey(keyName: String, password: String): Completable =
464+
backupWorker.restorePrivateKey(keyName, password)
421465

422466
/**
423467
* Changes the password on a backed-up private key.
@@ -1273,7 +1317,9 @@ abstract class EThreeCore {
12731317
data class PrivateKeyChangedParams(val card: Card, val isNew: Boolean)
12741318

12751319
internal fun privateKeyChanged(params: PrivateKeyChangedParams? = null) {
1320+
logger.finer("Private key changed")
12761321
if (params != null) {
1322+
logger.finest("Store card in card storage")
12771323
lookupManager.cardStorage.storeCard(params.card)
12781324
}
12791325

@@ -1288,6 +1334,7 @@ abstract class EThreeCore {
12881334
}
12891335

12901336
internal fun privateKeyDeleted() {
1337+
logger.finer("Private key deleted")
12911338
lookupManager.cardStorage.reset()
12921339
groupManager?.localGroupStorage?.reset()
12931340
tempChannelManager?.localStorage?.reset()
@@ -1366,9 +1413,11 @@ abstract class EThreeCore {
13661413
}
13671414

13681415
private fun setupRatchet(params: PrivateKeyChangedParams? = null, keyPair: VirgilKeyPair) {
1416+
logger.fine("Setup Ratchet")
13691417
if (!enableRatchet) throw EThreeRatchetException(EThreeRatchetException.Description.RATCHET_IS_DISABLED)
13701418

13711419
if (params != null) {
1420+
logger.finer("Setup Ratchet with params. isNew = ${params.isNew}")
13721421
val chat = setupSecureChat(keyPair, params.card)
13731422

13741423
if (params.isNew) {
@@ -1394,6 +1443,7 @@ abstract class EThreeCore {
13941443

13951444
scheduleKeysRotation(chat, false)
13961445
} else {
1446+
logger.finer("Setup Ratchet without params")
13971447
val card = findCachedUser(this.identity).get() ?: throw EThreeRatchetException(
13981448
EThreeRatchetException.Description.NO_SELF_CARD_LOCALLY
13991449
)
@@ -1419,11 +1469,12 @@ abstract class EThreeCore {
14191469
}
14201470

14211471
private fun scheduleKeysRotation(chat: SecureChat, startFromNow: Boolean) {
1472+
logger.finer("Schedule keys rotation. Start from now = $startFromNow")
14221473
val secureChat = getSecureChat()
14231474

14241475
this.timer = RepeatingTimer(this.keyRotationInterval, startFromNow, object : TimerTask() {
14251476
override fun run() {
1426-
logger.info("\"Key rotation started\"")
1477+
logger.info("Key rotation started")
14271478

14281479
try {
14291480
val logs = secureChat.rotateKeys().get()

ethree-common/src/main/java/com/virgilsecurity/android/common/storage/cloud/CloudKeyManager.kt

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,27 @@ import com.virgilsecurity.android.common.util.Const.VIRGIL_BASE_URL
4040
import com.virgilsecurity.keyknox.KeyknoxManager
4141
import com.virgilsecurity.keyknox.client.HttpClient
4242
import com.virgilsecurity.keyknox.client.KeyknoxClient
43+
import com.virgilsecurity.keyknox.client.KeyknoxPullParams
44+
import com.virgilsecurity.keyknox.client.KeyknoxPushParams
45+
import com.virgilsecurity.keyknox.cloud.CloudEntrySerializer
4346
import com.virgilsecurity.keyknox.cloud.CloudKeyStorage
4447
import com.virgilsecurity.keyknox.exception.DecryptionFailedException
48+
import com.virgilsecurity.keyknox.exception.EntrySavingException
4549
import com.virgilsecurity.keyknox.exception.KeyknoxCryptoException
50+
import com.virgilsecurity.keyknox.model.CloudEntries
4651
import com.virgilsecurity.keyknox.model.CloudEntry
52+
import com.virgilsecurity.keyknox.utils.Serializer
4753
import com.virgilsecurity.pythia.brainkey.BrainKey
4854
import com.virgilsecurity.pythia.brainkey.BrainKeyContext
4955
import com.virgilsecurity.pythia.client.VirgilPythiaClient
5056
import com.virgilsecurity.pythia.crypto.VirgilPythiaCrypto
5157
import com.virgilsecurity.sdk.crypto.VirgilCrypto
5258
import com.virgilsecurity.sdk.crypto.VirgilPrivateKey
5359
import com.virgilsecurity.sdk.jwt.contract.AccessTokenProvider
60+
import com.virgilsecurity.sdk.storage.JsonKeyEntry
61+
import com.virgilsecurity.sdk.utils.ConvertionUtils
5462
import java.net.URL
63+
import java.util.*
5564

5665
/**
5766
* CloudKeyManager
@@ -85,13 +94,36 @@ internal class CloudKeyManager internal constructor(
8594

8695
internal fun exists(password: String) = setupCloudKeyStorage(password).exists(identity)
8796

88-
internal fun store(key: VirgilPrivateKey, password: String) {
97+
internal fun store(key: VirgilPrivateKey, keyName: String?, password: String) {
8998
val exportedIdentityKey = this.crypto.exportPrivateKey(key)
90-
setupCloudKeyStorage(password).store(this.identity, exportedIdentityKey)
99+
if (keyName == null) {
100+
// Store key in keyknox v1
101+
setupCloudKeyStorage(password).store(this.identity, exportedIdentityKey)
102+
} else {
103+
// Store key in keyknox v2
104+
val brainKeyPair = this.brainKey.generateKeyPair(password)
105+
val pullParams = KeyknoxPullParams(this.identity, "e3kit", "backup", keyName)
106+
val keyknoxValue = this.keyknoxManager.pullValue(pullParams, listOf(brainKeyPair.publicKey), brainKeyPair.privateKey)
107+
108+
val params = KeyknoxPushParams(listOf(this.identity), "e3kit", "backup", keyName)
109+
val now = Date()
110+
val entry = CloudEntry(this.identity, exportedIdentityKey, now, now, mapOf())
111+
this.keyknoxManager.pushValue(params, ConvertionUtils.toBytes(Serializer.gson.toJson(entry)), keyknoxValue.keyknoxHash, listOf(brainKeyPair.publicKey), brainKeyPair.privateKey)
112+
}
91113
}
92114

93-
internal fun retrieve(password: String): CloudEntry {
94-
return setupCloudKeyStorage(password).retrieve(identity)
115+
internal fun retrieve(keyName: String?, password: String): CloudEntry {
116+
if (keyName == null) {
117+
// Retrieve key from keyknox v1
118+
return setupCloudKeyStorage(password).retrieve(keyName ?: this.identity)
119+
} else {
120+
// Retrieve key from keyknox v2
121+
val brainKeyPair = this.brainKey.generateKeyPair(password)
122+
val pullParams = KeyknoxPullParams(this.identity, "e3kit", "backup", keyName)
123+
val keyknoxValue = this.keyknoxManager.pullValue(pullParams, listOf(brainKeyPair.publicKey), brainKeyPair.privateKey)
124+
val entry = Serializer.gson.fromJson<CloudEntry>(ConvertionUtils.toString(keyknoxValue.value), CloudEntry::class.java)
125+
return entry
126+
}
95127
}
96128

97129
internal fun delete(password: String) {

ethree-common/src/main/java/com/virgilsecurity/android/common/worker/AuthEncryptWorker.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,14 @@ import com.virgilsecurity.android.common.model.FindUsersResult
3838
import com.virgilsecurity.android.common.storage.local.LocalKeyStorage
3939
import com.virgilsecurity.common.extension.toData
4040
import com.virgilsecurity.common.model.Data
41+
import com.virgilsecurity.keyknox.utils.unwrapCompanionClass
4142
import com.virgilsecurity.sdk.cards.Card
4243
import com.virgilsecurity.sdk.crypto.VirgilCrypto
4344
import com.virgilsecurity.sdk.crypto.VirgilPublicKey
4445
import com.virgilsecurity.sdk.crypto.exceptions.VerificationException
4546
import java.nio.charset.StandardCharsets
4647
import java.util.*
48+
import java.util.logging.Logger
4749

4850
/**
4951
* AuthEncryptWorker
@@ -63,6 +65,7 @@ internal class AuthEncryptWorker internal constructor(
6365
decryptInternal(data, user?.publicKey)
6466

6567
internal fun authDecrypt(data: Data, user: Card, date: Date): Data {
68+
logger.fine("Auth decrypt data with card ${user.identifier}")
6669
var card = user
6770

6871
while (card.previousCard != null) {
@@ -77,6 +80,7 @@ internal class AuthEncryptWorker internal constructor(
7780
}
7881

7982
@JvmOverloads internal fun authDecrypt(text: String, user: Card? = null): String {
83+
logger.fine("Auth decrypt text with card ${user?.identifier}")
8084
val data = try {
8185
Data.fromBase64String(text)
8286
} catch (exception: IllegalArgumentException) {
@@ -89,6 +93,7 @@ internal class AuthEncryptWorker internal constructor(
8993
}
9094

9195
internal fun authDecrypt(text: String, user: Card, date: Date): String {
96+
logger.fine("Auth decrypt text with card ${user.identifier}")
9297
val data = try {
9398
Data.fromBase64String(text)
9499
} catch (exception: IllegalArgumentException) {
@@ -101,6 +106,7 @@ internal class AuthEncryptWorker internal constructor(
101106
}
102107

103108
@JvmOverloads internal fun authEncrypt(text: String, users: FindUsersResult? = null): String {
109+
logger.fine("Auth encrypt text")
104110
if (users != null) require(users.isNotEmpty()) { "Passed empty FindUsersResult" }
105111

106112
val data = try {
@@ -143,4 +149,8 @@ internal class AuthEncryptWorker internal constructor(
143149
}
144150
}
145151
}
152+
153+
companion object {
154+
private val logger = Logger.getLogger(unwrapCompanionClass(this::class.java).name)
155+
}
146156
}

ethree-common/src/main/java/com/virgilsecurity/android/common/worker/AuthorizationWorker.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015-2020, Virgil Security, Inc.
2+
* Copyright (c) 2015-2021, Virgil Security, Inc.
33
*
44
* Lead Maintainer: Virgil Security Inc. <support@virgilsecurity.com>
55
*
@@ -36,8 +36,10 @@ package com.virgilsecurity.android.common.worker
3636
import com.virgilsecurity.android.common.exception.EThreeException
3737
import com.virgilsecurity.android.common.storage.local.LocalKeyStorage
3838
import com.virgilsecurity.common.model.Completable
39+
import com.virgilsecurity.keyknox.utils.unwrapCompanionClass
3940
import com.virgilsecurity.sdk.cards.CardManager
4041
import com.virgilsecurity.sdk.crypto.VirgilKeyPair
42+
import java.util.logging.Logger
4143

4244
/**
4345
* AuthorizationWorker
@@ -54,6 +56,7 @@ internal class AuthorizationWorker internal constructor(
5456
@JvmOverloads
5557
internal fun register(keyPair: VirgilKeyPair? = null) = object : Completable {
5658
override fun execute() {
59+
logger.fine("Register new key pair")
5760
if (localKeyStorage.exists())
5861
throw EThreeException(EThreeException.Description.PRIVATE_KEY_EXISTS)
5962

@@ -70,6 +73,7 @@ internal class AuthorizationWorker internal constructor(
7073

7174
@Synchronized internal fun unregister() = object : Completable {
7275
override fun execute() {
76+
logger.fine("Unregister key pair")
7377
val cards = cardManager.searchCards(this@AuthorizationWorker.identity)
7478
if (cards.isEmpty()) {
7579
throw EThreeException(EThreeException.Description.USER_IS_NOT_REGISTERED)
@@ -85,6 +89,7 @@ internal class AuthorizationWorker internal constructor(
8589

8690
@Synchronized internal fun rotatePrivateKey() = object : Completable {
8791
override fun execute() {
92+
logger.fine("Rotate private key")
8893
if (localKeyStorage.exists())
8994
throw EThreeException(EThreeException.Description.PRIVATE_KEY_EXISTS)
9095

@@ -99,7 +104,12 @@ internal class AuthorizationWorker internal constructor(
99104
internal fun hasLocalPrivateKey() = localKeyStorage.exists()
100105

101106
internal fun cleanup() {
107+
logger.fine("Cleanup")
102108
localKeyStorage.delete()
103109
privateKeyDeleted()
104110
}
111+
112+
companion object {
113+
private val logger = Logger.getLogger(unwrapCompanionClass(this::class.java).name)
114+
}
105115
}

0 commit comments

Comments
 (0)