Skip to content

Commit

Permalink
Merge pull request #55 from VirgilSecurity/dev
Browse files Browse the repository at this point in the history
Resolve multi card issues
  • Loading branch information
andrii-iakovenko authored Nov 24, 2020
2 parents 1c2507e + 3cbc708 commit 0a5aaa8
Show file tree
Hide file tree
Showing 9 changed files with 313 additions and 11 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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.7'
final String SDK_VERSION = '2.0.8'

subprojects {
group BASE_VIRGIL_PACKAGE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,15 @@ class EThreeBenchmark(
}
}

@Test
fun dummy_test() {
val state = benchmarkRule.getState()

while (state.keepRunning()) {
UUID.randomUUID()
}
}

companion object {
private const val TEXT = "Hello, my name is text. I am here to be encrypted (:"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* Copyright (c) 2015-2020, Virgil Security, Inc.
*
* Lead Maintainer: Virgil Security Inc. <support@virgilsecurity.com>
*
* 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.utils.TestConfig
import com.virgilsecurity.android.common.utils.TestUtils
import com.virgilsecurity.android.ethree.interaction.EThree
import com.virgilsecurity.common.model.Data
import com.virgilsecurity.sdk.crypto.VirgilCrypto
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.util.*

/**
* This test covers snippets that used in documentation.
*/
@RunWith(AndroidJUnit4::class)
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 {
override fun onGetToken(): String {
return TestUtils.generateTokenString(aliceIdentity)
}
},
TestConfig.context)
assertNotNull(this.aliceEthree)
this.aliceEthree.register().execute()

this.bobIdentity = UUID.randomUUID().toString()
this.bobEthree = EThree(bobIdentity,
object : OnGetTokenCallback {
override fun onGetToken(): String {
return TestUtils.generateTokenString(bobIdentity)
}
},
TestConfig.context)
assertNotNull(this.bobEthree)
this.bobEthree.register().execute()
}

@Test
fun encryptShared_complex() {
val triple = encryptShared()
val p2pEncryptedStreamKeyData = triple.first
val groupEncryptedStreamKeyData = triple.second
val encryptedData = triple.third

val decryptedData = decryptShared(p2pEncryptedStreamKeyData, groupEncryptedStreamKeyData, encryptedData)

assertArrayEquals("Hello".toByteArray(), decryptedData)
}

private fun encryptShared(): Triple<ByteArray, ByteArray, ByteArray> {
// encryptShared >>

// 1. Prepare streams.
val plaintext = "Hello"
val data = plaintext.toByteArray()
val inputStream = ByteArrayInputStream(data)
val inputStreamSize = data.size
val encryptedOutputStream = ByteArrayOutputStream()

// 2. Encrypt stream.
val streamKeyData = aliceEthree.encryptShared(inputStream, inputStreamSize, encryptedOutputStream)

// 3. Upload data from `encryptedOutputStream` to a remote storage.

/**
* Application specific code.
*/

// 4.1 Encrypt `streamKeyData` to a specific user (peer-to-peer).
val bobCard = aliceEthree.findUser(bobIdentity).get()
val p2pEncryptedStreamKeyData = aliceEthree.authEncrypt(Data(streamKeyData), bobCard)

// 4.2 Encrypt `streamKeyData` to a group.
val groupId = "group-chat-1"
val bobUsersResult = aliceEthree.findUsers(arrayListOf(bobIdentity)).get()
val aliceGroup = aliceEthree.createGroup(groupId, bobUsersResult).get()
val groupEncryptedStreamKeyData = aliceGroup.encrypt(streamKeyData)

// 5. Send encrypted `streamKeyData` (p2pEncryptedStreamKeyData, or groupEncryptedStreamKeyData) to destination device.

/**
* Application specific code.
*/

// << encryptShared

return Triple(p2pEncryptedStreamKeyData.value, groupEncryptedStreamKeyData, encryptedOutputStream.toByteArray())
}

