All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.virgilsecurity.keyknox.storage.SyncKeyStorage.kt Maven / Gradle / Ivy

Go to download

Virgil is a stack of security libraries (ECIES with Crypto Agility wrapped in Virgil Cryptogram) and all the necessary infrastructure to enable seamless, end-to-end encryption for any application, platform or device. Learn about Virgil Java/Android SDK https://virgilsecurity.com/api-docs/java-android/quickstart

The newest version!
/*
 * 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.keyknox.storage

import com.virgilsecurity.keyknox.cloud.CloudKeyStorage
import com.virgilsecurity.keyknox.cloud.CloudKeyStorageProtocol
import com.virgilsecurity.keyknox.exception.*
import com.virgilsecurity.sdk.crypto.VirgilCrypto
import com.virgilsecurity.sdk.crypto.VirgilPrivateKey
import com.virgilsecurity.sdk.crypto.VirgilPublicKey
import com.virgilsecurity.sdk.jwt.contract.AccessTokenProvider
import com.virgilsecurity.sdk.storage.DefaultKeyStorage
import com.virgilsecurity.sdk.storage.KeyEntry
import com.virgilsecurity.sdk.storage.KeyStorage

/**
 * Class responsible for synchronization between Keychain and Keyknox Cloud.
 */
class SyncKeyStorage {

    val identity: String
    val cloudKeyStorage: CloudKeyStorageProtocol
    private var keyStorage: KeyStorageWrapper
    private val keychainUtils = KeychainUtils()

    /**
     * @param identity User's identity to separate keys in Keychain.
     * @param keyStorage KeychainStorageProtocol implementation.
     * @param cloudKeyStorage CloudKeyStorageProtocol implementation.
     */
    constructor(identity: String, keyStorage: KeyStorage,
                cloudKeyStorage: CloudKeyStorageProtocol) {
        this.identity = identity
        this.keyStorage = KeyStorageWrapper(identity, keyStorage)
        this.cloudKeyStorage = cloudKeyStorage
    }

    /**
     * @param identity User's identity to separate keys in Keychain.
     * @param cloudKeyStorage CloudKeyStorageProtocol implementation.
     */
    constructor(identity: String, cloudKeyStorage: CloudKeyStorageProtocol) {
        this.identity = identity
        this.cloudKeyStorage = cloudKeyStorage
        this.keyStorage = KeyStorageWrapper(identity, DefaultKeyStorage())
    }

    /**
     * @param identity User's identity to separate keys in Keychain.
     * @param accessTokenProvider AccessTokenProvider implementation.
     * @param crypto Crypto
     * @param publicKeys Public keys used for encryption and signature verification.
     * @param privateKey Private key used for decryption and signature generation.
     */
    constructor(identity: String,
                accessTokenProvider: AccessTokenProvider,
                crypto: VirgilCrypto,
                publicKeys: List,
                privateKey: VirgilPrivateKey) {
        this.identity = identity
        this.cloudKeyStorage = CloudKeyStorage(accessTokenProvider = accessTokenProvider, crypto = crypto,
                                               publicKeys = publicKeys,
                                               privateKey = privateKey)
        this.keyStorage = KeyStorageWrapper(identity, DefaultKeyStorage())
    }

    /**
     * Updates entry in Keyknox Cloud and Keychain.
     *
     * @param name Entry name.
     * @param data New data.
     * @param meta New meta.
     */
    fun update(name: String, data: ByteArray, meta: Map? = null) {
        if (!this.keyStorage.exists(name)) {
            throw KeychainEntryNotFoundWhileUpdatingException()
        }
        try {
            this.cloudKeyStorage.exists(name)
        } catch (e: Exception) {
            throw CloudEntryNotFoundWhileUpdatingException()
        }
        val cloudEntry = this.cloudKeyStorage.update(name, data, meta)
        val newMeta = this.keychainUtils.createMetaForKeychain(cloudEntry)
        this.keyStorage.update(name, data, newMeta)
    }

    /**
     * Retrieves entry from Keychain.
     *
     * @param name Name.
     *
     * @return Key entry.
     */
    fun retrieve(name: String): KeyEntry {
        return this.keyStorage.retrieve(name)
    }

    /**
     * Deletes entries from both Keychain and Keyknox Cloud.
     *
     * @param names Names to delete.
     */
    fun delete(names: List) {
        names.forEach { name ->
            if (!this.keyStorage.exists(name)) {
                throw CloudEntryNotFoundWhileDeletingException()
            }
        }

        this.cloudKeyStorage.delete(names)

        names.forEach { name ->
            this.keyStorage.delete(name)
        }
    }

    /**
     * Deletes entry from both Keychain and Keyknox Cloud.
     *
     * @param name Name.
     */
    fun delete(name: String) {
        this.delete(kotlin.collections.listOf(name))
    }

    /**
     * Stores entry in both Keychain and Keyknox Cloud.
     *
     * @param name Name.
     * @param data Data.
     * @param meta Meta.
     *
     * @return Key entry.
     */
    fun store(name: String, data: ByteArray, meta: Map? = null): KeyEntry {
        val keyEntry = this.keyStorage.createEntry(name, data)
        keyEntry.meta = meta ?: mapOf()

        val keyEntries = this.store(kotlin.collections.listOf(keyEntry))
        if (keyEntries.size != 1) {
            throw EntrySavingException()
        }
        return keyEntries.first()
    }

