Skip to content

Commit 0a5aaa8

Browse files
Merge pull request #55 from VirgilSecurity/dev
Resolve multi card issues
2 parents 1c2507e + 3cbc708 commit 0a5aaa8

File tree

9 files changed

+313
-11
lines changed

9 files changed

+313
-11
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.7'
144+
final String SDK_VERSION = '2.0.8'
145145

146146
subprojects {
147147
group BASE_VIRGIL_PACKAGE

ethree-benchmark/src/androidTest/java/com/virgilsecurity/android/ethree_benchmark/EThreeBenchmark.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,15 @@ class EThreeBenchmark(
149149
}
150150
}
151151

152+
@Test
153+
fun dummy_test() {
154+
val state = benchmarkRule.getState()
155+
156+
while (state.keepRunning()) {
157+
UUID.randomUUID()
158+
}
159+
}
160+
152161
companion object {
153162
private const val TEXT = "Hello, my name is text. I am here to be encrypted (:"
154163

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright (c) 2015-2020, Virgil Security, Inc.
3+
*
4+
* Lead Maintainer: Virgil Security Inc. <support@virgilsecurity.com>
5+
*
6+
* All rights reserved.
7+
*
8+
* Redistribution and use in source and binary forms, with or without
9+
* modification, are permitted provided that the following conditions are met:
10+
*
11+
* (1) Redistributions of source code must retain the above copyright notice, this
12+
* list of conditions and the following disclaimer.
13+
*
14+
* (2) Redistributions in binary form must reproduce the above copyright notice,
15+
* this list of conditions and the following disclaimer in the documentation
16+
* and/or other materials provided with the distribution.
17+
*
18+
* (3) Neither the name of virgil nor the names of its
19+
* contributors may be used to endorse or promote products derived from
20+
* this software without specific prior written permission.
21+
*
22+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32+
*/
33+
34+
package com.virgilsecurity.android.common.snippet
35+
36+
import androidx.test.ext.junit.runners.AndroidJUnit4
37+
import com.virgilsecurity.android.common.callback.OnGetTokenCallback
38+
import com.virgilsecurity.android.common.utils.TestConfig
39+
import com.virgilsecurity.android.common.utils.TestUtils
40+
import com.virgilsecurity.android.ethree.interaction.EThree
41+
import com.virgilsecurity.common.model.Data
42+
import com.virgilsecurity.sdk.crypto.VirgilCrypto
43+
import org.junit.Assert.assertArrayEquals
44+
import org.junit.Assert.assertNotNull
45+
import org.junit.Before
46+
import org.junit.Test
47+
import org.junit.runner.RunWith
48+
import java.io.ByteArrayInputStream
49+
import java.io.ByteArrayOutputStream
50+
import java.util.*
51+
52+
/**
53+
* This test covers snippets that used in documentation.
54+
*/
55+
@RunWith(AndroidJUnit4::class)
56+
class SnippetsTest {
57+
58+
private lateinit var aliceIdentity: String
59+
private lateinit var bobIdentity: String
60+
private lateinit var crypto: VirgilCrypto
61+
private lateinit var aliceEthree: EThree
62+
private lateinit var bobEthree: EThree
63+
64+
@Before
65+
fun setup() {
66+
this.crypto = VirgilCrypto()
67+
this.aliceIdentity = UUID.randomUUID().toString()
68+
this.aliceEthree = EThree(aliceIdentity,
69+
object : OnGetTokenCallback {
70+
override fun onGetToken(): String {
71+
return TestUtils.generateTokenString(aliceIdentity)
72+
}
73+
},
74+
TestConfig.context)
75+
assertNotNull(this.aliceEthree)
76+
this.aliceEthree.register().execute()
77+
78+
this.bobIdentity = UUID.randomUUID().toString()
79+
this.bobEthree = EThree(bobIdentity,
80+
object : OnGetTokenCallback {
81+
override fun onGetToken(): String {
82+
return TestUtils.generateTokenString(bobIdentity)
83+
}
84+
},
85+
TestConfig.context)
86+
assertNotNull(this.bobEthree)
87+
this.bobEthree.register().execute()
88+
}
89+
90+
@Test
91+
fun encryptShared_complex() {
92+
val triple = encryptShared()
93+
val p2pEncryptedStreamKeyData = triple.first
94+
val groupEncryptedStreamKeyData = triple.second
95+
val encryptedData = triple.third
96+
97+
val decryptedData = decryptShared(p2pEncryptedStreamKeyData, groupEncryptedStreamKeyData, encryptedData)
98+
99+
assertArrayEquals("Hello".toByteArray(), decryptedData)
100+
}
101+
102+
private fun encryptShared(): Triple<ByteArray, ByteArray, ByteArray> {
103+
// encryptShared >>
104+
105+
// 1. Prepare streams.
106+
val plaintext = "Hello"
107+
val data = plaintext.toByteArray()
108+
val inputStream = ByteArrayInputStream(data)
109+
val inputStreamSize = data.size
110+
val encryptedOutputStream = ByteArrayOutputStream()
111+
112+
// 2. Encrypt stream.
113+
val streamKeyData = aliceEthree.encryptShared(inputStream, inputStreamSize, encryptedOutputStream)
114+
115+
// 3. Upload data from `encryptedOutputStream` to a remote storage.
116+
117+
/**
118+
* Application specific code.
119+
*/
120+
121+
// 4.1 Encrypt `streamKeyData` to a specific user (peer-to-peer).
122+
val bobCard = aliceEthree.findUser(bobIdentity).get()
123+
val p2pEncryptedStreamKeyData = aliceEthree.authEncrypt(Data(streamKeyData), bobCard)
124+
125+
// 4.2 Encrypt `streamKeyData` to a group.
126+
val groupId = "group-chat-1"
127+
val bobUsersResult = aliceEthree.findUsers(arrayListOf(bobIdentity)).get()
128+
val aliceGroup = aliceEthree.createGroup(groupId, bobUsersResult).get()
129+
val groupEncryptedStreamKeyData = aliceGroup.encrypt(streamKeyData)
130+
131+
// 5. Send encrypted `streamKeyData` (p2pEncryptedStreamKeyData, or groupEncryptedStreamKeyData) to destination device.
132+
133+
/**
134+
* Application specific code.
135+
*/
136+
137+
// << encryptShared
138+
139+
return Triple(p2pEncryptedStreamKeyData.value, groupEncryptedStreamKeyData, encryptedOutputStream.toByteArray())
140+
}
141+
142+
private fun decryptShared(p2pEncryptedStreamKeyData: ByteArray, groupEncryptedStreamKeyData: ByteArray, encryptedData: ByteArray): ByteArray? {
143+
// decryptShared >>
144+
145+
// 1. Receive `encryptedStreamKeyData` and download data from the remote storage.
146+
/**
147+
* Application specific code.
148+
*/
149+
150+
// 2. Prepare streams.
151+
val encryptedInputStream = ByteArrayInputStream(encryptedData)
152+
val decryptedOutputStream = ByteArrayOutputStream()
153+
154+
// 3. Find initiator's Card.
155+
val aliceCard = bobEthree.findUser(aliceIdentity).get()
156+
157+
// 4.1 Decrypt `encryptedStreamKeyData` received peer-to-peer.
158+
val p2pDecryptedStreamKeyData = bobEthree.authDecrypt(Data(p2pEncryptedStreamKeyData), aliceCard).value
159+
160+
// 4.2 Decrypt `encryptedStreamKeyData` received to the group.
161+
val groupId = "group-chat-1"
162+
val bobGroup = bobEthree.loadGroup(groupId, aliceCard).get() // load correspond group
163+
val groupDecryptedStreamKeyData = bobGroup.decrypt(groupEncryptedStreamKeyData, aliceCard) // decrypt key
164+
165+
// 5. Decrypt stream.
166+
val decryptedStreamKeyData = p2pDecryptedStreamKeyData ?: groupDecryptedStreamKeyData
167+
168+
bobEthree.decryptShared(encryptedInputStream, decryptedOutputStream, decryptedStreamKeyData, aliceCard)
169+
170+
// << decryptShared
171+
172+
return decryptedOutputStream.toByteArray()
173+
}
174+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,6 @@ class GroupException @JvmOverloads constructor(
5555
GROUP_IS_OUTDATED(ErrorCode.GROUP + 10, "Group is not up to date. Call update or loadGroup."),
5656
INCONSISTENT_STATE(ErrorCode.GROUP + 11, "Inconsistent state."),
5757
INITIATOR_REMOVAL_FAILED(ErrorCode.GROUP + 12, "Group initiator is not able to remove himself from a group."),
58+
GROUP_ALREADY_EXISTS(ErrorCode.GROUP + 13, "Group with the same ID is already exists."),
5859
}
5960
}

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
package com.virgilsecurity.android.common.storage.cloud
3535

36+
import com.virgilsecurity.android.common.exception.GroupException
3637
import com.virgilsecurity.android.common.model.Ticket
3738
import com.virgilsecurity.android.common.storage.local.LocalKeyStorage
3839
import com.virgilsecurity.android.common.util.Const
@@ -41,6 +42,7 @@ import com.virgilsecurity.common.util.toHexString
4142
import com.virgilsecurity.crypto.foundation.GroupSessionMessage
4243
import com.virgilsecurity.keyknox.KeyknoxManager
4344
import com.virgilsecurity.keyknox.client.*
45+
import com.virgilsecurity.keyknox.exception.KeyknoxServiceException
4446
import com.virgilsecurity.sdk.cards.Card
4547
import com.virgilsecurity.sdk.crypto.VirgilPublicKey
4648
import com.virgilsecurity.sdk.jwt.contract.AccessTokenProvider
@@ -80,11 +82,26 @@ internal class CloudTicketStorage internal constructor(
8082
sessionId,
8183
"$epoch")
8284

83-
keyknoxManager.pushValue(params,
84-
ticketData,
85-
null,
86-
publicKeys + selfKeyPair.publicKey,
87-
selfKeyPair.privateKey)
85+
try {
86+
keyknoxManager.pushValue(params,
87+
ticketData,
88+
null,
89+
publicKeys + selfKeyPair.publicKey,
90+
selfKeyPair.privateKey)
91+
}
92+
catch (e: KeyknoxServiceException) {
93+
// Maybe the group is already exists
94+
var pullParams = KeyknoxPullParams(this.identity, GROUP_SESSION_ROOT, sessionId, "$epoch")
95+
val pulledValue = keyknoxManager.pullValue(pullParams,
96+
publicKeys + selfKeyPair.publicKey,
97+
selfKeyPair.privateKey)
98+
99+
if (pulledValue != null) {
100+
throw GroupException(GroupException.Description.GROUP_ALREADY_EXISTS)
101+
} else {
102+
throw e
103+
}
104+
}
88105
}
89106