private fun decryptShared(p2pEncryptedStreamKeyData: ByteArray, groupEncryptedStreamKeyData: ByteArray, encryptedData: ByteArray): ByteArray? {
// decryptShared >>

// 1. Receive `encryptedStreamKeyData` and download data from the remote storage.
/**
* Application specific code.
*/

// 2. Prepare streams.
val encryptedInputStream = ByteArrayInputStream(encryptedData)
val decryptedOutputStream = ByteArrayOutputStream()

// 3. Find initiator's Card.
val aliceCard = bobEthree.findUser(aliceIdentity).get()

// 4.1 Decrypt `encryptedStreamKeyData` received peer-to-peer.
val p2pDecryptedStreamKeyData = bobEthree.authDecrypt(Data(p2pEncryptedStreamKeyData), aliceCard).value

// 4.2 Decrypt `encryptedStreamKeyData` received to the group.
val groupId = "group-chat-1"
val bobGroup = bobEthree.loadGroup(groupId, aliceCard).get() // load correspond group
val groupDecryptedStreamKeyData = bobGroup.decrypt(groupEncryptedStreamKeyData, aliceCard) // decrypt key

// 5. Decrypt stream.
val decryptedStreamKeyData = p2pDecryptedStreamKeyData ?: groupDecryptedStreamKeyData

bobEthree.decryptShared(encryptedInputStream, decryptedOutputStream, decryptedStreamKeyData, aliceCard)

// << decryptShared

return decryptedOutputStream.toByteArray()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@ class GroupException @JvmOverloads constructor(
GROUP_IS_OUTDATED(ErrorCode.GROUP + 10, "Group is not up to date. Call update or loadGroup."),
INCONSISTENT_STATE(ErrorCode.GROUP + 11, "Inconsistent state."),
INITIATOR_REMOVAL_FAILED(ErrorCode.GROUP + 12, "Group initiator is not able to remove himself from a group."),
GROUP_ALREADY_EXISTS(ErrorCode.GROUP + 13, "Group with the same ID is already exists."),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

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

import com.virgilsecurity.android.common.exception.GroupException
import com.virgilsecurity.android.common.model.Ticket
import com.virgilsecurity.android.common.storage.local.LocalKeyStorage
import com.virgilsecurity.android.common.util.Const
Expand All @@ -41,6 +42,7 @@ import com.virgilsecurity.common.util.toHexString
import com.virgilsecurity.crypto.foundation.GroupSessionMessage
import com.virgilsecurity.keyknox.KeyknoxManager
import com.virgilsecurity.keyknox.client.*
import com.virgilsecurity.keyknox.exception.KeyknoxServiceException
import com.virgilsecurity.sdk.cards.Card
import com.virgilsecurity.sdk.crypto.VirgilPublicKey
import com.virgilsecurity.sdk.jwt.contract.AccessTokenProvider
Expand Down Expand Up @@ -80,11 +82,26 @@ internal class CloudTicketStorage internal constructor(
sessionId,
"$epoch")

keyknoxManager.pushValue(params,
ticketData,
null,
publicKeys + selfKeyPair.publicKey,
selfKeyPair.privateKey)
try {
keyknoxManager.pushValue(params,
ticketData,
null,
publicKeys + selfKeyPair.publicKey,
selfKeyPair.privateKey)
}
catch (e: KeyknoxServiceException) {
// Maybe the group is already exists
var pullParams = KeyknoxPullParams(this.identity, GROUP_SESSION_ROOT, sessionId, "$epoch")
val pulledValue = keyknoxManager.pullValue(pullParams,
publicKeys + selfKeyPair.publicKey,
selfKeyPair.privateKey)

if (pulledValue != null) {
throw GroupException(GroupException.Description.GROUP_ALREADY_EXISTS)
} else {
throw e
}
}
}

internal fun getEpochs(sessionId: Data, identity: String): Set<String> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,13 @@ internal class AuthorizationWorker internal constructor(
@Synchronized internal fun unregister() = object : Completable {
override fun execute() {
val cards = cardManager.searchCards(this@AuthorizationWorker.identity)
val card = cards.firstOrNull()
?: throw EThreeException(EThreeException.Description.USER_IS_NOT_REGISTERED)
if (cards.isEmpty()) {
throw EThreeException(EThreeException.Description.USER_IS_NOT_REGISTERED)
}
cards.forEach { card ->
cardManager.revokeCard(card.identifier)
}

cardManager.revokeCard(card.identifier)
localKeyStorage.delete()
privateKeyDeleted()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@
package com.virgilsecurity.android.ethree.interaction.sync

import com.virgilsecurity.android.common.exception.EThreeException
import com.virgilsecurity.android.common.exception.GroupException
import com.virgilsecurity.android.common.model.EThreeParams
import com.virgilsecurity.android.common.model.FindUsersResult
import com.virgilsecurity.android.ethree.interaction.EThree
import com.virgilsecurity.android.ethree.utils.TestConfig
import com.virgilsecurity.keyknox.exception.KeyknoxServiceException
import com.virgilsecurity.sdk.cards.Card
import com.virgilsecurity.sdk.cards.CardManager
import com.virgilsecurity.sdk.cards.model.RawSignedModel
Expand All @@ -53,7 +55,7 @@ import com.virgilsecurity.sdk.jwt.accessProviders.GeneratorJwtProvider
import com.virgilsecurity.sdk.storage.DefaultKeyStorage
import com.virgilsecurity.sdk.storage.KeyStorage
import com.virgilsecurity.sdk.utils.Tuple
import junit.framework.Assert
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import java.util.*
Expand Down Expand Up @@ -121,6 +123,44 @@ class EThreeGroupsTest {
val userTimelineGroup = eThree.createGroup("feed-group-${identity}", userCards).get()
}

@Test
fun createGroup_alreadyExists_shouldFail() {
// Register a set of identities
val size = 2
val otherUsers: MutableSet<String> = mutableSetOf()
for (i in 1..size) {
otherUsers.add(UUID.randomUUID().toString())
}
Assert.assertEquals(size, otherUsers.size)

otherUsers.forEach { identity ->
val cardManager = initCardManager(identity)
val rawCard = generateRawCard(identity, cardManager).right
cardManager.publishCard(rawCard)
}

// Initialize eThree
val params = EThreeParams(identity, {jwtGenerator.generateToken(identity).stringRepresentation()}, TestConfig.context)
val eThree = EThree(params)
eThree.register().execute()

val userCards = eThree.findUsers(
otherUsers.toList(),
forceReload = true,
checkResult = false
).get()
val createdGroup = eThree.createGroup("feed-group-${identity}", userCards).get()
Assert.assertNotNull(createdGroup)

try {
eThree.createGroup("feed-group-${identity}", userCards).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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ package com.virgilsecurity.android.ethree.interaction.sync
import com.virgilsecurity.android.common.callback.OnGetTokenCallback
import com.virgilsecurity.android.ethree.interaction.EThree
import com.virgilsecurity.android.ethree.utils.TestConfig
import com.virgilsecurity.android.ethree.utils.TestUtils
import com.virgilsecurity.keyknox.KeyknoxManager
import com.virgilsecurity.keyknox.client.KeyknoxClient
import com.virgilsecurity.keyknox.cloud.CloudKeyStorage
Expand Down Expand Up @@ -79,6 +78,7 @@ class EThreeSyncPositive {
private lateinit var identity: String
private lateinit var jwtGenerator: JwtGenerator
private lateinit var keyStorage: KeyStorage
private lateinit var crypto: VirgilCrypto

@Before fun setup() {
jwtGenerator = JwtGenerator(
Expand All @@ -91,6 +91,7 @@ class EThreeSyncPositive {

keyStorage = DefaultKeyStorage(TestConfig.DIRECTORY_PATH, TestConfig.KEYSTORE_NAME)
identity = UUID.randomUUID().toString()
crypto = VirgilCrypto()
}

private fun initAndRegisterEThree(identity: String): EThree {
Expand Down Expand Up @@ -179,6 +180,27 @@ class EThreeSyncPositive {
assertEquals(0, cardsUnregistered.size)
}

@Test fun unregister_multiple_cards() {
val eThree = initAndRegisterEThree(identity)
assertTrue(keyStorage.exists(identity))

// Register one more card for the same identity
val cardManager = initCardManager(identity)
val keyPair1: VirgilKeyPair = crypto.generateKeyPair()
val card2 = cardManager.publishCard(keyPair1.privateKey, keyPair1.publicKey, identity)

val cards = initCardManager(identity).searchCards(identity)
assertNotNull(cards)
assertEquals(2, cards.size)

// unregister should remove all cards of the identity
eThree.unregister().execute()
assertFalse(keyStorage.exists(identity))

val cardsUnregistered = initCardManager(identity).searchCards(identity)
assertEquals(0, cardsUnregistered.size)
}

// STE-15_2-4 - Sync
@Test fun backup_key_after_register() {
val password = UUID.randomUUID().toString()
Expand Down
Loading

0 comments on commit 0a5aaa8

Please sign in to comment.