diff --git a/.gitignore b/.gitignore index 8e76f259..5d63a8f3 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,6 @@ eclipse-aar/ build/ gradle-wrapper.jar gradle-wrapper.properties -gradle.properties # Ignore Gradle GUI config gradle-app.setting @@ -178,3 +177,5 @@ fabric.properties # Project-specific !crypto/libs/virgil_crypto_java.jar publishArtifacts.sh +env.json +compat_data.json diff --git a/.travis.yml b/.travis.yml index 3888e218..9be54f9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,13 +2,6 @@ language: android sudo: required jdk: oraclejdk8 -# Include branches -branches: - only: - - dev - - master - - travis-setup - before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ @@ -26,8 +19,6 @@ env: - ANDROID_EMULATOR_API=24 - ADB_INSTALL_TIMEOUT=5 # minutes - ANDROID_ABI=default/armeabi-v7a - - secure: Nz/+LNm9NO3PgvrwLIKDVjlJTskzZPKYXWnNe1T8kUflcQp6dygc9M3tqOgXVRTb33DPhZFZoM04fHJ7iAAyOydH0uNt3nI8zujFiQg7wGOm4vUczk6PYQrWNe/uAyrov+KjLbjNElBxZPDgMDTRD2e3j+UG0X+Xzjm3LqrWChpDjS037EuCpEILNdNETkHeAMVwAvcHrWkXiFMO6aYCHV+teo0zivrELbo6MCaxfMOKY0eMckgxJFtQbAF3hshr98325cczvIzQt5J/bFBkMaBx0IkSDAktDk++jVnoqIfpQVkM6x4mSjz64Q22BW5wIHsgfkT/Cjm7vn1LNpQsPO28p1Ja/W3TqVCeU1XR6sm7Zn+3b7ecSfGjh7p8L6gu9rzpsGIHOG88aWZuqpnF5Vgxjs4l2AXd/luAjv0ATLY45A9SMllLgzixrV4R0xn/DWUlKHsZ4KjiZZipFRXHYvxI/iB8BJR+fRSPKqWWtVxB44mHN7zBS5PvRfu/L7AugF3C2+5OgU+r15u+hQYdHxOrQhWEUKObSJ42MKjEzwaH6oioYKee0ImdB1iPu0IP1jvcImtWanPGU/wxP4ssjTdEOAZepJg90gNes/Hx2gdal6iRtvN2G1xpCJXty4EBLbK2lIfXP6//IiQG0EDLAJfw5npRBluiDvG/tcDxW1E= - - secure: C7b6XlTt+gqr3O4avm7CwgheSi0o0Hn0SQPZHa1Fs9+xbcNLI5EAE6BMNIDH/70aOl/DygJptxmr7Fx8NwAiU+dGWKqe6NhIqpZiizGz9o1J3FpoF5+v+r0777b2oHrqnUiBT7MO//3R1kFL4WmEyoEKzkwT5yMz3qnCI1feKCvKXtLdoyDt3E9w9fMz83lWSXIp0YYpaMyUDKBVwdkbPFbfvr9Ew2spWjTrUkb0gVTbz+GHzdX0Y0PrKHusdZiLQSd3MqmXP7y4VcIafzKEWgXaxe94GBc/r5v9RLBwhqORIhtcu0GJMq9KuWH2uROspgJ9jzgAAJcrTWxaNJ02oarRQF7QdJPdfuIq5XLGqOaMCLKAFjAaogyPacfbUmcfeFokbPW9I7LoHIDcCydSdYC3K3Iw725FoPoNhVncOuwxouMnaWECXLQVlxCQmNN8QSlTBC6bFrgQ/2NwkMPSkMKr+/dKxnPvnWmo6uzqGSZwnLoGlnIryNPzIf8CZTsSWcl6dWxw7LYNMuPOVZGz4AMN8NKFqIrbl0sE3cr00z8N1FHwX2hD2D6dYRMpnv5l6N6ZxxKQT4BwmmpnFIIiKJyoNodUlgYtZX3iJICLQcpmHTrTle+h6XT24biUFF/uPO2TUGmhCEVzLgdlPvbiCePlAg1oytJIxmXjrmCJx/A= android: components: @@ -56,7 +47,8 @@ before_install: - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license" - chmod +x gradlew - ./gradlew dependencies || true - - openssl aes-256-cbc -K $encrypted_62e1b1822e5d_key -iv $encrypted_62e1b1822e5d_iv -in ethree-common/src/androidTest/resources/compat/compat_data.json.enc -out ethree-common/src/androidTest/resources/compat/compat_data.json -d + - openssl aes-256-cbc -K $encrypted_7a2772d532c7_key -iv $encrypted_7a2772d532c7_iv -in creds.tar.enc -out creds.tar -d + - tar xvf creds.tar before_script: - android list targets @@ -68,7 +60,10 @@ before_script: - adb shell input keyevent 82 & script: - - "./gradlew clean build connectedCheck -PdisablePreDex --stacktrace" + - "./gradlew clean" + - "./gradlew :ethree-common:connectedCheck -PdisablePreDex --stacktrace" + - "./gradlew :tests:connectedCheck -PdisablePreDex --stacktrace" + - "./gradlew :testsenclave:connectedCheck -PdisablePreDex --stacktrace" after_success: .ci/push-javadoc-to-gh-pages.sh diff --git a/README.md b/README.md index b379a010..1db2c01a 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,16 @@ [![Build Status](https://travis-ci.com/VirgilSecurity/virgil-e3kit-kotlin.svg?branch=master)](https://travis-ci.com/VirgilSecurity/virgil-e3kit-kotlin) [![GitHub license](https://img.shields.io/badge/license-BSD%203--Clause-blue.svg)](https://github.com/VirgilSecurity/virgil/blob/master/LICENSE) -[Introduction](#introduction) | [SDK Features](#features) | [Installation](#installation) | [Usage Examples](#usage-examples) | [Enable Group Chat](#enable-group-chat) | [Samples](#samples) | [License](#license) | [Docs](#docs) | [Support](#support) +[Introduction](#introduction) | [SDK Features](#features) | [Installation](#installation) | [Usage Examples](#usage-examples) | [Enable Group Chat](#enable-group-channel) | [Double Ratchet Channel](#double-ratchet-channel) | [Unregistered User Encryption](#unregistered-user-encryption) | [Samples](#samples) | [License](#license) | [Support](#support) ## Introduction - [Virgil Security](https://virgilsecurity.com) provides the E3Kit framework which simplifies work with Virgil Cloud and presents an easy-to-use API for adding a security layer to any application. In a few simple steps you can add end-to-end encryption with multidevice and group chats support. + [Virgil Security](https://virgilsecurity.com) provides the E3Kit which simplifies work with Virgil Cloud and presents an easy-to-use API for adding a security layer to any application. In a few simple steps you can add end-to-end encryption with multidevice and group channels support. The E3Kit allows developers to get up and running with Virgil API quickly and add full end-to-end security to their existing digital solutions to become HIPAA and GDPR compliant and more. ## Features + - Strong end-to-end encryption with authorization - One-to-one and group encryption - Files and stream encryption @@ -21,6 +22,7 @@ The E3Kit allows developers to get up and running with Virgil API quickly and ad - Public keys cache features - Access encrypted data from multiple user devices - Easy setup and integration into new or existing projects +- One-to-one channel with perfect forward secrecy using the Double Ratchet algorithm ## Installation @@ -31,7 +33,6 @@ You can install E3Kit SDK using [Gradle](https://gradle.org/). Please, choose pa | [`E3Kit`](./ethree-kotlin) | Standard package for Android API 21+ (Java/Kotlin) | | [`E3Kit`](./ethree-enclave) | Package with [Android Keystore](https://developer.android.com/training/articles/keystore) for Android API 23+ | - ## Usage Examples > Be sure to import `OnCompleteListener` and `OnResultListener` from `com.virgilsecurity.common.callback`. @@ -67,9 +68,9 @@ val messageToEncrypt = "Hello, Alice and Den!" // Search user's Cards to encrypt for ethree.findUsers(listOf("Alice, Den")) .addCallback(object : OnResultListener { - override fun onSuccess(result: FindUsersResult) { + override fun onSuccess(users: FindUsersResult) { // encrypt text - val encryptedMessage = ethree.encrypt(messageToEncrypt, result) + val encryptedMessage = ethree.authEncrypt(messageToEncrypt, users) } override fun onError(throwable: Throwable) { @@ -86,9 +87,9 @@ Decrypt and verify the signed & encrypted data using sender's public key and rec // Find user ethree.findUsers(listOf("bobUID")) .addCallback(object : OnResultListener { - override fun onSuccess(result: FindUsersResult) { + override fun onSuccess(users: FindUsersResult) { // Decrypt text and verify if it was really written by Bob - val originText = ethree.decrypt(encryptedText, result["bobUID"]) + val originText = ethree.authDecrypt(encryptedText, users["bobUID"]) } override fun onError(throwable: Throwable) { @@ -111,12 +112,12 @@ val usersToEncryptTo = listOf(user1UID, user2UID, user3UID) // Find users ethree.findUsers(usersToEncryptTo) .addCallback(object : OnResultListener { - override fun onSuccess(result: FindUsersResult) { + override fun onSuccess(users: FindUsersResult) { val assetManager = context.assets assetManager.open("some_file.txt").use { inputStream -> ByteArrayOutputStream().use { outputStream -> - ethree.encrypt(inputStream, outputStream, result) + ethree.encrypt(inputStream, outputStream, users) } } } @@ -137,18 +138,84 @@ ByteArrayOutputStream().use { outputStream -> } ``` -## Enable Group Chat +#### Multidevice support + +In order to enable multidevice support you need to backup Private Key. It wiil be encrypted with [BrainKey](https://github.com/VirgilSecurity/virgil-pythia-x), generated from password and sent to virgil cloud. + +```kotlin +val backupListener = + object : OnCompleteListener { + override fun onSuccess() { + // Private Key successfully backuped + } + + override fun onError(throwable: Throwable) { + // Error handling + } + } + +eThree.backupPrivateKey(userPassword).addCallback(backupListener) +``` + +After private key was backuped you can use `restorePrivateKey` method to load and decrypt Private Key from virgil cloud. -E3Kit also provides you with tools for easy group chats creating and management. In this section we assume that your users have installed and initialized the E3Kit, and are already registered at Virgil Cloud. +```kotlin +val restoreListener = + object : OnCompleteListener { + override fun onSuccess() { + // Private Key successfully restored and saved locally + } -### Create Group Chat + override fun onError(throwable: Throwable) { + // Error handling + } + } + +eThree.restorePrivateKey(keyPassword).addCallback(restoreListener) +``` -Let's imagine Alice wants to start a group chat with Bob and Carol. First, Alice creates a new group ticket by running the `createGroup` feature and the E3Kit stores the ticket on the Virgil Cloud. This ticket holds a shared root key for future group encryption. +If you authorize users using password in your application, please do not use the same password to backup Private Key, since it breaks e2ee. Instead, you can derive from your user password two different ones. + +```kotlin + 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 +``` + + +#### Convinience initializer + +`EThree` initializer has plenty of optional parameters to customize it's behaviour. You can easily set them using `EThreeParams` class. + +```kotlin + val params = EThreeParams(identity = "Alice", + tokenCallback = tokenCallback, + context = context) + + params.enableRatchet = true + params.keyChangedCallback = myCallback + + val ethree = EThree(params = params) +``` + +## Enable Group Channel + +In this section, you'll find out how to build a group channel using the Virgil E3Kit. + +We assume that your users have installed and initialized the E3Kit, and used snippet above to register. + +#### Create group channel + +Let's imagine Alice wants to start a group channel with Bob and Carol. First, Alice creates a new group ticket by running the `createGroup` feature and the E3Kit stores the ticket on the Virgil Cloud. This ticket holds a shared root key for future group encryption. Alice has to specify a unique `identifier` of group with length > 10 and `findUsersResult` of participants. We recommend tying this identifier to your unique transport channel id. + ```kotlin ethree.createGroup(groupId, users).addCallback(object : OnResultListener { - override fun onSuccess(result: Group) { + override fun onSuccess(group: Group) { // Group created and saved locally! } @@ -158,13 +225,13 @@ ethree.createGroup(groupId, users).addCallback(object : OnResultListener }) ``` -### Start Group Chat Session +#### Start group channel session -Now, other participants, Bob and Carol, want to join the Alice's group and have to start the group session using the `loadGroup` method that loads and saves the group ticket locally. This function requires specifying the group `identifier` and group initiator's Card. +Now, other participants, Bob and Carol, want to join the Alice's group and have to start the group session by loading the group ticket using the `loadGroup` method. This function requires specifying the group `identifier` and group initiator's Card. ```kotlin ethree.loadGroup(groupId, users["Alice"]!!).addCallback(object : OnResultListener { - override fun onSuccess(result: Group) { + override fun onSuccess(group: Group) { // Group loaded and saved locally! } @@ -174,17 +241,17 @@ ethree.loadGroup(groupId, users["Alice"]!!).addCallback(object : OnResultListene }) ``` -Then, you can use the `getGroup` method to retrieve group instance from local storage. +Use the `loadGroup` method to load and save group locally. Then, you can use the `getGroup` method to retrieve group instance from local storage. ```kotlin val group = ethree.getGroup(groupId) ``` -### Encrypt and Decrypt Messages +#### Encrypt and Decrypt Messages To encrypt and decrypt messages, use the `encrypt` and `decrypt` E3Kit functions, which allows you to work with data and strings. -Use the following code snippets to encrypt messages: +Use the following code-snippets to encrypt messages: ```kotlin // prepare a message @@ -193,20 +260,20 @@ val messageToEncrypt = "Hello, Bob and Carol!" val encrypted = group.encrypt(messageToEncrypt) ``` -Use the following code snippets to decrypt messages: +Use the following code-snippets to decrypt messages: ```kotlin -val decrypted = group.decrypt(encrypted, findUsersResult["Alice"]!!) +val decrypted = group.decrypt(encrypted, users["Alice"]!!) ``` -At the decrypt step, you should also use `findUsers` method to verify that the message hasn't been tempered with. +At the decrypt step, you also use `findUsers` method to verify that the message hasn't been tempered with. -### Manage Group Chat +### Manage Group Channel -E3Kit also allows you to perform other operations, like participants management, while you work with group chat. In current version of E3Kit only the group initiator can change participants or delete group. +E3Kit also allows you to perform other operations, like participants management, while you work with group channel. In this version of E3Kit only group initiator can change participants or delete group. #### Add new participant -To add a new chat member, the chat owner needs to use the `add` method and specify the new member's Card. New member will be able to decrypt all previous messages history. +To add a new channel member, the channel owner has to use the `add` method and specify the new member's Card. New member will be able to decrypt all previous messages history. ```kotlin group.add(users["Den"]!!).addCallback(object : OnCompleteListener { @@ -222,7 +289,7 @@ group.add(users["Den"]!!).addCallback(object : OnCompleteListener { #### Remove participant -To remove a participant, group owner needs to use the `remove` method and specify the member's Card. Removed participants won't be able to load or update this group: +To remove participant, group owner has to use the `remove` method and specify the member's Card. Removed participants won't be able to load or update this group. ```kotlin group.remove(users["Den"]!!).addCallback(object : OnCompleteListener { @@ -236,9 +303,9 @@ group.remove(users["Den"]!!).addCallback(object : OnCompleteListener { }) ``` -#### Update group chat +#### Update group channel -In case of changes in your group, i.e. adding a new participant, or deleting an existing one, each group chat participant has to update the encryption key by calling the `update` E3Kit method or reloading Group by `loadGroup`: +In the event of changes in your group, i.e. adding a new participant, or deleting an existing one, each group channel participant has to update the encryption key by calling the `update` E3Kit method or reloading Group by `loadGroup`. ```kotlin group.update().addCallback(object : OnCompleteListener { @@ -252,9 +319,9 @@ group.update().addCallback(object : OnCompleteListener { }) ``` -#### Delete group chat +#### Delete group channel -To delete a group, the owner needs to use the `deleteGroup` method and specify the group `identifier`: +To delete a group, the owner has to use the `deleteGroup` method and specify the group `identifier`. ```kotlin ethree.deleteGroup(groupId).addCallback(object : OnCompleteListener { @@ -268,6 +335,171 @@ ethree.deleteGroup(groupId).addCallback(object : OnCompleteListener { }) ``` +## Double Ratchet Channel +In this section, you'll find out how to create and manage secure channel sessions between two users using the Double Ratchet algorithm so that each message is separately encrypted. + +**Double Ratchet** is a session key management algorithm that provides extra secure end-to-end encryption for messaging between two users or endpoints. +The Double Ratchet algorithm provides perfect forward secrecy and post-compromise security by generating unique session keys for each new message. Even if the communication is somehow compromised, a potential attacker will only be able to access the most recent message, and soon as a new message is sent by one of the two users, the attacker will be locked out again. + +The session keys are generated using a cryptographically strong unidirectional function, which prevents an attacker from potentially obtaining earlier keys derived from later ones. In addition, the parties renegotiate the keys after each message sent or received (using a new key pair unknown to the attacker), which makes it impossible to obtain later keys from earlier ones. + +We assume that you have installed and initialized the E3Kit, and your application users are registered using the snippet above. + +#### Create channel + +To create a peer-to-peer connection using Double Ratchet protocol use the folowing snippet +```kotlin +ethree.createRatchetChannel(users["Bob"]) + .addCallback(object : OnResultListener { + override fun onSuccess(result: RatchetChannel) { + // Channel created and saved locally! + } + + override fun onError(throwable: Throwable) { + // Error handling + } + + }) +``` + +#### Join channel + +After someone created channel with user, he can join it + +```kotlin +ethree.joinRatchetChannel(users["Bob"]) + .addCallback(object : OnResultListener { + override fun onSuccess(result: RatchetChannel) { + // Channel joined and saved locally! + } + + override fun onError(throwable: Throwable) { + // Error handling + } + + }) +``` + +#### Get channel + +After joining or creating channel you can use getRatchetChannel method to retrieve it from local storage. +```kotlin +val channel = ethree.getRatchetChannel(users["Alice"]) +``` + +#### Delete channel + +Use this snippet to delete channel from local storage and clean cloud invite. + +```kotlin +ethree.deleteRatchetChannel(users["Bob"]) + .addCallback(object : OnCompleteListener { + override fun onSuccess() { + // Channel was deleted! + } + + override fun onError(throwable: Throwable) { + // Error handling + } + }) +``` + +#### Encrypt and decrypt messages + +Use the following code-snippets to encrypt messages: +```kotlin +// prepare a message +val messageToEncrypt = "Hello, Bob!" + +val encrypted = channel.encrypt(messageToEncrypt) +``` + +Use the following code-snippets to decrypt messages: +```kotlin +val decrypted = channel.decrypt(encrypted) +``` + +## Unregistered User Encryption +In this section, you'll learn how to create and use temporary channels in order to send encrypted data to users not yet registered on the Virgil Cloud. + +Warning: the temporary channel key used in this method is stored unencrypted and therefore is not as secure as end-to-end encryption, and should be a last resort after exploring the preferred [non-technical solutions](https://help.virgilsecurity.com/en/articles/3314614-how-do-i-encrypt-for-a-user-that-isn-t-registered-yet-with-e3kit). + +To set up encrypted communication with unregistered user not yet known by Virgil, the channel creator generates a temporary key pair, saves it unencrypted on Virgil Cloud, and gives access to the identity of the future user. The channel creator uses this key for encryption. Then when the participant registers, he can load this temporary key from Virgil Cloud and use to decrypt messages. + +We assume that channel creator has installed and initialized the E3Kit, and used the snippet above to register. + +#### Create channel + +To create a channel with unregistered user use the folowing snippet +```kotlin +ethree.createTemporaryChannel("Bob") + .addCallback(object : OnResultListener { + override fun onSuccess(result: TemporaryChannel) { + // Channel created and saved locally! + } + + override fun onError(throwable: Throwable) { + // Error handling + } + }) +``` + +#### Load channel + +After user is registered, he can load temporary channel +```kotlin +ethree.loadTemporaryChannel(asCreator = false, identity = "Bob") + .addCallback(object : OnResultListener { + override fun onSuccess(result: TemporaryChannel) { + // Channel loaded and saved locally! + } + + override fun onError(throwable: Throwable) { + // Error handling + } + }) + } +``` + +If channel creator changes or cleans up their device, he can load temporary channel in simular way +```kotlin +ethree.loadTemporaryChannel(asCreator = true, identity = "Bob") + .addCallback(object : OnResultListener { + override fun onSuccess(result: TemporaryChannel) { + // Channel loaded and saved locally! + } + + override fun onError(throwable: Throwable) { + // Error handling + } + }) + } +``` + +#### Get channel + +After loading or creating channel, you can use getTemporaryChannel method to retrieve it from local storage +```kotlin +val channel = ethree.getTemporaryChannel("Alice") +``` + +#### Delete channel + +Use this snippet to delete channel from local storage and clean cloud invite + +```kotlin +ethree.deleteTemporaryChannel("Bob") + .addCallback(object : OnCompleteListener { + override fun onSuccess() { + // Channel was deleted! + } + + override fun onError(throwable: Throwable) { + // Error handling + } + }) +``` + ## Samples You can find the code samples for Java and Kotlin here: @@ -281,33 +513,15 @@ You can find the code samples for Java and Kotlin here: | [`Android Kotlin Back4App`](./samples/android-kotlin-back4app) | | [`Android Kotlin Nexmo`](./samples/android-kotlin-nexmo) | -You can run any of them on an emulator to check out the example of how to initialize the SDK, register users and encrypt messages using the E3Kit. +You can run the demo to check out the example of how to initialize the SDK, register users and encrypt messages using E3Kit. ## License This library is released under the [3-clause BSD License](LICENSE.md). -## Docs -Virgil Security has a powerful set of APIs, and the documentation below can get you started today. - -* E3kit integrations with: - * [Custom platform][_any_platform] - * [Firebase][_firebase] - * [Twilio][_twilio] - * [Nexmo][_nexmo] - * [Pubnub][_pubnub] -* [Reference API][_reference_api] - ## Support Our developer support team is here to help you. Find out more information on our [Help Center](https://help.virgilsecurity.com/). You can find us on [Twitter](https://twitter.com/VirgilSecurity) or send us email support@VirgilSecurity.com. Also, get extra help from our support team on [Slack](https://virgilsecurity.com/join-community). - -[_any_platform]: https://developer.virgilsecurity.com/docs/use-cases/v5/encrypted-communication -[_twilio]: https://developer.virgilsecurity.com/docs/use-cases/v5/encrypted-communication-for-twilio -[_nexmo]: https://developer.virgilsecurity.com/docs/use-cases/v5/encrypted-communication-for-nexmo -[_firebase]: https://developer.virgilsecurity.com/docs/use-cases/v5/encrypted-communication-for-firebase -[_pubnub]: https://developer.virgilsecurity.com/docs/use-cases/v5/smart-door-lock -[_reference_api]: https://developer.virgilsecurity.com/docs/api-reference diff --git a/build.gradle b/build.gradle index f2ce464c..3b08cf38 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019, Virgil Security, Inc. + * Copyright (c) 2015-2020, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * @@ -32,36 +32,43 @@ */ buildscript { + ext.kotlin_version = '1.3.61' + ext.versions = [ // Virgil - virgilSdk : '6.0', - virgilCrypto: '0.10.2', - pythia : '0.3.2', + virgilSdk : '7.1.0', + virgilCrypto : '0.12.0', + pythia : '0.3.3', + ratchet : '0.1.1', // Kotlin - kotlin : '1.3.50', - coroutines : '1.3.0-M1', + kotlin : '1.3.61', + coroutines : '1.3.3', // Gradle - gradle : '3.5.0', + gradle : '3.5.3', // Maven - mavenPublish: '3.6.2', + mavenPublish : '3.6.2', // Android - android : '4.1.1.4', - appCompat : '28.0.0', + android : '4.1.1.4', + appCompat : '1.1.0', // Room - room : '2.2.0-rc01', + room : '2.2.3', // Docs - dokka : '0.9.18', + dokka : '0.9.18', // Tests - junit : '4.12', - testsRunner : '1.1.1', - espresso : '3.0.2', + junit : '4.12', + testsRunner : '1.1.1', + espresso : '3.0.2', + virgilTestCommon: '0.1', + + // Benchmark + androidBenchmark: '1.0.0', ] ext.androidOptions = [ compileSdkVersion : 28, @@ -80,6 +87,8 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" classpath "org.jetbrains.dokka:dokka-gradle-plugin:$versions.dokka" classpath "digital.wup:android-maven-publish:$versions.mavenPublish" + classpath "androidx.benchmark:benchmark-gradle-plugin:$versions.androidBenchmark" + } } @@ -91,6 +100,179 @@ allprojects { } } +/** + * Gets property from system properties. + * + * @param name Of System property to get. + * + * @return System property or null if not found. + */ +def static getSystemProperty(String name) { + def property + if (System.getProperty(name) != null) { + property = System.getProperty(name) + } else { + property = System.getenv(name) + } + + return property +} + +/** + * Gets property from both - gradle properties and system properties. + * + * @param name Of property to get. + */ +def getGradleOrSystemProperty(String name, Project project) { + def property + + if (project.hasProperty(name)) { + property = project.getProperty(name) + } else { + property = getSystemProperty(name) + } + + return property +} + +// Artifacts packages +final String BASE_VIRGIL_PACKAGE = 'com.virgilsecurity' + +// Packages versions +final String SDK_VERSION = '0.8.0' + +subprojects { + group BASE_VIRGIL_PACKAGE + version SDK_VERSION + + def isRegular = (it.name == 'ethree-common' + || it.name == 'ethree-kotlin' + || it.name == 'ethree-enclave') + def isTest = (it.name == 'tests' || it.name == 'testsenclave') + def isBenchmark = (it.name == 'ethree-benchmark') + + if (isRegular) { + apply plugin: 'com.android.library' + apply plugin: 'digital.wup.android-maven-publish' + apply plugin: 'org.jetbrains.dokka' + apply plugin: 'signing' + apply plugin: 'maven-publish' + } else if (isTest) { + apply plugin: 'com.android.application' + apply from: '../tests-verbal-output.gradle' + } else if (isBenchmark) { + apply plugin: 'com.android.library' + } + + apply plugin: 'kotlin-android' + apply plugin: 'kotlin-android-extensions' + + if (it.name == 'ethree-common') { + apply from: '../tests-verbal-output.gradle' + apply plugin: 'kotlin-kapt' + } + + android { + compileSdkVersion androidOptions.compileSdkVersion + buildToolsVersion androidOptions.buildToolsVersion + + defaultConfig { + targetSdkVersion androidOptions.targetSdkVersion + } + buildTypes { + debug { + minifyEnabled false + } + release { + minifyEnabled false + } + } + lintOptions { + abortOnError false + } + } + + sourceCompatibility = "8" + targetCompatibility = "8" + + if (isRegular) { + task sourcesJar(type: Jar) { + from(project.android.sourceSets.main.java.srcDirs) + classifier = 'sources' + } + + task javadocJar(type: Jar, dependsOn: 'dokka') { + from("$buildDir/javadoc") + classifier = 'javadoc' + } + + def authentication_username = getGradleOrSystemProperty('authentication_username', project) + def authentication_password = getGradleOrSystemProperty('authentication_password', project) + + publishing { + publications { + mavenJava(MavenPublication) { + artifact javadocJar + artifact sourcesJar + from components.android + + pom { + description = 'Virgil Security provides an SDK which symplifies work with Virgil services and presents easy to use API for adding security to any application. In a few simple steps you can setup user encryption with multidevice support.' + url = 'https://www.virgilsecurity.com/' + licenses { + license { + name = 'Virgil Security, Inc. license' + url = 'https://github.com/VirgilSecurity/virgil-e3kit-kotlin/blob/master/LICENSE.txt' + } + } + developers { + developer { + id = 'BuddahLD' + name = 'Danylo Oliinyk' + email = 'doliinyk@virgilsecurity.com' + organizationUrl = 'https://github.com/BuddahLD' + } + developer { + id = 'andrii-iakovenko' + name = 'Andrii Iakovenko' + email = 'andrii-iakovenko@gmail.com' + organizationUrl = 'https://github.com/andrii-iakovenko' + } + } + scm { + connection = 'scm:git:https://github.com/VirgilSecurity/virgil-e3kit-kotlin.git' + developerConnection = 'scm:git:git@github.com:VirgilSecurity/virgil-e3kit-kotlin.git' + url = 'https://github.com/VirgilSecurity/virgil-e3kit-kotlin' + } + } + } + } + + repositories { + maven { + def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots" + url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl + credentials { + username "${authentication_username}" + password "${authentication_password}" + } + } + } + } + + signing { + sign publishing.publications.mavenJava + } + + dokka { + outputFormat = 'html' + outputDirectory = "$buildDir/javadoc" + reportUndocumented = false + } + } +} + task clean(type: Delete) { delete rootProject.buildDir } diff --git a/creds.tar.enc b/creds.tar.enc new file mode 100644 index 00000000..77af1dc6 Binary files /dev/null and b/creds.tar.enc differ diff --git a/ethree-benchmark/benchmark-proguard-rules.pro b/ethree-benchmark/benchmark-proguard-rules.pro new file mode 100644 index 00000000..357b0655 --- /dev/null +++ b/ethree-benchmark/benchmark-proguard-rules.pro @@ -0,0 +1,37 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +-dontobfuscate + +-ignorewarnings + +-keepattributes *Annotation* + +-dontnote junit.framework.** +-dontnote junit.runner.** + +-dontwarn androidx.test.** +-dontwarn org.junit.** +-dontwarn org.hamcrest.** +-dontwarn com.squareup.javawriter.JavaWriter + +-keepclasseswithmembers @org.junit.runner.RunWith public class * diff --git a/ethree-benchmark/build.gradle b/ethree-benchmark/build.gradle new file mode 100644 index 00000000..8f903f76 --- /dev/null +++ b/ethree-benchmark/build.gradle @@ -0,0 +1,82 @@ +/* + * 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. + */ + +apply plugin: 'androidx.benchmark' + +android { + defaultConfig { + minSdkVersion androidOptions.minSdkVersionRegular + testInstrumentationRunner 'androidx.benchmark.junit4.AndroidBenchmarkRunner' + } + + buildTypes { + debug { + // Since debuggable can't be modified by gradle for library modules, + // it must be done in a manifest - see src/androidTest/AndroidManifest.xml + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'benchmark-proguard-rules.pro' + } + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin" + + implementation project(":ethree-kotlin") + + // Virgil test common + implementation "com.virgilsecurity:test-common:$versions.virgilTestCommon" + + androidTestImplementation "androidx.test:runner:$versions.testsRunner" + androidTestImplementation "androidx.test.ext:junit:$versions.testsRunner" + androidTestImplementation "junit:junit:$versions.junit" + androidTestImplementation "androidx.benchmark:benchmark-junit4:$versions.androidBenchmark" +} + +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + kotlinOptions { + jvmTarget = "1.8" + } +} + +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + dependsOn(copyEnvFile) +} + +task copyEnvFile(type: Copy) { + def assetsFolder = new File(project.projectDir.absolutePath + '/src/androidTest/resources/testProperties') + if (!assetsFolder.exists()) assetsFolder.mkdirs() + + from rootProject.projectDir.absolutePath + '/env.json' + into assetsFolder.absolutePath +} diff --git a/ethree-benchmark/src/androidTest/AndroidManifest.xml b/ethree-benchmark/src/androidTest/AndroidManifest.xml new file mode 100644 index 00000000..4c023161 --- /dev/null +++ b/ethree-benchmark/src/androidTest/AndroidManifest.xml @@ -0,0 +1,49 @@ + + + + + + + + diff --git a/ethree-benchmark/src/androidTest/java/com/virgilsecurity/android/ethree_benchmark/EThreeBenchmark.kt b/ethree-benchmark/src/androidTest/java/com/virgilsecurity/android/ethree_benchmark/EThreeBenchmark.kt new file mode 100644 index 00000000..1958cb0b --- /dev/null +++ b/ethree-benchmark/src/androidTest/java/com/virgilsecurity/android/ethree_benchmark/EThreeBenchmark.kt @@ -0,0 +1,164 @@ +/* + * 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_benchmark + +import android.util.Log +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +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.sdk.crypto.KeyPairType +import com.virgilsecurity.sdk.crypto.VirgilKeyPair +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import java.util.* + +@LargeTest +@RunWith(Parameterized::class) +class EThreeBenchmark( + private val keyType: KeyPairType +) { + + @get:Rule + val benchmarkRule = BenchmarkRule() + + private fun setupDevice(keyPair: VirgilKeyPair? = null): EThree { + val identity = UUID.randomUUID().toString() + + val tokenCallback = object : OnGetTokenCallback { + override fun onGetToken(): String { + return TestUtils.generateTokenString(identity) + } + } + + val ethree = EThree(identity, tokenCallback, TestConfig.context) + + ethree.register(keyPair).execute() + + return ethree + } + + // test01 + @Ignore("Run only on a purpose on a real device (takes a lot of time ~1h+)") + @Test + fun findUser_encrypt() { + println("Testing $keyType") + + val aliceKeyPair = TestConfig.virgilCrypto.generateKeyPair(keyType) + val bobKeyPair = TestConfig.virgilCrypto.generateKeyPair(keyType) + + val alice = setupDevice(aliceKeyPair) + val bob = setupDevice(bobKeyPair) + + alice.findUser(bob.identity).get() + + benchmarkRule.measureRepeated { + val bobCard = alice.findCachedUser(bob.identity).get()!! + + alice.authEncrypt(TEXT, bobCard) + } + } + + // test02 + @Ignore("Run only on a purpose on a real device (takes a lot of time ~1h+)") + @Test + fun findUser_decrypt() { + val aliceKeyPair = TestConfig.virgilCrypto.generateKeyPair(keyType) + val bobKeyPair = TestConfig.virgilCrypto.generateKeyPair(keyType) + + val alice = setupDevice(aliceKeyPair) + val bob = setupDevice(bobKeyPair) + + val bobCard = alice.findUser(bob.identity).get() + val encrypted = alice.authEncrypt(TEXT, bobCard) + + bob.findUser(alice.identity).get() + + benchmarkRule.measureRepeated { + val aliceCard = bob.findCachedUser(alice.identity).get()!! + + bob.authDecrypt(encrypted, aliceCard) + } + } + + // test03 + @Ignore("Run only on a purpose on a real device (takes a lot of time ~1h+)") + @Test + fun group_update() { + val ethree1 = setupDevice() + val ethree2 = setupDevice() + val ethree3 = setupDevice() + + val identifier = UUID.randomUUID().toString() + + val result = ethree1.findUsers(listOf(ethree2.identity, ethree3.identity)).get() + val group1 = ethree1.createGroup(identifier, result).get() + + val card1 = ethree2.findUser(ethree1.identity).get() + val card3 = ethree1.findUser(ethree3.identity).get() + + val group2 = ethree2.loadGroup(identifier, card1).get() + + val state = benchmarkRule.getState() + + while (state.keepRunning()) { + state.pauseTiming() + + group1.remove(card3).execute() + group1.add(card3).execute() + + state.resumeTiming() + + group2.update().execute() + } + } + + companion object { + private const val TEXT = "Hello, my name is text. I am here to be encrypted (:" + + @JvmStatic + @Parameterized.Parameters + fun data(): Collection> = arrayListOf( + arrayOf(KeyPairType.ED25519), + arrayOf(KeyPairType.SECP256R1) + ) + } +} diff --git a/ethree-benchmark/src/androidTest/java/com/virgilsecurity/android/ethree_benchmark/utils/TestConfig.kt b/ethree-benchmark/src/androidTest/java/com/virgilsecurity/android/ethree_benchmark/utils/TestConfig.kt new file mode 100644 index 00000000..8e3cce28 --- /dev/null +++ b/ethree-benchmark/src/androidTest/java/com/virgilsecurity/android/ethree_benchmark/utils/TestConfig.kt @@ -0,0 +1,100 @@ +/* + * 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.common.utils + +import android.content.Context +import androidx.test.platform.app.InstrumentationRegistry +import com.virgilsecurity.android.common.BuildConfig +import com.virgilsecurity.sdk.crypto.VirgilCrypto +import com.virgilsecurity.sdk.crypto.VirgilPrivateKey +import com.virgilsecurity.sdk.utils.ConvertionUtils +import com.virgilsecurity.testcommon.property.EnvPropertyReader +import com.virgilsecurity.testcommon.utils.PropertyUtils +import java.io.File +import java.io.FileOutputStream + +class TestConfig { + companion object { + private const val APP_ID = "APP_ID" + private const val APP_PRIVATE_KEY = "APP_PRIVATE_KEY" + private const val APP_PUBLIC_KEY_ID = "APP_PUBLIC_KEY_ID" + private const val VIRGIL_SERVICE_ADDRESS = "VIRGIL_SERVICE_ADDRESS" + + private const val ENVIRONMENT_PARAMETER = "environment" + + private val propertyReader: EnvPropertyReader by lazy { + val environment = PropertyUtils.getSystemProperty(ENVIRONMENT_PARAMETER) + + val resourceEnvStream = + this.javaClass.classLoader.getResourceAsStream("testProperties/env.json") + val tempEnvDirectory = File(context.filesDir, "tempEnvDir") + tempEnvDirectory.mkdirs() + + val tempEnvFile = File(tempEnvDirectory, "env.json") + + val outputStream = FileOutputStream(tempEnvFile) + outputStream.write(resourceEnvStream.readBytes()) + outputStream.close() + + if (environment != null) + EnvPropertyReader.Builder() + .environment(EnvPropertyReader.Environment.fromType(environment)) + .filePath(tempEnvFile.parent) + .build() + else + EnvPropertyReader.Builder() + .filePath(tempEnvFile.parent) + .build() + } + + val virgilCrypto = VirgilCrypto(false) + val appId: String by lazy { propertyReader.getProperty(APP_ID) } + val appKey: VirgilPrivateKey by lazy { + with(propertyReader.getProperty(APP_PRIVATE_KEY)) { + virgilCrypto.importPrivateKey(com.virgilsecurity.crypto.foundation.Base64.decode(this.toByteArray())).privateKey + } + } + val appPublicKeyId: String by lazy { propertyReader.getProperty(APP_PUBLIC_KEY_ID) } + val virgilServiceAddress: String by lazy { + propertyReader.getProperty(VIRGIL_SERVICE_ADDRESS) + } + + const val VIRGIL_CARDS_SERVICE_PATH = "/card/v5/" + + val context: Context = InstrumentationRegistry.getInstrumentation().targetContext + val DIRECTORY_PATH: String = InstrumentationRegistry.getInstrumentation() + .targetContext.filesDir.absolutePath + const val KEYSTORE_NAME = "virgil.keystore" + } +} diff --git a/ethree-benchmark/src/androidTest/java/com/virgilsecurity/android/ethree_benchmark/utils/TestUtils.kt b/ethree-benchmark/src/androidTest/java/com/virgilsecurity/android/ethree_benchmark/utils/TestUtils.kt new file mode 100644 index 00000000..f8b60e08 --- /dev/null +++ b/ethree-benchmark/src/androidTest/java/com/virgilsecurity/android/ethree_benchmark/utils/TestUtils.kt @@ -0,0 +1,97 @@ +/* + * 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.common.utils + +import com.virgilsecurity.android.common.utils.TestConfig.Companion.virgilCrypto +import com.virgilsecurity.sdk.cards.Card +import com.virgilsecurity.sdk.cards.ModelSigner +import com.virgilsecurity.sdk.cards.model.RawCardContent +import com.virgilsecurity.sdk.cards.model.RawSignedModel +import com.virgilsecurity.sdk.client.VirgilCardClient +import com.virgilsecurity.sdk.common.TimeSpan +import com.virgilsecurity.sdk.crypto.VirgilAccessTokenSigner +import com.virgilsecurity.sdk.crypto.VirgilCardCrypto +import com.virgilsecurity.sdk.jwt.Jwt +import com.virgilsecurity.sdk.jwt.JwtGenerator +import com.virgilsecurity.sdk.jwt.accessProviders.ConstAccessTokenProvider +import com.virgilsecurity.sdk.utils.ConvertionUtils +import java.util.* +import java.util.concurrent.TimeUnit + +class TestUtils { + + companion object { + fun generateTokenString(identity: String): String = + JwtGenerator( + TestConfig.appId, + TestConfig.appKey, + TestConfig.appPublicKeyId, + TimeSpan.fromTime(600, TimeUnit.SECONDS), + VirgilAccessTokenSigner(virgilCrypto) + ).generateToken(identity).stringRepresentation() + + fun generateToken(identity: String): Jwt = + JwtGenerator( + TestConfig.appId, + TestConfig.appKey, + TestConfig.appPublicKeyId, + TimeSpan.fromTime(600, TimeUnit.SECONDS), + VirgilAccessTokenSigner(virgilCrypto) + ).generateToken(identity) + + fun publishCard(identity: String? = null, previousCardId: String? = null): Card { + val keyPair = virgilCrypto.generateKeyPair() + val exportedPublicKey = virgilCrypto.exportPublicKey(keyPair.publicKey) + val identityNew = identity ?: UUID.randomUUID().toString() + val content = RawCardContent(identityNew, + ConvertionUtils.toBase64String(exportedPublicKey), + "5.0", + Date(), + previousCardId) + val snapshot = content.snapshot() + val rawCard = RawSignedModel(snapshot) + val token = generateToken(identityNew) + val provider = ConstAccessTokenProvider(token) + val signer = ModelSigner(VirgilCardCrypto(virgilCrypto)) + signer.selfSign(rawCard, keyPair.privateKey) + val cardClient = VirgilCardClient(TestConfig.virgilServiceAddress + TestConfig.VIRGIL_CARDS_SERVICE_PATH) + + val responseRawCard = + cardClient.publishCard(rawCard, + provider.getToken(null).stringRepresentation()) + + return Card.parse(VirgilCardCrypto(virgilCrypto), responseRawCard) + } + } +} diff --git a/ethree-benchmark/src/main/AndroidManifest.xml b/ethree-benchmark/src/main/AndroidManifest.xml new file mode 100644 index 00000000..68d952e7 --- /dev/null +++ b/ethree-benchmark/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + diff --git a/ethree-common/build.gradle b/ethree-common/build.gradle index c45fa658..067a3f5e 100644 --- a/ethree-common/build.gradle +++ b/ethree-common/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019, Virgil Security, Inc. + * Copyright (c) 2015-2020, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * @@ -31,35 +31,9 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -plugins { - id 'signing' - id 'maven-publish' -} - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'kotlin-kapt' - -apply plugin: 'digital.wup.android-maven-publish' -apply plugin: 'org.jetbrains.dokka' - -group 'com.virgilsecurity' -version '0.5.1-beta1' - -def APP_ID = hasProperty('APP_ID') ? APP_ID : System.getenv('APP_ID') -def API_PRIVATE_KEY = hasProperty('API_PRIVATE_KEY') ? API_PRIVATE_KEY : System.getenv('API_PRIVATE_KEY') -def API_PUBLIC_KEY = hasProperty('API_PUBLIC_KEY') ? API_PUBLIC_KEY : System.getenv('API_PUBLIC_KEY') -def API_PUBLIC_KEY_ID = hasProperty('API_PUBLIC_KEY_ID') ? API_PUBLIC_KEY_ID : System.getenv('API_PUBLIC_KEY_ID') -def VIRGIL_BASE_URL = hasProperty('VIRGIL_BASE_URL') ? VIRGIL_BASE_URL : System.getenv('VIRGIL_BASE_URL') - android { - compileSdkVersion androidOptions.compileSdkVersion - buildToolsVersion androidOptions.buildToolsVersion - defaultConfig { minSdkVersion androidOptions.minSdkVersionRegular - targetSdkVersion androidOptions.targetSdkVersion consumerProguardFiles 'proguard-rules.txt' javaCompileOptions { @@ -73,47 +47,23 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - buildTypes { - debug { - minifyEnabled false - useProguard false - } - release { - minifyEnabled false - useProguard false - } - buildTypes.each { - it.buildConfigField "String", "APP_ID", "$APP_ID" - it.buildConfigField "String", "API_PRIVATE_KEY", "$API_PRIVATE_KEY" - it.buildConfigField "String", "API_PUBLIC_KEY", "$API_PUBLIC_KEY" - it.buildConfigField "String", "API_PUBLIC_KEY_ID", "$API_PUBLIC_KEY_ID" - it.buildConfigField "String", "VIRGIL_BASE_URL", "$VIRGIL_BASE_URL" - } - } } dependencies { // Kotlin compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlin" - compileOnly "org.jetbrains.kotlinx:kotlinx-coroutines-core:$versions.coroutines" - - // Virgil Crypto - implementation "com.virgilsecurity.crypto:pythia-android:$versions.virgilCrypto" + api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$versions.coroutines" // Virgil SDK + api "com.virgilsecurity.sdk:sdk-android:$versions.virgilSdk" api "com.virgilsecurity.sdk:crypto-android:$versions.virgilSdk" - api "com.virgilsecurity:common:$versions.virgilSdk" - api("com.virgilsecurity.sdk:sdk:$versions.virgilSdk") { - exclude group: 'com.virgilsecurity.crypto' - } - api("com.virgilsecurity.sdk:keyknox:$versions.virgilSdk") { - exclude group: 'com.virgilsecurity.sdk', module: "sdk" - } - api("com.virgilsecurity:pythia:$versions.pythia") { - exclude group: 'com.virgilsecurity.crypto' - exclude group: 'com.virgilsecurity.sdk' - } + // Virgil Pythia + api "com.virgilsecurity:pythia-android:$versions.pythia" + api "com.virgilsecurity.crypto:pythia-android:$versions.virgilCrypto" + + // Virgil Ratchet + api "com.virgilsecurity:ratchet-android:$versions.ratchet" // Android compileOnly "com.google.android:android:$versions.android" @@ -123,6 +73,9 @@ dependencies { implementation "androidx.room:room-ktx:$versions.room" kapt "androidx.room:room-compiler:$versions.room" + // Gson + implementation "com.google.code.gson:gson:$versions.gson" + // Tests core testImplementation "junit:junit:$versions.junit" androidTestImplementation "androidx.test.ext:junit:$versions.testsRunner" @@ -130,18 +83,21 @@ dependencies { // Test internal androidTestImplementation project(':ethree-kotlin') + + // Virgil test common + implementation "com.virgilsecurity:test-common:$versions.virgilTestCommon" } -task generateVersionVirgilAgent { +task generateVirgilInfo { outputs.dir "$buildDir/generated" doFirst { - def versionFile = file("$buildDir/generated/release/com/virgilsecurity/android/common/build/VersionVirgilAgent.kt") + def versionFile = file("$buildDir/generated/release/com/virgilsecurity/android/common/build/VirgilInfo.kt") versionFile.parentFile.mkdirs() versionFile.text = """ package com.virgilsecurity.android.common.build; -object VersionVirgilAgent { +object VirgilInfo { const val VERSION = "$project.version" } """ @@ -151,78 +107,34 @@ object VersionVirgilAgent { project.android.sourceSets.main.java.srcDirs = ["${buildDir}/generated/release/", "src/main/java"] tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { - dependsOn(generateVersionVirgilAgent) -} - -sourceCompatibility = "8" -targetCompatibility = "8" - -task sourcesJar(type: Jar) { - from(project.android.sourceSets.main.java.srcDirs) - classifier = 'sources' -} - -task javadocJar(type: Jar, dependsOn: 'dokka') { - from("$buildDir/javadoc") - classifier = 'javadoc' + dependsOn(generateVirgilInfo) + dependsOn(copyEnvFile) + dependsOn(copyCompatDataFile) } -def authentication_username = hasProperty('authentication_username') ? authentication_username : System.getenv('authentication_username') -def authentication_password = hasProperty('authentication_password') ? authentication_password : System.getenv('authentication_password') - publishing { publications { mavenJava(MavenPublication) { artifactId = 'ethree-common' - from components.android - artifact sourcesJar - artifact javadocJar pom { name = 'Virgil E3Kit Kotlin Common' - description = 'Virgil Security provides an SDK which symplifies work with Virgil services and presents easy to use API for adding security to any application. In a few simple steps you can setup user encryption with multidevice support.' - url = 'https://www.virgilsecurity.com/' - licenses { - license { - name = 'Virgil Security, Inc. license' - url = 'https://github.com/VirgilSecurity/virgil-e3kit-kotlin/blob/master/LICENSE.txt' - } - } - developers { - developer { - id = 'BuddahLD' - name = 'Danylo Oliinyk' - email = 'doliinyk@virgilsecurity.com' - organizationUrl = 'https://github.com/BuddahLD' - } - } - scm { - connection = 'scm:git:https://github.com/VirgilSecurity/virgil-e3kit-kotlin.git' - developerConnection = 'scm:git:git@github.com:VirgilSecurity/virgil-e3kit-kotlin.git' - url = 'https://github.com/VirgilSecurity/virgil-e3kit-kotlin' - } - } - } - } - - repositories { - maven { - def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" - def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots" - url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl - credentials { - username "${authentication_username}" - password "${authentication_password}" } } } } -signing { - sign publishing.publications.mavenJava +task copyEnvFile(type: Copy) { + def resourcesFolder = new File(project.projectDir.absolutePath + '/src/androidTest/resources/testProperties') + if (!resourcesFolder.exists()) resourcesFolder.mkdirs() + + from rootProject.projectDir.absolutePath + '/env.json' + into resourcesFolder.absolutePath } -dokka { - outputFormat = 'html' - outputDirectory = "$buildDir/javadoc" - reportUndocumented = false +task copyCompatDataFile(type: Copy) { + def resourcesFolder = new File(project.projectDir.absolutePath + '/src/androidTest/resources/compat') + if (!resourcesFolder.exists()) resourcesFolder.mkdirs() + + from rootProject.projectDir.absolutePath + '/compat_data.json' + into resourcesFolder.absolutePath } diff --git a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/model/TicketTest.kt b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/model/TicketTest.kt index 36cb1e72..298bcf14 100644 --- a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/model/TicketTest.kt +++ b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/model/TicketTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019, Virgil Security, Inc. + * Copyright (c) 2015-2020, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * @@ -35,7 +35,8 @@ package com.virgilsecurity.android.common.model import android.os.Parcel import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.virgilsecurity.android.common.exception.GroupIdTooShortException +import com.virgilsecurity.android.common.exception.GroupException +import com.virgilsecurity.common.extension.toData import com.virgilsecurity.common.model.Data import com.virgilsecurity.sdk.crypto.HashAlgorithm import com.virgilsecurity.sdk.crypto.VirgilCrypto @@ -55,7 +56,7 @@ class TicketTest { @Test fun ticket_parcelable_with_serializable_participants() { val crypto = VirgilCrypto() - val identifierData = Data(UUID.randomUUID().toString().toByteArray()) + val identifierData = UUID.randomUUID().toString().toData() val sessionId = computeSessionId(identifierData, crypto) val participantsSet = setOf("Bob", "Alice", "Jane") val ticket = Ticket(crypto, sessionId, participantsSet) @@ -75,7 +76,7 @@ class TicketTest { @Test(expected = IllegalArgumentException::class) fun ticket_parcelable_with_not_serializable_participants() { val crypto = VirgilCrypto() - val identifierData = Data(UUID.randomUUID().toString().toByteArray()) + val identifierData = UUID.randomUUID().toString().toData() val sessionId = computeSessionId(identifierData, crypto) val participantsSet = NotSerializableSet("Bob", "Alice", "Jane") @@ -85,14 +86,13 @@ class TicketTest { } private fun computeSessionId(identifier: Data, crypto: VirgilCrypto): Data { - if (identifier.data.size <= 10) { - throw GroupIdTooShortException("Group Id length should be > 10") - } + if (identifier.value.size <= 10) + throw GroupException(GroupException.Description.SHORT_GROUP_ID) - val hash = crypto.computeHash(identifier.data, HashAlgorithm.SHA512) - .sliceArray(IntRange(0, 31)) + val hash = crypto.computeHash(identifier.value, HashAlgorithm.SHA512) + .sliceArray(IntRange(0, 31)).toData() - return Data(hash) + return hash } private class NotSerializableSet(vararg values: String) : Set { diff --git a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/storage/sql/SQLCardStorageTest.kt b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/storage/sql/SQLCardStorageTest.kt index f754c9a7..f4ef973b 100644 --- a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/storage/sql/SQLCardStorageTest.kt +++ b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/storage/sql/SQLCardStorageTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019, Virgil Security, Inc. + * Copyright (c) 2015-2020, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * diff --git a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/utils/TestConfig.kt b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/utils/TestConfig.kt index ab13f52f..8e3cce28 100644 --- a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/utils/TestConfig.kt +++ b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/utils/TestConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019, Virgil Security, Inc. + * Copyright (c) 2015-2020, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * @@ -33,28 +33,68 @@ package com.virgilsecurity.android.common.utils +import android.content.Context import androidx.test.platform.app.InstrumentationRegistry import com.virgilsecurity.android.common.BuildConfig import com.virgilsecurity.sdk.crypto.VirgilCrypto import com.virgilsecurity.sdk.crypto.VirgilPrivateKey import com.virgilsecurity.sdk.utils.ConvertionUtils +import com.virgilsecurity.testcommon.property.EnvPropertyReader +import com.virgilsecurity.testcommon.utils.PropertyUtils +import java.io.File +import java.io.FileOutputStream class TestConfig { companion object { + private const val APP_ID = "APP_ID" + private const val APP_PRIVATE_KEY = "APP_PRIVATE_KEY" + private const val APP_PUBLIC_KEY_ID = "APP_PUBLIC_KEY_ID" + private const val VIRGIL_SERVICE_ADDRESS = "VIRGIL_SERVICE_ADDRESS" + + private const val ENVIRONMENT_PARAMETER = "environment" + + private val propertyReader: EnvPropertyReader by lazy { + val environment = PropertyUtils.getSystemProperty(ENVIRONMENT_PARAMETER) + + val resourceEnvStream = + this.javaClass.classLoader.getResourceAsStream("testProperties/env.json") + val tempEnvDirectory = File(context.filesDir, "tempEnvDir") + tempEnvDirectory.mkdirs() + + val tempEnvFile = File(tempEnvDirectory, "env.json") + + val outputStream = FileOutputStream(tempEnvFile) + outputStream.write(resourceEnvStream.readBytes()) + outputStream.close() + + if (environment != null) + EnvPropertyReader.Builder() + .environment(EnvPropertyReader.Environment.fromType(environment)) + .filePath(tempEnvFile.parent) + .build() + else + EnvPropertyReader.Builder() + .filePath(tempEnvFile.parent) + .build() + } + val virgilCrypto = VirgilCrypto(false) - val appId = BuildConfig.APP_ID - val apiKey: VirgilPrivateKey by lazy { - virgilCrypto.importPrivateKey(ConvertionUtils.base64ToBytes(BuildConfig.API_PRIVATE_KEY)) - .privateKey + val appId: String by lazy { propertyReader.getProperty(APP_ID) } + val appKey: VirgilPrivateKey by lazy { + with(propertyReader.getProperty(APP_PRIVATE_KEY)) { + virgilCrypto.importPrivateKey(com.virgilsecurity.crypto.foundation.Base64.decode(this.toByteArray())).privateKey + } + } + val appPublicKeyId: String by lazy { propertyReader.getProperty(APP_PUBLIC_KEY_ID) } + val virgilServiceAddress: String by lazy { + propertyReader.getProperty(VIRGIL_SERVICE_ADDRESS) } - val apiPublicKeyId = BuildConfig.API_PUBLIC_KEY_ID - val virgilBaseUrl = BuildConfig.VIRGIL_BASE_URL const val VIRGIL_CARDS_SERVICE_PATH = "/card/v5/" - val context = InstrumentationRegistry.getInstrumentation().targetContext - val DIRECTORY_PATH = InstrumentationRegistry.getInstrumentation() + val context: Context = InstrumentationRegistry.getInstrumentation().targetContext + val DIRECTORY_PATH: String = InstrumentationRegistry.getInstrumentation() .targetContext.filesDir.absolutePath - val KEYSTORE_NAME = "virgil.keystore" + const val KEYSTORE_NAME = "virgil.keystore" } } diff --git a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/utils/TestUtils.kt b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/utils/TestUtils.kt index a00d0bad..f8b60e08 100644 --- a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/utils/TestUtils.kt +++ b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/utils/TestUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019, Virgil Security, Inc. + * Copyright (c) 2015-2020, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * @@ -52,17 +52,11 @@ import java.util.concurrent.TimeUnit class TestUtils { companion object { - const val THROTTLE_TIMEOUT = 2 * 1000L // 2 seconds - - fun pause(timeout: Long = THROTTLE_TIMEOUT) { - Thread.sleep(timeout) - } - fun generateTokenString(identity: String): String = JwtGenerator( TestConfig.appId, - TestConfig.apiKey, - TestConfig.apiPublicKeyId, + TestConfig.appKey, + TestConfig.appPublicKeyId, TimeSpan.fromTime(600, TimeUnit.SECONDS), VirgilAccessTokenSigner(virgilCrypto) ).generateToken(identity).stringRepresentation() @@ -70,8 +64,8 @@ class TestUtils { fun generateToken(identity: String): Jwt = JwtGenerator( TestConfig.appId, - TestConfig.apiKey, - TestConfig.apiPublicKeyId, + TestConfig.appKey, + TestConfig.appPublicKeyId, TimeSpan.fromTime(600, TimeUnit.SECONDS), VirgilAccessTokenSigner(virgilCrypto) ).generateToken(identity) @@ -91,7 +85,7 @@ class TestUtils { val provider = ConstAccessTokenProvider(token) val signer = ModelSigner(VirgilCardCrypto(virgilCrypto)) signer.selfSign(rawCard, keyPair.privateKey) - val cardClient = VirgilCardClient(TestConfig.virgilBaseUrl + TestConfig.VIRGIL_CARDS_SERVICE_PATH) + val cardClient = VirgilCardClient(TestConfig.virgilServiceAddress + TestConfig.VIRGIL_CARDS_SERVICE_PATH) val responseRawCard = cardClient.publishCard(rawCard, diff --git a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/AuthenticationTests.kt b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/AuthenticationTests.kt index 3ce1912a..ebce0405 100644 --- a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/AuthenticationTests.kt +++ b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/AuthenticationTests.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019, Virgil Security, Inc. + * Copyright (c) 2015-2020, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * @@ -68,11 +68,7 @@ class AuthenticationTests { this.crypto = VirgilCrypto() this.keyStorage = DefaultKeyStorage(TestConfig.DIRECTORY_PATH, TestConfig.KEYSTORE_NAME) this.ethree = EThree(identity, - object : OnGetTokenCallback { - override fun onGetToken(): String { - return TestUtils.generateTokenString(identity) - } - }, + { TestUtils.generateTokenString(identity) }, TestConfig.context) assertNotNull(this.ethree) @@ -113,9 +109,9 @@ class AuthenticationTests { try { ethree.register().execute() - } catch (throwable: Throwable) { - if (throwable !is EThreeException) - fail() + } catch (exception: EThreeException) { + assertTrue(exception.description + == EThreeException.Description.USER_IS_ALREADY_REGISTERED) } } @@ -128,15 +124,19 @@ class AuthenticationTests { try { ethree.register().execute() - } catch (throwable: Throwable) { - if (throwable !is EThreeException) - fail() + fail() + } catch (exception: EThreeException) { + assertTrue(exception.description == EThreeException.Description.PRIVATE_KEY_EXISTS) } } // test05 STE_12 - @Test(expected = EThreeException::class) fun rotate_without_published_card() { - ethree.rotatePrivateKey().execute() + @Test fun rotate_without_published_card() { + try { + ethree.rotatePrivateKey().execute() + } catch (exception: EThreeException) { + assertTrue(exception.description == EThreeException.Description.USER_IS_NOT_REGISTERED) + } } // test06 STE_13 @@ -146,9 +146,9 @@ class AuthenticationTests { try { ethree.rotatePrivateKey().execute() - } catch (throwable: Throwable) { - if (throwable !is EThreeException) - fail() + fail() + } catch (exception: EThreeException) { + assertTrue(exception.description == EThreeException.Description.PRIVATE_KEY_EXISTS) } } @@ -182,8 +182,9 @@ class AuthenticationTests { try { ethree.unregister().execute() } catch (throwable: Throwable) { - if (throwable !is EThreeException) - fail() + if (throwable is EThreeException) + assertTrue(throwable.description + == EThreeException.Description.USER_IS_NOT_REGISTERED) } ethree.register().execute() diff --git a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/BackupTests.kt b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/BackupTests.kt index cc52ad62..ae5174cc 100644 --- a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/BackupTests.kt +++ b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/BackupTests.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019, Virgil Security, Inc. + * Copyright (c) 2015-2020, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * @@ -36,8 +36,6 @@ package com.virgilsecurity.android.common.worker import androidx.test.ext.junit.runners.AndroidJUnit4 import com.virgilsecurity.android.common.callback.OnGetTokenCallback import com.virgilsecurity.android.common.exception.EThreeException -import com.virgilsecurity.android.common.exception.PrivateKeyNotFoundException -import com.virgilsecurity.android.common.exception.WrongPasswordException import com.virgilsecurity.android.common.utils.TestConfig import com.virgilsecurity.android.common.utils.TestUtils import com.virgilsecurity.android.ethree.interaction.EThree @@ -95,7 +93,7 @@ class BackupTests { }) val brainKeyContext = BrainKeyContext.Builder() .setAccessTokenProvider(tokenProvider) - .setPythiaClient(VirgilPythiaClient(TestConfig.virgilBaseUrl)) + .setPythiaClient(VirgilPythiaClient(TestConfig.virgilServiceAddress)) .setPythiaCrypto(VirgilPythiaCrypto()) .build() val keyPair = BrainKey(brainKeyContext).generateKeyPair(passwordBrainKey) @@ -105,7 +103,7 @@ class BackupTests { keyStorage, CloudKeyStorage( KeyknoxManager( - KeyknoxClient(tokenProvider, URL(TestConfig.virgilBaseUrl)), + KeyknoxClient(tokenProvider, URL(TestConfig.virgilServiceAddress)), KeyknoxCrypto() ), listOf(keyPair.publicKey), @@ -122,13 +120,11 @@ class BackupTests { @Test fun backup_private_key() { try { ethree.backupPrivateKey(password).execute() - } catch (throwable: Throwable) { - if (throwable !is EThreeException) - fail() + fail() + } catch (exception: EThreeException) { + assertTrue(exception.description == EThreeException.Description.MISSING_PRIVATE_KEY) } - TestUtils.pause() - val keyPair = TestConfig.virgilCrypto.generateKeyPair() val data = TestConfig.virgilCrypto.exportPrivateKey(keyPair.privateKey) @@ -136,16 +132,12 @@ class BackupTests { ethree.backupPrivateKey(password).execute() - TestUtils.pause() - val syncKeyStorage = initSyncKeyStorage(ethree.identity, password) val syncEntry = syncKeyStorage.retrieve(ethree.identity) assertNotNull(syncEntry) assertArrayEquals(data, syncEntry.value) - TestUtils.pause() - try { ethree.backupPrivateKey(password).execute() fail() @@ -164,26 +156,20 @@ class BackupTests { val syncKeyStorage = initSyncKeyStorage(ethree.identity, password) syncKeyStorage.store(ethree.identity, data) - TestUtils.pause() - try { ethree.restorePrivateKey(WRONG_PASSWORD).execute() - } catch (throwable: Throwable) { - if (throwable !is WrongPasswordException) + } catch (exception: EThreeException) { + if (exception.description != EThreeException.Description.WRONG_PASSWORD) fail() } - TestUtils.pause() - ethree.restorePrivateKey(password).execute() val retrievedEntry = keyStorage.load(ethree.identity) assertNotNull(retrievedEntry) assertArrayEquals(data, retrievedEntry.value) - TestUtils.pause() - try { ethree.restorePrivateKey(password).execute() fail() @@ -202,23 +188,17 @@ class BackupTests { val syncKeyStorage = initSyncKeyStorage(ethree.identity, password) syncKeyStorage.store(ethree.identity, data) - TestUtils.pause() - val passwordNew = UUID.randomUUID().toString() ethree.changePassword(password, passwordNew).execute() - TestUtils.pause() - try { ethree.restorePrivateKey(password).execute() - } catch (throwable: Throwable) { - if (throwable !is WrongPasswordException) + } catch (exception: EThreeException) { + if (exception.description != EThreeException.Description.WRONG_PASSWORD) fail() } - TestUtils.pause() - ethree.restorePrivateKey(passwordNew).execute() val retrievedEntry = keyStorage.load(ethree.identity) @@ -230,21 +210,17 @@ class BackupTests { @Test fun reset_private_key_backup() { try { ethree.resetPrivateKeyBackup(password).execute() - } catch (throwable: Throwable) { - if (throwable !is PrivateKeyNotFoundException) - fail() + fail() + } catch (exception: EThreeException) { + assertTrue(exception.description == EThreeException.Description.MISSING_PRIVATE_KEY) } val keyPair = TestConfig.virgilCrypto.generateKeyPair() val data = TestConfig.virgilCrypto.exportPrivateKey(keyPair.privateKey) - TestUtils.pause() - val syncKeyStorage = initSyncKeyStorage(ethree.identity, password) syncKeyStorage.store(ethree.identity, data) - TestUtils.pause() - ethree.resetPrivateKeyBackup(password).execute() syncKeyStorage.sync() @@ -259,23 +235,12 @@ class BackupTests { // test05 STE_19 @Test fun reset_private_key_backup_no_password() { - try { - ethree.resetPrivateKeyBackup(password).execute() - } catch (throwable: Throwable) { - if (throwable !is PrivateKeyNotFoundException) - fail() - } - val keyPair = TestConfig.virgilCrypto.generateKeyPair() val data = TestConfig.virgilCrypto.exportPrivateKey(keyPair.privateKey) - TestUtils.pause() - val syncKeyStorage = initSyncKeyStorage(ethree.identity, password) syncKeyStorage.store(ethree.identity, data) - TestUtils.pause() - ethree.resetPrivateKeyBackup().execute() syncKeyStorage.sync() @@ -288,6 +253,14 @@ class BackupTests { } } + // test06 STE_70 + @Test fun derive_passwords() { + val derived = EThree.derivePasswords(this.password) + assertNotNull(derived) + + assertNotEquals(derived.backupPassword, derived.loginPassword) + } + companion object { private const val WRONG_PASSWORD = "WRONG_PASSWORD" } 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 953366f0..e88647f6 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019, Virgil Security, Inc. + * Copyright (c) 2015-2020, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * @@ -36,7 +36,7 @@ package com.virgilsecurity.android.common.worker import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.gson.JsonObject import com.google.gson.JsonParser -import com.virgilsecurity.android.common.build.VersionVirgilAgent +import com.virgilsecurity.android.common.build.VirgilInfo import com.virgilsecurity.android.common.callback.OnGetTokenCallback import com.virgilsecurity.android.common.exception.* import com.virgilsecurity.android.common.manager.GroupManager @@ -50,6 +50,7 @@ import com.virgilsecurity.android.common.util.Const 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.extension.toData import com.virgilsecurity.common.model.Data import com.virgilsecurity.crypto.foundation.Base64 import com.virgilsecurity.sdk.cards.CardManager @@ -83,49 +84,41 @@ class GroupTests { this.crypto = TestConfig.virgilCrypto this.ethree = createEThree() - this.groupId = Data(this.crypto.generateRandomData(100)) + this.groupId = this.crypto.generateRandomData(100).toData() } - @Test - fun ste26() { - // Create with invalid participants count. Should throw error + // test001 STE_26 + @Test fun create_with_invalid_participants_count() { val card = this.ethree.findUser(ethree.identity).get() - try { - val users = FindUsersResult() - users[ethree.identity] = card - this.ethree.createGroup(groupId, users).get() - fail() - } catch (e: InvalidParticipantsCountGroupException) { - } - - val users = FindUsersResult() + val lookup = FindUsersResult() for (i in 0 until 100) { val identity = UUID.randomUUID().toString() - users[identity] = card + lookup[identity] = card } try { - this.ethree.createGroup(groupId, users).get() + this.ethree.createGroup(groupId, lookup).get() fail() - } catch (e: InvalidParticipantsCountGroupException) { + } catch (exception: GroupException) { + assertTrue(exception.description + == GroupException.Description.INVALID_PARTICIPANTS_COUNT) } - val firstEntry = users.entries.first() - val newUsers = FindUsersResult() - newUsers[firstEntry.key] = firstEntry.value + val firstEntry = lookup.entries.first() + val newLookup = FindUsersResult() + newLookup[firstEntry.key] = firstEntry.value - val group = this.ethree.createGroup(groupId, newUsers).get() + val group = this.ethree.createGroup(groupId, newLookup).get() assertEquals(2, group.participants.size) assertTrue(group.participants.contains(this.ethree.identity)) - assertTrue(group.participants.contains(newUsers.keys.first())) + assertTrue(group.participants.contains(newLookup.keys.first())) } - @Test - fun ste27() { - // createGroup should add self + // test002 STE_27 + @Test fun create_should_add_self() { val ethree2 = createEThree() - val groupId2 = Data(this.crypto.generateRandomData(100)) + val groupId2 = this.crypto.generateRandomData(100).toData() val users = this.ethree.findUsers(listOf(ethree.identity, ethree2.identity)).get() @@ -141,24 +134,23 @@ class GroupTests { assertEquals(group1.participants, group2.participants) } - @Test - fun ste28() { - // groupId should not be short + // test003 STE_28 + @Test fun groupId_should_not_be_short() { val ethree2 = createEThree() - val invalidGroupId = Data(this.crypto.generateRandomData(5)) + val invalidGroupId = this.crypto.generateRandomData(5).toData() val lookup = this.ethree.findUsers(listOf(ethree2.identity)).get() try { this.ethree.createGroup(invalidGroupId, lookup).get() fail() - } catch (e: GroupIdTooShortException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.SHORT_GROUP_ID) } } - @Test - fun ste29() { - // get group + // test004 STE_29 + @Test fun get_group() { val ethree2 = createEThree() assertNull(this.ethree.getGroup(groupId)) @@ -173,9 +165,8 @@ class GroupTests { assertEquals(cachedGroup.initiator, group.initiator) } - @Test - fun ste30() { - // load_group + // test005 STE_30 + @Test fun load_group() { val ethree2 = createEThree() val lookup = this.ethree.findUsers(listOf(ethree2.identity)).get() @@ -190,9 +181,8 @@ class GroupTests { assertEquals(group1.initiator, group2.initiator) } - @Test - fun ste31() { - // load alien or non-existing group should throw error + // test006 STE_31 + @Test fun load_alien_or_unexistent_group() { val ethree2 = createEThree() val ethree3 = createEThree() @@ -201,7 +191,8 @@ class GroupTests { try { ethree2.loadGroup(groupId, card1).get() fail() - } catch (e: GroupNotFoundException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.GROUP_WAS_NOT_FOUND) } val lookup = this.ethree.findUsers(listOf(ethree3.identity)).get() @@ -211,13 +202,13 @@ class GroupTests { try { ethree2.loadGroup(groupId, card1).get() fail() - } catch (e: GroupNotFoundException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.GROUP_WAS_NOT_FOUND) } } - @Test - fun ste32() { - // actions on deleted group should throw error + // test007 STE_32 + @Test fun actions_on_deleted_group() { val ethree2 = createEThree() val lookup = this.ethree.findUsers(listOf(ethree2.identity)).get() @@ -232,27 +223,29 @@ class GroupTests { try { this.ethree.loadGroup(groupId, card1).get() fail() - } catch (e: GroupNotFoundException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.GROUP_WAS_NOT_FOUND) } try { group2.update().execute() fail() - } catch (e: GroupNotFoundException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.GROUP_WAS_NOT_FOUND) } try { ethree2.loadGroup(groupId, card1).get() fail() - } catch (e: GroupNotFoundException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.GROUP_WAS_NOT_FOUND) } assertNull(ethree2.getGroup(groupId)) } - @Test - fun ste33() { - // add more than max should throw error + // test008 STE_33 + @Test fun add_more_than_max() { val identity = UUID.randomUUID().toString() val selfKeyPair = crypto.generateKeyPair() @@ -272,7 +265,7 @@ class GroupTests { identity, crypto, virgilCardVerifier) - val httpClient = HttpClient(Const.ETHREE_NAME, VersionVirgilAgent.VERSION) + val httpClient = HttpClient(Const.ETHREE_NAME, VirgilInfo.VERSION) val cardManager = CardManager(VirgilCardCrypto(crypto), accessTokenProvider, VirgilCardVerifier(VirgilCardCrypto(crypto), false, false), @@ -283,8 +276,7 @@ class GroupTests { val groupManager = GroupManager(localGroupStorage, ticketStorageCloud, localKeyStorage, - lookupManager, - this.crypto) + lookupManager) val participants = mutableSetOf() @@ -293,44 +285,45 @@ class GroupTests { participants.add(identity) } - val sessionId = Data(this.crypto.generateRandomData(32)) + val sessionId = this.crypto.generateRandomData(32).toData() val ticket = Ticket(this.crypto, sessionId, participants) val rawGroup = RawGroup(GroupInfo(identity), listOf(ticket)) assertNotNull(groupManager) - val group = Group(rawGroup, this.crypto, localKeyStorage, groupManager, lookupManager) + val group = Group(rawGroup, localKeyStorage, groupManager, lookupManager) val card = TestUtils.publishCard() try { group.add(card).execute() fail() - } catch (e: InvalidParticipantsCountGroupException) { + } catch (exception: GroupException) { + assertTrue(exception.description + == GroupException.Description.INVALID_PARTICIPANTS_COUNT) } } - @Test - fun ste34() { - // remove last participant should throw error + // test009 STE_72 + @Test fun remove_last_participant() { val ethree2 = createEThree() - val lookup = this.ethree.findUsers(listOf(ethree2.identity)).get() - val card = lookup[ethree2.identity] + val card = ethree2.findUser(ethree2.identity).get() assertNotNull(card) - val group1 = this.ethree.createGroup(groupId, lookup).get() + val group = ethree2.createGroup(this.groupId).get() try { - group1.remove(card!!).execute() + group.remove(card).execute() fail() - } catch (e: InvalidParticipantsCountGroupException) { + } catch (exception: GroupException) { + assertTrue(exception.description + == GroupException.Description.INVALID_PARTICIPANTS_COUNT) } } - @Test - fun ste35() { - // remove + // test010 STE_35 + @Test fun remove() { val ethree2 = createEThree() val ethree3 = createEThree() @@ -353,57 +346,22 @@ class GroupTests { try { group2.update().execute() fail() - } catch (e: GroupNotFoundException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.GROUP_WAS_NOT_FOUND) } try { ethree2.loadGroup(groupId, card1).get() fail() - } catch (e: GroupNotFoundException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.GROUP_WAS_NOT_FOUND) } assertNull(ethree2.getGroup(groupId)) } - @Test - fun ste36() { - // change group by noninitiator should_throw_error - val ethree2 = createEThree() - val ethree3 = createEThree() - val ethree4 = createEThree() - val identities = listOf(ethree2.identity, ethree3.identity) - - val lookup = this.ethree.findUsers(identities).get() - this.ethree.createGroup(this.groupId, lookup).get() - val card3 = lookup[ethree3.identity] - assertNotNull(card3) - - val ethree1Card = ethree2.findUser(this.ethree.identity).get() - val group2 = ethree2.loadGroup(this.groupId, ethree1Card).get() - - try { - ethree2.deleteGroup(groupId).execute() - fail() - } catch (e: PermissionDeniedGroupException) { - } - - try { - group2.remove(card3!!).execute() - fail() - } catch (e: PermissionDeniedGroupException) { - } - - try { - val ethree4Card = ethree2.findUser(ethree4.identity).get() - group2.add(ethree4Card).execute() - fail() - } catch (e: PermissionDeniedGroupException) { - } - } - - @Test - fun ste37() { - // add + // test011 STE_37 + @Test fun add() { val ethree2 = createEThree() val ethree3 = createEThree() @@ -429,9 +387,39 @@ class GroupTests { assertEquals(participants, group3.participants) } - @Test - fun ste38() { - // decrypt with old card should throw error + // test012 STE_36 + @Test fun change_group_by_non_initiator() { + val ethree2 = createEThree() + val ethree3 = createEThree() + val ethree4 = createEThree() + val identities = listOf(ethree2.identity, ethree3.identity) + + val lookup = this.ethree.findUsers(identities).get() + this.ethree.createGroup(this.groupId, lookup).get() + val card3 = lookup[ethree3.identity] + assertNotNull(card3) + + val ethree1Card = ethree2.findUser(this.ethree.identity).get() + val group2 = ethree2.loadGroup(this.groupId, ethree1Card).get() + + try { + group2.remove(lookup[ethree3.identity]!!).execute() + fail() + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.GROUP_PERMISSION_DENIED) + } + + try { + val ethree4Card = ethree2.findUser(ethree4.identity).get() + group2.add(ethree4Card).execute() + fail() + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.GROUP_PERMISSION_DENIED) + } + } + + // test013 STE_38 + @Test fun decrypt_with_old_card() { val ethree2 = createEThree() val lookup = this.ethree.findUsers(listOf(ethree2.identity)).get() @@ -450,13 +438,13 @@ class GroupTests { try { group1.decrypt(encrypted, card2) fail() - } catch (e: VerificationFailedGroupException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.VERIFICATION_FAILED) } } - @Test - fun ste39() { - // integration_encryption + // test014 STE_39 + @Test fun integration_encryption() { val ethree2 = createEThree() val ethree3 = createEThree() @@ -488,7 +476,7 @@ class GroupTests { assertEquals(message2, selfDecrypted2) // Other updates, decrypts - group2.update() + group2.update().execute() val group3 = ethree3.loadGroup(groupId, card1).get() val decrypted22 = group2.decrypt(encrypted2, card1) @@ -509,7 +497,8 @@ class GroupTests { try { group2.decrypt(encrypted3, card1) fail() - } catch (e: GroupException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.GROUP_IS_OUTDATED) } group3.update().execute() @@ -546,9 +535,8 @@ class GroupTests { assertEquals(message4, decrypted4) } - @Test - fun ste42() { - // decrypt with old group should throw error + // test015 STE_42 + @Test fun decrypt_with_old_group() { val ethree2 = createEThree() val ethree3 = createEThree() @@ -569,13 +557,13 @@ class GroupTests { try { group2.decrypt(encrypted, card1) fail() - } catch (e: GroupIsOutdatedGroupException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.GROUP_IS_OUTDATED) } } - @Test - fun ste43() { - // decrypt with old group should throw error + // test016 STE_43 + @Test fun decrypt_with_old_group_two() { val ethree2 = createEThree() val lookup = this.ethree.findUsers(listOf(ethree2.identity)).get() @@ -602,13 +590,15 @@ class GroupTests { try { group1.decrypt(encrypted1, card2) fail() - } catch (e: VerificationFailedGroupException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.VERIFICATION_FAILED) } try { group1.decrypt(encrypted1, card2, date2) fail() - } catch (e: VerificationFailedGroupException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.VERIFICATION_FAILED) } val dectypted1 = group1.decrypt(encrypted1, card2, date1) @@ -617,18 +607,19 @@ class GroupTests { try { group1.decrypt(encrypted2, card2, date1) fail() - } catch (e: VerificationFailedGroupException) { + } catch (exception: GroupException) { + assertTrue(exception.description == GroupException.Description.VERIFICATION_FAILED) } val dectypted2 = group1.decrypt(encrypted2, card2, date2) assertEquals(message2, dectypted2) } - @Test - fun ste45() { - // Compatibility test - val compatDataStream = - this.javaClass.classLoader?.getResourceAsStream("compat/compat_data.json") + // test017 STE_45 + @Test fun compatibility() { + val compatDataStream = this.javaClass + .classLoader + ?.getResourceAsStream("compat/compat_data.json") val compatJson = JsonParser().parse(InputStreamReader(compatDataStream)) as JsonObject val groupCompatJson = compatJson.getAsJsonObject("Group") @@ -677,12 +668,11 @@ class GroupTests { assertEquals(originCompatText, decrypted) } - @Test - fun ste46() { - // string identifier + // test018 STE_46 + @Test fun string_identifier() { val ethree2 = createEThree() - val identifier = Data(this.crypto.generateRandomData(32)) + val identifier = this.crypto.generateRandomData(32).toData() val result = this.ethree.findUsers(listOf(ethree2.identity)).get() this.ethree.createGroup(identifier, result).get() @@ -696,6 +686,33 @@ class GroupTests { this.ethree.deleteGroup(identifier) } + // test019 STE_73 + @Test fun added_participant_should_decrypt_history() { + val ethree2 = createEThree() + + val identifier = UUID.randomUUID().toString() + val group1 = this.ethree.createGroup(identifier).get() + + val message = UUID.randomUUID().toString() + val encrypted = group1.encrypt(message) + + val card2 = ethree.findUser(ethree2.identity).get() + group1.add(card2).execute() + + val card1 = ethree2.findUser(ethree.identity).get() + val group2 = ethree2.loadGroup(identifier, card1).get() + + val decrypted = group2.decrypt(encrypted, card1) + + assertEquals(message, decrypted) + } + + // test020 STE_85 + @Test fun delete_unexistent_channel() { + val fakeId = UUID.randomUUID().toString() + ethree.deleteGroup(fakeId).execute() + } + private fun createEThree(): EThree { val identity = UUID.randomUUID().toString() val tokenCallback = object : OnGetTokenCallback { diff --git a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/PeerToPeerTest.kt b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/PeerToPeerTest.kt index 67becc7a..c74001e6 100644 --- a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/PeerToPeerTest.kt +++ b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/PeerToPeerTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019, Virgil Security, Inc. + * Copyright (c) 2015-2020, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * @@ -40,9 +40,11 @@ import com.virgilsecurity.android.common.model.FindUsersResult 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.cards.Card +import com.virgilsecurity.common.extension.toData +import com.virgilsecurity.sdk.common.TimeSpan +import com.virgilsecurity.sdk.crypto.KeyPairType import com.virgilsecurity.sdk.crypto.VirgilCrypto +import com.virgilsecurity.sdk.crypto.exceptions.VerificationException import com.virgilsecurity.sdk.storage.DefaultKeyStorage import com.virgilsecurity.sdk.utils.ConvertionUtils import org.junit.Assert.* @@ -52,7 +54,7 @@ import org.junit.runner.RunWith import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.util.* -import kotlin.String.Companion +import java.util.concurrent.TimeUnit /** * PeerToPeerTest @@ -90,7 +92,9 @@ class PeerToPeerTest { return TestUtils.generateTokenString(identityTwo) } }, - TestConfig.context) + TestConfig.context, + enableRatchet = false, + keyRotationInterval = TimeSpan.fromTime(3600, TimeUnit.SECONDS)) assertNotNull(ethreeTwo) @@ -99,19 +103,19 @@ class PeerToPeerTest { val card = ethree.findUser(ethreeTwo.identity).get() assertNotNull(card) - val encrypted = ethree.encrypt(TEXT, card) + val encrypted = ethree.authEncrypt(TEXT, card) val otherCard = TestUtils.publishCard() try { - ethreeTwo.decrypt(encrypted, otherCard) + ethreeTwo.authDecrypt(encrypted, otherCard) fail() } catch (throwable: Throwable) { // We're good } val cardTwo = ethreeTwo.findUser(ethree.identity).get() - val decryptedTwo = ethreeTwo.decrypt(encrypted, cardTwo) + val decryptedTwo = ethreeTwo.authDecrypt(encrypted, cardTwo) assertEquals(TEXT, decryptedTwo) } @@ -119,7 +123,7 @@ class PeerToPeerTest { @Test(expected = IllegalArgumentException::class) fun encrypt_empty_keys() { ethree.register().execute() - ethree.encrypt(TEXT, FindUsersResult()) + ethree.authEncrypt(TEXT, FindUsersResult()) } // test03 STE_5 @@ -128,13 +132,13 @@ class PeerToPeerTest { val plainData = TEXT.toByteArray() val card = ethree.findUser(ethree.identity).get() - val encryptedData = crypto.encrypt(plainData, card.publicKey) + val encryptedData = crypto.encrypt(plainData, card.publicKey, false) val encryptedString = ConvertionUtils.toBase64String(encryptedData) val otherCard = TestUtils.publishCard() try { - ethree.decrypt(encryptedString, otherCard) + ethree.authDecrypt(encryptedString, otherCard) fail() } catch (throwable: Throwable) { // We're food @@ -149,17 +153,15 @@ class PeerToPeerTest { val card = TestUtils.publishCard() try { - ethree.encrypt(TEXT, FindUsersResult(mapOf(ethree.identity to card))) - } catch (throwable: Throwable) { - if (throwable !is EThreeException) - fail() + ethree.authEncrypt(TEXT, FindUsersResult(mapOf(ethree.identity to card))) + } catch (exception: EThreeException) { + assertTrue(exception.description == EThreeException.Description.MISSING_PRIVATE_KEY) } try { - ethree.decrypt(Data(TEXT.toByteArray()).toBase64String(), card) - } catch (throwable: Throwable) { - if (throwable !is EThreeException) - fail() + ethree.authDecrypt(TEXT.toData().toBase64String(), card) + } catch (exception: EThreeException) { + assertTrue(exception.description == EThreeException.Description.MISSING_PRIVATE_KEY) } } @@ -167,16 +169,18 @@ class PeerToPeerTest { @Test fun encrypt_decrypt_stream() { ethree.register().execute() - val inputStream = ByteArrayInputStream(TEXT.toByteArray()) + val data = TEXT.toByteArray() + val inputStream = ByteArrayInputStream(data) + val size = data.size val outputStream = ByteArrayOutputStream() - ethree.encrypt(inputStream, outputStream) + ethree.authEncrypt(inputStream, size, outputStream) val encryptedData = outputStream.toByteArray() val inputStreamTwo = ByteArrayInputStream(encryptedData) val outputStreamTwo = ByteArrayOutputStream() - ethree.decrypt(inputStreamTwo, outputStreamTwo) + ethree.authDecrypt(inputStreamTwo, outputStreamTwo) val decryptedData = outputStreamTwo.toByteArray() @@ -195,7 +199,10 @@ class PeerToPeerTest { return TestUtils.generateTokenString(identityTwo) } }, - TestConfig.context) + TestConfig.context, + keyPairType = KeyPairType.ED25519, + enableRatchet = false, + keyRotationInterval = TimeSpan.fromTime(3600, TimeUnit.SECONDS)) assertNotNull(ethreeTwo) @@ -206,9 +213,9 @@ class PeerToPeerTest { val dateOne = Date() - TestUtils.pause(1000) // 1 sec + Thread.sleep(1000) // 1 sec - val encrypted = ethree.encrypt(TEXT, FindUsersResult(mapOf(card.identity to card))) + val encrypted = ethree.authEncrypt(TEXT, FindUsersResult(mapOf(card.identity to card))) ethree.cleanup() @@ -217,36 +224,36 @@ class PeerToPeerTest { val dateTwo = Date() val encryptedTwo = - ethree.encrypt(TEXT + TEXT, FindUsersResult(mapOf(card.identity to card))) + ethree.authEncrypt(TEXT + TEXT, FindUsersResult(mapOf(card.identity to card))) val cardTwo = ethreeTwo.findUser(ethree.identity).get() assertNotNull(cardTwo) try { - ethreeTwo.decrypt(encrypted, cardTwo) + ethreeTwo.authDecrypt(encrypted, cardTwo) fail() } catch (throwable: Throwable) { - // We're good + assertTrue(throwable is VerificationException) } try { - ethreeTwo.decrypt(encrypted, cardTwo, dateTwo) + ethreeTwo.authDecrypt(encrypted, cardTwo, dateTwo) fail() } catch (throwable: Throwable) { - // We're good + assertTrue(throwable is VerificationException) } - val decrypted = ethreeTwo.decrypt(encrypted, cardTwo, dateOne) + val decrypted = ethreeTwo.authDecrypt(encrypted, cardTwo, dateOne) assertEquals(TEXT, decrypted) try { - ethreeTwo.decrypt(encryptedTwo, cardTwo, dateOne) + ethreeTwo.authDecrypt(encryptedTwo, cardTwo, dateOne) fail() } catch (throwable: Throwable) { - // We're good + assertTrue(throwable is VerificationException) } - val decryptedTwo = ethreeTwo.decrypt(encryptedTwo, cardTwo, dateTwo) + val decryptedTwo = ethreeTwo.authDecrypt(encryptedTwo, cardTwo, dateTwo) assertEquals(TEXT + TEXT, decryptedTwo) } @@ -281,6 +288,47 @@ class PeerToPeerTest { assertEquals(TEXT, decrypted) } + // test08 STE_71 + @Test fun encrypt_decrypt_deprecated_methods_should_succeed() { + val identityTwo = UUID.randomUUID().toString() + + ethree.register().execute() + + val ethreeTwo = EThree(identityTwo, + object : OnGetTokenCallback { + override fun onGetToken(): String { + return TestUtils.generateTokenString(identityTwo) + } + }, + TestConfig.context, + keyPairType = KeyPairType.ED25519, + enableRatchet = false, + keyRotationInterval = TimeSpan.fromTime(3600, TimeUnit.SECONDS)) + + assertNotNull(ethreeTwo) + + ethreeTwo.register().execute() + + val card = ethree.findUser(ethreeTwo.identity, forceReload = false).get() + assertNotNull(card) + + val encrypted = ethree.encrypt(TEXT, card) + + val otherCard = TestUtils.publishCard() + + try { + ethreeTwo.decrypt(encrypted, otherCard) + fail() + } catch (throwable: Throwable) { + assertTrue(throwable is EThreeException + && throwable.description == EThreeException.Description.VERIFICATION_FAILED) + } + + val cardTwo = ethreeTwo.findUser(ethree.identity, forceReload = false).get() + val decryptedTwo = ethreeTwo.decrypt(encrypted, cardTwo) + assertEquals(TEXT, decryptedTwo) + } + @Test fun signature_invalid_test() { ethree.register().execute() @@ -306,9 +354,8 @@ class PeerToPeerTest { try { ethreeTwo.decrypt(encrypted, cardOne) fail() - } catch (throwable: Throwable) { - if (throwable !is EThreeException) - fail() + } catch (exception: EThreeException) { + assertTrue(exception.description == EThreeException.Description.VERIFICATION_FAILED) } } diff --git a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/RatchetTests.kt b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/RatchetTests.kt new file mode 100644 index 00000000..d9a8fcb5 --- /dev/null +++ b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/RatchetTests.kt @@ -0,0 +1,466 @@ +/* + * 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.common.worker + +import com.virgilsecurity.android.common.callback.OnGetTokenCallback +import com.virgilsecurity.android.common.exception.EThreeRatchetException +import com.virgilsecurity.android.common.model.ratchet.RatchetChannel +import com.virgilsecurity.android.common.storage.local.LocalKeyStorage +import com.virgilsecurity.android.common.util.Defaults +import com.virgilsecurity.android.common.utils.TestConfig +import com.virgilsecurity.android.common.utils.TestUtils +import com.virgilsecurity.android.ethree.interaction.EThree +import com.virgilsecurity.ratchet.securechat.SecureChat +import com.virgilsecurity.ratchet.securechat.SecureChatContext +import com.virgilsecurity.sdk.cards.Card +import com.virgilsecurity.sdk.common.TimeSpan +import com.virgilsecurity.sdk.crypto.VirgilCrypto +import com.virgilsecurity.sdk.jwt.accessProviders.CachingJwtProvider +import com.virgilsecurity.sdk.storage.DefaultKeyStorage +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import java.util.* + +/** + * RatchetTests + */ +class RatchetTests { + + private lateinit var crypto: VirgilCrypto + private lateinit var keyStorage: DefaultKeyStorage + + @Before fun setup() { + this.crypto = VirgilCrypto() + this.keyStorage = DefaultKeyStorage(TestConfig.DIRECTORY_PATH, TestConfig.KEYSTORE_NAME) + } + + private fun setupDevice(enableRatchet: Boolean = true, + keyRotationInterval: TimeSpan = Defaults.keyRotationInterval): Pair { + val identityNew = UUID.randomUUID().toString() + + val tokenCallback = object : OnGetTokenCallback { + override fun onGetToken(): String { + return TestUtils.generateTokenString(identityNew) + } + } + + val ethree = EThree(identityNew, + tokenCallback, + TestConfig.context, + enableRatchet = enableRatchet, + keyRotationInterval = keyRotationInterval) + + ethree.register().execute() + + val card = ethree.findUser(identityNew).get() + + return Pair(ethree, card) + } + + private fun encryptDecrypt100Times(channel1: RatchetChannel, channel2: RatchetChannel) { + for (i in 1..100) { + val sender: RatchetChannel + val receiver: RatchetChannel + + if (Random().nextBoolean()) { + sender = channel1 + receiver = channel2 + } else { + sender = channel2 + receiver = channel1 + } + + val encrypted = sender.encrypt(TEXT) + val decrypted = receiver.decrypt(encrypted) + + assertEquals(TEXT, decrypted) + } + } + + // test001 STE_51 + @Test fun encrypt_decrypt_should_succeed() { + val (ethree1, card1) = setupDevice() + val (ethree2, card2) = setupDevice() + + val chat1 = ethree1.createRatchetChannel(card2).get() + val chat2 = ethree2.joinRatchetChannel(card1).get() + + encryptDecrypt100Times(chat1, chat2) + } + + // test002 STE_52 + @Test fun create_with_self_should_throw_error() { + val (ethree, card) = setupDevice() + + try { + ethree.createRatchetChannel(card).get() + fail() + } catch (exception: EThreeRatchetException) { + if (exception.description != EThreeRatchetException.Description.SELF_CHANNEL_IS_FORBIDDEN) + fail() + } + } + + // test003 STE_53 + @Test fun create_with_disabled_ratchet_user_should_throw_error() { + val (_, card1) = setupDevice(enableRatchet = false) + val (ethree2, _) = setupDevice() + + try { + ethree2.createRatchetChannel(card1).get() + fail() + } catch (exception: EThreeRatchetException) { + if (exception.description != EThreeRatchetException.Description.USER_IS_NOT_USING_RATCHET) + fail() + } + } + + // test004 STE_54 + @Test fun create_which_exists_should_throw_error() { + val (ethree1, _) = setupDevice() + val (_, card2) = setupDevice() + + ethree1.createRatchetChannel(card2).get() + + try { + ethree1.createRatchetChannel(card2).get() + fail() + } catch (exception: EThreeRatchetException) { + if (exception.description != EThreeRatchetException.Description.CHANNEL_ALREADY_EXISTS) + fail() + } + + val secureChat1 = getSecureChat(ethree1) + secureChat1.deleteSession(card2.identity) + + try { + ethree1.createRatchetChannel(card2).get() + fail() + } catch (exception: EThreeRatchetException) { + if (exception.description != EThreeRatchetException.Description.CHANNEL_ALREADY_EXISTS) + fail() + } + } + + // test005 STE_55 + @Test fun create_after_delete_should_succeed() { + val (ethree1, card1) = setupDevice() + val (ethree2, card2) = setupDevice() + + ethree1.createRatchetChannel(card2).get() + ethree2.joinRatchetChannel(card1).get() + + ethree1.deleteRatchetChannel(card2).execute() + ethree2.deleteRatchetChannel(card1).execute() + + val newChat1 = ethree1.createRatchetChannel(card2).get() + val newChat2 = ethree2.joinRatchetChannel(card1).get() + + encryptDecrypt100Times(newChat1, newChat2) + } + + // test006 STE_56 + @Test fun join_with_self_should_throw_error() { + val (ethree, card) = setupDevice() + + try { + ethree.joinRatchetChannel(card).get() + fail() + } catch (exception: EThreeRatchetException) { + if (exception.description != EThreeRatchetException.Description.SELF_CHANNEL_IS_FORBIDDEN) + fail() + } + } + + // test007 STE_57 + @Test fun join_which_exists_should_throw_error() { + val (ethree1, card1) = setupDevice() + val (ethree2, card2) = setupDevice() + + ethree1.createRatchetChannel(card2).get() + ethree2.joinRatchetChannel(card1).get() + + try { + ethree2.joinRatchetChannel(card1).get() + fail() + } catch (exception: EThreeRatchetException) { + if (exception.description != EThreeRatchetException.Description.CHANNEL_ALREADY_EXISTS) + fail() + } + } + + // test 008 STE_58 + @Test fun join_without_invitation_should_throw_error() { + val (_, card1) = setupDevice() + val (ethree2, _) = setupDevice() + + try { + ethree2.joinRatchetChannel(card1).get() + fail() + } catch (exception: EThreeRatchetException) { + if (exception.description != EThreeRatchetException.Description.NO_INVITE) + fail() + } + } + + // test 009 STE_59 + @Test fun join_after_delete_should_throw_error() { + val (ethree1, card1) = setupDevice() + val (ethree2, card2) = setupDevice() + + ethree1.createRatchetChannel(card2).get() + ethree1.deleteRatchetChannel(card2).execute() + + try { + ethree2.joinRatchetChannel(card1).get() + fail() + } catch (exception: EThreeRatchetException) { + if (exception.description != EThreeRatchetException.Description.NO_INVITE) + fail() + } + } + + // test 010 STE_60 + @Test fun join_after_rotate_should_throw_error() { + val (ethree1, card1) = setupDevice() + val (ethree2, card2) = setupDevice() + + ethree1.createRatchetChannel(card2).get() + ethree1.cleanup() + ethree1.rotatePrivateKey().execute() + + try { + ethree2.joinRatchetChannel(card1).get() + } catch (exception: EThreeRatchetException) { + if (exception.description != EThreeRatchetException.Description.NO_INVITE) + fail() + } + } + + // test 011 STE_61 + @Test fun join_after_unregister_should_succeed() { + val (ethree1, card1) = setupDevice() + val (ethree2, card2) = setupDevice() + + val chat1 = ethree1.createRatchetChannel(card2).get() + + val encrypted = chat1.encrypt(TEXT) + + ethree1.unregister().execute() + + val chat2 = ethree2.joinRatchetChannel(card1).get() + val decrypted = chat2.decrypt(encrypted) + + assertEquals(TEXT, decrypted) + } + + // test 012 STE_62 + @Test fun getRatchetChannel_should_succeed() { + val (ethree1, card1) = setupDevice() + val (ethree2, card2) = setupDevice() + + assertNull(ethree1.getRatchetChannel(card2)) + assertNull(ethree2.getRatchetChannel(card1)) + + ethree1.createRatchetChannel(card2).get() + assertNotNull(ethree1.getRatchetChannel(card2)) + + ethree2.createRatchetChannel(card1).get() + assertNotNull(ethree2.getRatchetChannel(card1)) + + ethree1.deleteRatchetChannel(card2).execute() + assertNull(ethree1.getRatchetChannel(card2)) + + ethree2.deleteRatchetChannel(card1).execute() + assertNull(ethree2.getRatchetChannel(card1)) + } + + // test 013 STE_63 + @Test fun delete_nonexistent_chat_should_succeed() { + val (ethree1, _) = setupDevice() + val (_, card2) = setupDevice() + + try { + ethree1.deleteRatchetChannel(card2).execute() + } catch (exception: Exception) { + fail() + } + } + + // test 014 STE_64 + @Test fun enableRatchet() { + val (ethree1, _) = setupDevice(enableRatchet = false) + val (_, card2) = setupDevice() + + try { + ethree1.createRatchetChannel(card2).get() + } catch (exception: EThreeRatchetException) { + if (exception.description != EThreeRatchetException.Description.RATCHET_IS_DISABLED) + fail() + } + + try { + ethree1.joinRatchetChannel(card2).get() + } catch (exception: EThreeRatchetException) { + if (exception.description != EThreeRatchetException.Description.RATCHET_IS_DISABLED) + fail() + } + + try { + ethree1.getRatchetChannel(card2) + } catch (exception: EThreeRatchetException) { + if (exception.description != EThreeRatchetException.Description.RATCHET_IS_DISABLED) + fail() + } + + try { + ethree1.deleteRatchetChannel(card2).execute() + } catch (exception: EThreeRatchetException) { + if (exception.description != EThreeRatchetException.Description.RATCHET_IS_DISABLED) + fail() + } + } + + // test 015 STE_65 + @Test fun auto_keys_rotation() { + val (ethree1, card1) = setupDevice() + val (ethree2, card2) = setupDevice() + + ethree2.createRatchetChannel(card1).get() + + val secureChat1 = getSecureChat(ethree1) + + secureChat1.oneTimeKeysStorage.startInteraction() + val keys1 = secureChat1.oneTimeKeysStorage.retrieveAllKeys() + secureChat1.oneTimeKeysStorage.stopInteraction() + + ethree1.joinRatchetChannel(card2).get() + + secureChat1.oneTimeKeysStorage.startInteraction() + val keys2 = secureChat1.oneTimeKeysStorage.retrieveAllKeys() + secureChat1.oneTimeKeysStorage.stopInteraction() + + var keysRotated = false + for (key1 in keys1) { + if (keys2.find { it.identifier.contentEquals(key1.identifier) } == null) { + keysRotated = true + break + } + } + + assertTrue(keysRotated) + } + + // test 016 STE_66 + @Test fun multiple_encrypt_decrypt_should_succeed() { + val (ethree1, card1) = setupDevice() + val (ethree2, card2) = setupDevice() + + val chat1 = ethree1.createRatchetChannel(card2).get() + + val messages = mutableListOf() + for (i in 0 until 100) { + messages.add(UUID.randomUUID().toString()) + } + + val encrypted = chat1.encryptMultiple(RatchetChannel.MultipleString(messages)) + + val chat2 = ethree2.joinRatchetChannel(card1).get() + + val decrypted = chat2.decryptMultiple(encrypted) + + for (i in 0 until messages.size) { + assertEquals(messages[i], + decrypted.multipleText[i]) + } + } + + // test 017 STE_67 + @Test fun decrypt_messages_after_rotate_identity_key_should_succeed() { + val (ethree1, _) = setupDevice() + val (ethree2, card2) = setupDevice() + + ethree1.createRatchetChannel(card2).get() + + ethree1.cleanup() + ethree1.rotatePrivateKey().execute() + + val newCard1 = ethree2.findUser(ethree1.identity, forceReload = true).get() + + val chat1 = ethree1.createRatchetChannel(card2).get() + val chat2 = ethree2.joinRatchetChannel(newCard1).get() + + encryptDecrypt100Times(chat1, chat2) + } + + // test 018 STE_68 + @Test fun chats_with_different_names() { + val (ethree1, card1) = setupDevice() + val (ethree2, card2) = setupDevice() + + val name1 = UUID.randomUUID().toString() + val chat11 = ethree1.createRatchetChannel(card2, name1).get() + + val name2 = UUID.randomUUID().toString() + val chat22 = ethree2.createRatchetChannel(card1, name2).get() + + val chat12 = ethree1.joinRatchetChannel(card2, name2).get() + val chat21 = ethree2.joinRatchetChannel(card1, name1).get() + + encryptDecrypt100Times(chat11, chat21) + encryptDecrypt100Times(chat12, chat22) + } + + private fun getSecureChat(ethree: EThree): SecureChat { + val localKeyStorage = LocalKeyStorage(ethree.identity, keyStorage, crypto) + + val card = ethree.findCachedUser(ethree.identity).get()!! + + val keyPair = localKeyStorage.retrieveKeyPair() + val cachingTokenProvider = CachingJwtProvider( + CachingJwtProvider.RenewJwtCallback { TestUtils.generateToken(ethree.identity) } + ) + val context = SecureChatContext(card, + keyPair, + cachingTokenProvider, + TestConfig.DIRECTORY_PATH) + + return SecureChat(context) + } + + companion object { + private const val TEXT = "Hello, my name is text. I am here to be encrypted (:" + } +} diff --git a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/SearchTests.kt b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/SearchTests.kt index 98ff2693..e3bb9f96 100644 --- a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/SearchTests.kt +++ b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/SearchTests.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019, Virgil Security, Inc. + * Copyright (c) 2015-2020, Virgil Security, Inc. * * Lead Maintainer: Virgil Security Inc. * @@ -40,8 +40,8 @@ import com.virgilsecurity.android.common.exception.FindUsersException 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.exception.EmptyArgumentException import com.virgilsecurity.sdk.crypto.VirgilCrypto -import com.virgilsecurity.sdk.exception.EmptyArgumentException import com.virgilsecurity.sdk.storage.DefaultKeyStorage import org.junit.Assert.* import org.junit.Before @@ -138,9 +138,13 @@ class SearchTests { try { ethree.findUser(cardOne.identity).get() + fail() } catch (throwable: Throwable) { - if (throwable !is FindUsersException) + if (throwable is FindUsersException) { + assertTrue(throwable.description == FindUsersException.Description.DUPLICATE_CARDS) + } else { fail() + } } } @@ -171,12 +175,45 @@ class SearchTests { TestConfig.context, onKeyChangedCallback) - TestUtils.pause(3 * 1000) // 3 sec - assertTrue(onKeyChangedCallback.called) val cardCached = ethreeNew.findCachedUser(card.identity).get() ?: error("") assertEquals(cardNew.identifier, cardCached.identifier) } + + // test06 STE_47 + @Test fun checkResult() { + val card = TestUtils.publishCard() + + val identities = listOf(card.identity, this.identity) + + try { + ethree.findUsers(identities).get() + fail() + } catch (throwable: FindUsersException) { + assertTrue(throwable.description == FindUsersException.Description.CARD_WAS_NOT_FOUND) + } + + val cards = ethree.findUsers(identities, checkResult = false).get() + + assertEquals(1, cards.size) + assertEquals(card.identifier, cards[card.identity]!!.identifier) + } + + // test07 STE_48 + @Test fun updateCachedCards() { + val ethree2 = setupDevice() + + val card2 = ethree.findUser(ethree2.identity, forceReload = false).get() + + ethree2.cleanup() + ethree2.rotatePrivateKey().execute() + + ethree.updateCachedUsers().execute() + + val newCard2 = ethree.findUser(ethree2.identity, forceReload = false).get() + + assertEquals(card2.identifier, newCard2.previousCardId) + } } diff --git a/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/TempChannelTests.kt b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/TempChannelTests.kt new file mode 100644 index 00000000..2a7ec580 --- /dev/null +++ b/ethree-common/src/androidTest/java/com/virgilsecurity/android/common/worker/TempChannelTests.kt @@ -0,0 +1,330 @@ +/* + * 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.common.worker + +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.virgilsecurity.android.common.callback.OnGetTokenCallback +import com.virgilsecurity.android.common.exception.TemporaryChannelException +import com.virgilsecurity.android.common.model.temporary.TemporaryChannel +import com.virgilsecurity.android.common.storage.local.FileTempKeysStorage +import com.virgilsecurity.android.common.storage.local.LocalKeyStorage +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.extension.toData +import com.virgilsecurity.crypto.foundation.Base64 +import com.virgilsecurity.sdk.common.TimeSpan +import com.virgilsecurity.sdk.crypto.VirgilAccessTokenSigner +import com.virgilsecurity.sdk.crypto.VirgilCrypto +import com.virgilsecurity.sdk.crypto.VirgilKeyPair +import com.virgilsecurity.sdk.jwt.JwtGenerator +import com.virgilsecurity.sdk.storage.DefaultKeyStorage +import org.junit.Assert +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import java.io.InputStreamReader +import java.util.* +import java.util.concurrent.TimeUnit + +/** + * TempChannelTests + */ +class TempChannelTests { + + private lateinit var identity: String + private lateinit var keyStorage: DefaultKeyStorage + + @Before fun setup() { + this.identity = UUID.randomUUID().toString() + this.keyStorage = DefaultKeyStorage(TestConfig.DIRECTORY_PATH, TestConfig.KEYSTORE_NAME) + } + + private fun setupDevice(identity: String? = null, + keyPair: VirgilKeyPair? = null): EThree { + val identityNew = identity ?: UUID.randomUUID().toString() + + val tokenCallback = object : OnGetTokenCallback { + override fun onGetToken(): String { + return TestUtils.generateTokenString(identityNew) + } + } + + val ethree = EThree(identityNew, tokenCallback, TestConfig.context) + + ethree.register().execute() + + return ethree + } + + fun encryptDecrypt100Times(channel1: TemporaryChannel, channel2: TemporaryChannel) { + for (i in 1..100) { + val sender: TemporaryChannel + val receiver: TemporaryChannel + + if (Random().nextBoolean()) { + sender = channel1 + receiver = channel2 + } else { + sender = channel2 + receiver = channel1 + } + + val encrypted = sender.encrypt(TEXT) + val decrypted = receiver.decrypt(encrypted) + + Assert.assertEquals(TEXT, decrypted) + } + } + + // test01 STE_74 + @Test fun encrypt_decrypt() { + val ethree1 = setupDevice() + + val identity2 = UUID.randomUUID().toString() + val chat1 = ethree1.createTemporaryChannel(identity2).get() + + val encrypted = chat1.encrypt(TEXT) + + val ethree2 = setupDevice(identity2) + val chat2 = ethree2.loadTemporaryChannel(false, ethree1.identity).get() + val decrypted = chat2.decrypt(encrypted) + assertEquals(TEXT, decrypted) + + encryptDecrypt100Times(chat1, chat2) + + val newChat1 = ethree1.loadTemporaryChannel(true, identity2).get() + val newChat2 = ethree2.getTemporaryChannel(ethree1.identity)!! + + encryptDecrypt100Times(newChat1, newChat2) + } + + // test02 STE_75 + @Test fun create_existent_chat() { + val ethree = setupDevice() + + ethree.createTemporaryChannel(this.identity).get() + + try { + ethree.createTemporaryChannel(identity).get() + fail() + } catch (exception: TemporaryChannelException) { + assertEquals(exception.description, + TemporaryChannelException.Description.CHANNEL_ALREADY_EXISTS) + } + } + + // test03 STE_76 + @Test fun create_with_self() { + val ethree = setupDevice() + + try { + ethree.createTemporaryChannel(ethree.identity).get() + fail() + } catch (exception: TemporaryChannelException) { + assertEquals(exception.description, + TemporaryChannelException.Description.SELF_CHANNEL_IS_FORBIDDEN) + } + } + + // test04 STE_77 + @Test fun create_with_registered() { + val ethree1 = setupDevice() + val ethree2 = setupDevice() + + try { + ethree1.createTemporaryChannel(ethree2.identity).get() + fail() + } catch (exception: TemporaryChannelException) { + assertEquals(exception.description, + TemporaryChannelException.Description.USER_IS_REGISTERED) + } + } + + // test05 STE_78 + @Test fun get() { + val ethree1 = setupDevice() + + val identity2 = UUID.randomUUID().toString() + assertNull(ethree1.getTemporaryChannel(identity2)) + + ethree1.createTemporaryChannel(identity2).get() + assertNotNull(ethree1.getTemporaryChannel(identity2)) + + val ethree2 = setupDevice(identity2) + assertNull(ethree2.getTemporaryChannel(ethree1.identity)) + + ethree2.loadTemporaryChannel(false, ethree1.identity).get() + assertNotNull(ethree2.getTemporaryChannel(ethree1.identity)) + + ethree1.deleteTemporaryChannel(identity2).execute() + assertNull(ethree1.getTemporaryChannel(identity2)) + } + + // test06 STE_79 + @Test fun load_with_self() { + val ethree = setupDevice() + + try { + ethree.loadTemporaryChannel(true, identity).get() + } catch (exception: TemporaryChannelException) { + assertEquals(exception.description, + TemporaryChannelException.Description.CHANNEL_NOT_FOUND) + } + } + + // test07 STE_80 + @Test fun load_unexistent_chat() { + val ethree = setupDevice() + + try { + ethree.loadTemporaryChannel(true, identity).get() + } catch (exception: TemporaryChannelException) { + assertEquals(exception.description, + TemporaryChannelException.Description.CHANNEL_NOT_FOUND) + } + } + + // test08 STE_81 + @Test fun load_after_delete() { + val ethree1 = setupDevice() + + val identity2 = UUID.randomUUID().toString() + + ethree1.createTemporaryChannel(identity2).get() + ethree1.deleteTemporaryChannel(identity2).execute() + + try { + ethree1.loadTemporaryChannel(true, identity2).get() + } catch (exception: TemporaryChannelException) { + assertEquals(exception.description, + TemporaryChannelException.Description.CHANNEL_NOT_FOUND) + } + + val ethree2 = setupDevice(identity2) + + try { + ethree2.loadTemporaryChannel(false, ethree1.identity).get() + } catch (exception: TemporaryChannelException) { + assertEquals(exception.description, + TemporaryChannelException.Description.CHANNEL_NOT_FOUND) + } + } + + // test09 STE_82 + @Test fun delete_unexistent_chat() { + val ethree = setupDevice() + + try { + ethree.deleteTemporaryChannel(this.identity).execute() + } catch (exception: Exception) { + fail() + } + } + + // test10 STE_83 + @Test fun compatibility() { + val compatDataStream = this.javaClass + .classLoader + ?.getResourceAsStream("compat/compat_data.json") + val compatJson = JsonParser().parse(InputStreamReader(compatDataStream)) as JsonObject + val tempChannelCompatJson = compatJson.getAsJsonObject("TemporaryChannel") + + val identity = tempChannelCompatJson.get("Identity").asString + + val privateKeyCompat = TestConfig.virgilCrypto.importPrivateKey( + Base64.decode(compatJson.get("ApiPrivateKey").asString.toByteArray()) + ) + val jwtGenerator = JwtGenerator( + compatJson.get("AppId").asString, + privateKeyCompat.privateKey, + compatJson.get("ApiKeyId").asString, + TimeSpan.fromTime(600, TimeUnit.SECONDS), + VirgilAccessTokenSigner(TestConfig.virgilCrypto) + ) + + val tokenCallback = object : OnGetTokenCallback { + override fun onGetToken(): String { + return jwtGenerator.generateToken(identity).stringRepresentation() + } + } + + val ethree = EThree(identity, tokenCallback, TestConfig.context) + var ethreeNew: EThree? = null + + if (!ethree.hasLocalPrivateKey()) { + val privateKeyData = Base64.decode( + tempChannelCompatJson.get("PrivateKey").asString.toByteArray() + ).toData() + val localKeyStorage = LocalKeyStorage(identity, keyStorage, VirgilCrypto()) + localKeyStorage.store(privateKeyData) + + // Should call privateKeyChanged() + ethreeNew = EThree(identity, tokenCallback, TestConfig.context) + } + + val initiator = tempChannelCompatJson.get("Initiator").asString + val ethreeNew2 = ethreeNew ?: ethree + val chat = ethreeNew2.loadTemporaryChannel(false, initiator).get() + + val originText = tempChannelCompatJson.get("OriginText").asString + val encryptedText = tempChannelCompatJson.get("EncryptedText").asString + val decrypted = chat.decrypt(encryptedText) + assertEquals(originText, decrypted) + } + + // test11 STE_84 + @Test fun cleanup_should_reset_local_storage() { + val keyPair = TestConfig.virgilCrypto.generateKeyPair() + val ethree = setupDevice(keyPair = keyPair) + + val localTempStorage = FileTempKeysStorage(ethree.identity, + TestConfig.virgilCrypto, + keyPair, + TestConfig.context.filesDir.absolutePath) + assertNull(localTempStorage.retrieve(identity)) + + ethree.createTemporaryChannel(identity).get() + assertNotNull(localTempStorage.retrieve(identity)) + + ethree.cleanup() + + assertNull(localTempStorage.retrieve(identity)) + } + + companion object { + private const val TEXT = "Hello, my name is text. I am here to be encrypted (:" + } +} diff --git a/ethree-common/src/androidTest/resources/compat/compat_data.json.enc b/ethree-common/src/androidTest/resources/compat/compat_data.json.enc deleted file mode 100644 index f452bf3d..00000000 Binary files a/ethree-common/src/androidTest/resources/compat/compat_data.json.enc and /dev/null differ diff --git a/ethree-common/src/main/AndroidManifest.xml b/ethree-common/src/main/AndroidManifest.xml index fbee13d1..9f7d317e 100644 --- a/ethree-common/src/main/AndroidManifest.xml +++ b/ethree-common/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@