90107
internal fun getEpochs(sessionId: Data, identity: String): Set<String> {

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,13 @@ internal class AuthorizationWorker internal constructor(
7171
@Synchronized internal fun unregister() = object : Completable {
7272
override fun execute() {
7373
val cards = cardManager.searchCards(this@AuthorizationWorker.identity)
74-
val card = cards.firstOrNull()
75-
?: throw EThreeException(EThreeException.Description.USER_IS_NOT_REGISTERED)
74+
if (cards.isEmpty()) {
75+
throw EThreeException(EThreeException.Description.USER_IS_NOT_REGISTERED)
76+
}
77+
cards.forEach { card ->
78+
cardManager.revokeCard(card.identifier)
79+
}
7680

77-
cardManager.revokeCard(card.identifier)
7881
localKeyStorage.delete()
7982
privateKeyDeleted()
8083
}

tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/sync/EThreeGroupsTest.kt

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@
3434
package com.virgilsecurity.android.ethree.interaction.sync
3535

3636
import com.virgilsecurity.android.common.exception.EThreeException
37+
import com.virgilsecurity.android.common.exception.GroupException
3738
import com.virgilsecurity.android.common.model.EThreeParams
3839
import com.virgilsecurity.android.common.model.FindUsersResult
3940
import com.virgilsecurity.android.ethree.interaction.EThree
4041
import com.virgilsecurity.android.ethree.utils.TestConfig
42+
import com.virgilsecurity.keyknox.exception.KeyknoxServiceException
4143
import com.virgilsecurity.sdk.cards.Card
4244
import com.virgilsecurity.sdk.cards.CardManager
4345
import com.virgilsecurity.sdk.cards.model.RawSignedModel
@@ -53,7 +55,7 @@ import com.virgilsecurity.sdk.jwt.accessProviders.GeneratorJwtProvider
5355
import com.virgilsecurity.sdk.storage.DefaultKeyStorage
5456
import com.virgilsecurity.sdk.storage.KeyStorage
5557
import com.virgilsecurity.sdk.utils.Tuple
56-
import junit.framework.Assert
58+
import org.junit.Assert
5759
import org.junit.Before
5860
import org.junit.Test
5961
import java.util.*
@@ -121,6 +123,44 @@ class EThreeGroupsTest {
121123
val userTimelineGroup = eThree.createGroup("feed-group-${identity}", userCards).get()
122124
}
123125

126+
@Test
127+
fun createGroup_alreadyExists_shouldFail() {
128+
// Register a set of identities
129+
val size = 2
130+
val otherUsers: MutableSet<String> = mutableSetOf()
131+
for (i in 1..size) {
132+
otherUsers.add(UUID.randomUUID().toString())
133+
}
134+
Assert.assertEquals(size, otherUsers.size)
135+
136+
otherUsers.forEach { identity ->
137+
val cardManager = initCardManager(identity)
138+
val rawCard = generateRawCard(identity, cardManager).right
139+
cardManager.publishCard(rawCard)
140+
}
141+
142+
// Initialize eThree
143+
val params = EThreeParams(identity, {jwtGenerator.generateToken(identity).stringRepresentation()}, TestConfig.context)
144+
val eThree = EThree(params)
145+
eThree.register().execute()
146+
147+
val userCards = eThree.findUsers(
148+
otherUsers.toList(),
149+
forceReload = true,
150+
checkResult = false
151+
).get()
152+
val createdGroup = eThree.createGroup("feed-group-${identity}", userCards).get()
153+
Assert.assertNotNull(createdGroup)
154+
155+
try {
156+
eThree.createGroup("feed-group-${identity}", userCards).get()
157+
Assert.fail("Group with the same Id shouldn't be created");
158+
}
159+
catch (e: GroupException) {
160+
Assert.assertEquals(GroupException.Description.GROUP_ALREADY_EXISTS, e.description);
161+
}
162+
}
163+
124164
private fun initCardManager(identity: String): CardManager {
125165
val cardCrypto = VirgilCardCrypto()
126166
return CardManager(

tests/src/androidTest/java/com/virgilsecurity/android/ethree/interaction/sync/EThreeSyncPositive.kt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ package com.virgilsecurity.android.ethree.interaction.sync
3636
import com.virgilsecurity.android.common.callback.OnGetTokenCallback
3737
import com.virgilsecurity.android.ethree.interaction.EThree
3838
import com.virgilsecurity.android.ethree.utils.TestConfig
39-
import com.virgilsecurity.android.ethree.utils.TestUtils
4039
import com.virgilsecurity.keyknox.KeyknoxManager
4140
import com.virgilsecurity.keyknox.client.KeyknoxClient
4241
import com.virgilsecurity.keyknox.cloud.CloudKeyStorage
@@ -79,6 +78,7 @@ class EThreeSyncPositive {
7978
private lateinit var identity: String
8079
private lateinit var jwtGenerator: JwtGenerator
8180
private lateinit var keyStorage: KeyStorage
81+
private lateinit var crypto: VirgilCrypto
8282

8383
@Before fun setup() {
8484
jwtGenerator = JwtGenerator(
@@ -91,6 +91,7 @@ class EThreeSyncPositive {
9191

9292
keyStorage = DefaultKeyStorage(TestConfig.DIRECTORY_PATH, TestConfig.KEYSTORE_NAME)
9393
identity = UUID.randomUUID().toString()
94+
crypto = VirgilCrypto()
9495
}
9596

9697
private fun initAndRegisterEThree(identity: String): EThree {
@@ -179,6 +180,27 @@ class EThreeSyncPositive {
179180
assertEquals(0, cardsUnregistered.size)
180181
}
181182

183+
@Test fun unregister_multiple_cards() {
184+
val eThree = initAndRegisterEThree(identity)
185+
assertTrue(keyStorage.exists(identity))
186+
187+
// Register one more card for the same identity
188+
val cardManager = initCardManager(identity)
189+
val keyPair1: VirgilKeyPair = crypto.generateKeyPair()
190+
val card2 = cardManager.publishCard(keyPair1.privateKey, keyPair1.publicKey, identity)
191+
192+
val cards = initCardManager(identity).searchCards(identity)
193+
assertNotNull(cards)
194+
assertEquals(2, cards.size)
195+
196+
// unregister should remove all cards of the identity
197+
eThree.unregister().execute()
198+
assertFalse(keyStorage.exists(identity))
199+
200+
val cardsUnregistered = initCardManager(identity).searchCards(identity)
201+
assertEquals(0, cardsUnregistered.size)
202+
}
203+
182204
// STE-15_2-4 - Sync
183205
@Test fun backup_key_after_register() {
184206
val password = UUID.randomUUID().toString()

0 commit comments

Comments
 (0)