    /**
     * Stores entries in both Keychain and Keyknox Cloud.
     *
     * @param keyEntries Key entries to store.
     *
     * @return List of stored entries.
     */
    fun store(keyEntries: List): List {
        keyEntries.forEach { keyEntry ->
            if (this.keyStorage.exists(keyEntry.name)) {
                throw KeychainEntryAlreadyExistsWhileStoringException(keyEntry.name)
            }
            if (this.cloudKeyStorage.exists(keyEntry.name)) {
                throw CloudEntryAlreadyExistsWhileStoringException(keyEntry.name)
            }
        }

        val cloudEntries = this.cloudKeyStorage.store(keyEntries)
        val keychainEntries = mutableListOf()

        val keyEntryIt = keyEntries.iterator()
        val cloudEntryIt = cloudEntries.listIterator()
        while (keyEntryIt.hasNext() && cloudEntryIt.hasNext()) {
            val keyEntry = keyEntryIt.next()
            val cloudEntry = cloudEntryIt.next()

            if (!keyEntry.name.equals(cloudEntry.name)) {
                throw InconsistentStateException()
            }

            val meta = this.keychainUtils.createMetaForKeychain(cloudEntry)
            val keychainEntry = this.keyStorage.store(keyEntry.name, keyEntry.value, meta)

            keychainEntries.add(keychainEntry)
        }

        if (keyEntries.size != cloudEntries.size) {
            throw InconsistentStateException()
        }

        return keychainEntries
    }

    /**
     * Performs synchronization between Keychain and Keyknox Cloud.
     */
    fun sync() {
        this.cloudKeyStorage.retrieveCloudEntries()
        val keychainEntries = this.keyStorage.retrieveAll().filter {
            this.keychainUtils.filterKeyknoxKeychainEntry(it)
        }
        val cloudEntries = this.cloudKeyStorage.retrieveAll()

        val keychainSet = keychainEntries.map { it.name }
        val cloudSet = cloudEntries.map { it.name }

        val entriesToDelete = keychainSet.subtract(cloudSet).toList()
        val entriesToStore = cloudSet.subtract(keychainSet).toList()
        val entriesToCompare = keychainSet.intersect(cloudSet).toList()

        this.syncDeleteEntries(entriesToDelete)
        this.syncStoreEntries(entriesToStore)
        this.syncCompareEntries(entriesToCompare, keychainEntries)
    }

    /**
     * Updates recipients. See KeyknoxManager.updateRecipients.
     *
     * @param newPublicKeys New public keys.
     * @param newPrivateKey New private key.
     */
    fun updateRecipients(newPublicKeys: List? = null, newPrivateKey: VirgilPrivateKey? = null) {
        this.cloudKeyStorage.updateRecipients(newPublicKeys, newPrivateKey)
    }

    /**
     * Retrieves all entries from Keychain.
     *
     * @return Keychain entries.
     */
    fun retrieveAll(): List {
        return this.keyStorage.retrieveAll()
    }

    /**
     * Checks if entry exists in Keychain.
     *
     * @param name Entry name.
     *
     * @return True if entry exists, false - otherwise.
     */
    fun exists(name: String): Boolean {
        return this.keyStorage.exists(name)
    }

    /**
     * Deletes all entries in both Keychain and Keyknox Cloud.
     */
    fun deleteAll() {
        this.cloudKeyStorage.deleteAll()

        val entriesToDelete = this.keyStorage.retrieveAll()
                .filter { this.keychainUtils.filterKeyknoxKeychainEntry(it) }
                .map { it.name }

        this.syncDeleteEntries(entriesToDelete)
    }

    private fun syncDeleteEntries(entriesToDelete: List) {
        entriesToDelete.forEach {
            this.keyStorage.delete(it)
        }
    }

    private fun syncStoreEntries(entriesToStore: List) {
        entriesToStore.forEach { name ->
            val cloudEntry = this.cloudKeyStorage.retrieve(name)

            val meta = this.keychainUtils.createMetaForKeychain(cloudEntry)
            this.keyStorage.store(cloudEntry.name, cloudEntry.data, meta)
        }
    }

    private fun syncCompareEntries(entriesToCompare: List, keychainEntries: List) {
        // Determine newest version and either update keychain entry or upload newer version to cloud
        entriesToCompare.forEach { name ->
            val keychainEntry = keychainEntries.firstOrNull { name == it.name }
                    ?: throw KeychainEntryNotFoundWhileComparingException()
            val cloudEntry = this.cloudKeyStorage.retrieve(name)
            val keychainDate = this.keychainUtils.extractModificationDate(keychainEntry)

            if (keychainDate.second < cloudEntry.modificationDate) {
                val meta = this.keychainUtils.createMetaForKeychain(cloudEntry)
                this.keyStorage.update(cloudEntry.name, cloudEntry.data, meta)
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy