Move tink library into core module and expose via CoreCrypto
This also moves key derivation via HKDF into the core.
This commit is contained in:
parent
c19787a7fa
commit
e6905c0365
35 changed files with 276 additions and 114 deletions
|
@ -5,23 +5,33 @@
|
||||||
|
|
||||||
package com.stevesoltys.seedvault.crypto
|
package com.stevesoltys.seedvault.crypto
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.provider.Settings.Secure.ANDROID_ID
|
||||||
import com.google.crypto.tink.subtle.AesGcmHkdfStreaming
|
import com.google.crypto.tink.subtle.AesGcmHkdfStreaming
|
||||||
import com.stevesoltys.seedvault.encodeBase64
|
import com.stevesoltys.seedvault.encodeBase64
|
||||||
import com.stevesoltys.seedvault.header.HeaderReader
|
import com.stevesoltys.seedvault.header.HeaderReader
|
||||||
import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH
|
import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH
|
||||||
import com.stevesoltys.seedvault.header.MAX_VERSION_HEADER_SIZE
|
import com.stevesoltys.seedvault.header.MAX_VERSION_HEADER_SIZE
|
||||||
import com.stevesoltys.seedvault.header.SegmentHeader
|
import com.stevesoltys.seedvault.header.SegmentHeader
|
||||||
|
import com.stevesoltys.seedvault.header.VERSION
|
||||||
import com.stevesoltys.seedvault.header.VersionHeader
|
import com.stevesoltys.seedvault.header.VersionHeader
|
||||||
import org.calyxos.backup.storage.crypto.StreamCrypto
|
import org.calyxos.seedvault.core.crypto.CoreCrypto
|
||||||
import org.calyxos.backup.storage.crypto.StreamCrypto.deriveStreamKey
|
import org.calyxos.seedvault.core.crypto.CoreCrypto.ALGORITHM_HMAC
|
||||||
|
import org.calyxos.seedvault.core.crypto.CoreCrypto.deriveKey
|
||||||
|
import org.calyxos.seedvault.core.toByteArrayFromHex
|
||||||
|
import org.calyxos.seedvault.core.toHexString
|
||||||
import java.io.EOFException
|
import java.io.EOFException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import java.security.GeneralSecurityException
|
import java.security.GeneralSecurityException
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
import javax.crypto.Mac
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,13 +57,18 @@ internal interface Crypto {
|
||||||
*/
|
*/
|
||||||
fun getRandomBytes(size: Int): ByteArray
|
fun getRandomBytes(size: Int): ByteArray
|
||||||
|
|
||||||
fun getNameForPackage(salt: String, packageName: String): String
|
/**
|
||||||
|
* Returns the ID of the backup repository as a 64 char hex string.
|
||||||
|
*/
|
||||||
|
val repoId: String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name that identifies an APK in the backup storage plugin.
|
* A secret key of size [KEY_SIZE_BYTES]
|
||||||
* @param suffix empty string for normal APKs and the name of the split in case of an APK split
|
* only used to create a gear table specific to each main key.
|
||||||
*/
|
*/
|
||||||
fun getNameForApk(salt: String, packageName: String, suffix: String = ""): String
|
val gearTableKey: ByteArray
|
||||||
|
|
||||||
|
fun sha256(bytes: ByteArray): ByteArray
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a [AesGcmHkdfStreaming] encrypting stream
|
* Returns a [AesGcmHkdfStreaming] encrypting stream
|
||||||
|
@ -75,6 +90,40 @@ internal interface Crypto {
|
||||||
associatedData: ByteArray,
|
associatedData: ByteArray,
|
||||||
): InputStream
|
): InputStream
|
||||||
|
|
||||||
|
fun getAdForVersion(version: Byte = VERSION): ByteArray
|
||||||
|
|
||||||
|
@Deprecated("only for v1")
|
||||||
|
fun getNameForPackage(salt: String, packageName: String): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name that identifies an APK in the backup storage plugin.
|
||||||
|
* @param suffix empty string for normal APKs and the name of the split in case of an APK split
|
||||||
|
*/
|
||||||
|
@Deprecated("only for v1")
|
||||||
|
fun getNameForApk(salt: String, packageName: String, suffix: String = ""): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a [AesGcmHkdfStreaming] encrypting stream
|
||||||
|
* that gets encrypted and authenticated the given associated data.
|
||||||
|
*/
|
||||||
|
@Deprecated("only for v1")
|
||||||
|
@Throws(IOException::class, GeneralSecurityException::class)
|
||||||
|
fun newEncryptingStreamV1(
|
||||||
|
outputStream: OutputStream,
|
||||||
|
associatedData: ByteArray,
|
||||||
|
): OutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a [AesGcmHkdfStreaming] decrypting stream
|
||||||
|
* that gets decrypted and authenticated the given associated data.
|
||||||
|
*/
|
||||||
|
@Deprecated("only for v1")
|
||||||
|
@Throws(IOException::class, GeneralSecurityException::class)
|
||||||
|
fun newDecryptingStreamV1(
|
||||||
|
inputStream: InputStream,
|
||||||
|
associatedData: ByteArray,
|
||||||
|
): InputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads and decrypts a [VersionHeader] from the given [InputStream]
|
* Reads and decrypts a [VersionHeader] from the given [InputStream]
|
||||||
* and ensures that the expected version, package name and key match
|
* and ensures that the expected version, package name and key match
|
||||||
|
@ -123,29 +172,64 @@ internal const val TYPE_BACKUP_FULL: Byte = 0x02
|
||||||
internal const val TYPE_ICONS: Byte = 0x03
|
internal const val TYPE_ICONS: Byte = 0x03
|
||||||
|
|
||||||
internal class CryptoImpl(
|
internal class CryptoImpl(
|
||||||
|
private val context: Context,
|
||||||
private val keyManager: KeyManager,
|
private val keyManager: KeyManager,
|
||||||
private val cipherFactory: CipherFactory,
|
private val cipherFactory: CipherFactory,
|
||||||
private val headerReader: HeaderReader,
|
private val headerReader: HeaderReader,
|
||||||
) : Crypto {
|
) : Crypto {
|
||||||
|
|
||||||
private val key: ByteArray by lazy {
|
private val keyV1: ByteArray by lazy {
|
||||||
deriveStreamKey(keyManager.getMainKey(), "app data key".toByteArray())
|
deriveKey(keyManager.getMainKey(), "app data key".toByteArray())
|
||||||
}
|
}
|
||||||
private val secureRandom: SecureRandom by lazy { SecureRandom() }
|
private val streamKey: ByteArray by lazy {
|
||||||
|
deriveKey(keyManager.getMainKey(), "app backup stream key".toByteArray())
|
||||||
|
}
|
||||||
|
private val secureRandom: SecureRandom by lazy { SecureRandom.getInstanceStrong() }
|
||||||
|
|
||||||
override fun getRandomBytes(size: Int) = ByteArray(size).apply {
|
override fun getRandomBytes(size: Int) = ByteArray(size).apply {
|
||||||
secureRandom.nextBytes(this)
|
secureRandom.nextBytes(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val repoId: String
|
||||||
|
get() { // TODO maybe cache this, but what if main key changes during run-time?
|
||||||
|
@SuppressLint("HardwareIds")
|
||||||
|
val androidId = Settings.Secure.getString(context.contentResolver, ANDROID_ID)
|
||||||
|
val repoIdKey =
|
||||||
|
deriveKey(keyManager.getMainKey(), "app backup repoId key".toByteArray())
|
||||||
|
val hmacHasher: Mac = Mac.getInstance(ALGORITHM_HMAC).apply {
|
||||||
|
init(SecretKeySpec(repoIdKey, ALGORITHM_HMAC))
|
||||||
|
}
|
||||||
|
return hmacHasher.doFinal(androidId.toByteArrayFromHex()).toHexString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val gearTableKey: ByteArray
|
||||||
|
get() = deriveKey(keyManager.getMainKey(), "app backup gear table key".toByteArray())
|
||||||
|
|
||||||
|
override fun newEncryptingStream(
|
||||||
|
outputStream: OutputStream,
|
||||||
|
associatedData: ByteArray,
|
||||||
|
): OutputStream = CoreCrypto.newEncryptingStream(streamKey, outputStream, associatedData)
|
||||||
|
|
||||||
|
override fun newDecryptingStream(
|
||||||
|
inputStream: InputStream,
|
||||||
|
associatedData: ByteArray,
|
||||||
|
): InputStream = CoreCrypto.newDecryptingStream(streamKey, inputStream, associatedData)
|
||||||
|
|
||||||
|
override fun getAdForVersion(version: Byte): ByteArray = ByteBuffer.allocate(1)
|
||||||
|
.put(version)
|
||||||
|
.array()
|
||||||
|
|
||||||
|
@Deprecated("only for v1")
|
||||||
override fun getNameForPackage(salt: String, packageName: String): String {
|
override fun getNameForPackage(salt: String, packageName: String): String {
|
||||||
return sha256("$salt$packageName".toByteArray()).encodeBase64()
|
return sha256("$salt$packageName".toByteArray()).encodeBase64()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("only for v1")
|
||||||
override fun getNameForApk(salt: String, packageName: String, suffix: String): String {
|
override fun getNameForApk(salt: String, packageName: String, suffix: String): String {
|
||||||
return sha256("${salt}APK$packageName$suffix".toByteArray()).encodeBase64()
|
return sha256("${salt}APK$packageName$suffix".toByteArray()).encodeBase64()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sha256(bytes: ByteArray): ByteArray {
|
override fun sha256(bytes: ByteArray): ByteArray {
|
||||||
val messageDigest: MessageDigest = try {
|
val messageDigest: MessageDigest = try {
|
||||||
MessageDigest.getInstance("SHA-256")
|
MessageDigest.getInstance("SHA-256")
|
||||||
} catch (e: NoSuchAlgorithmException) {
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
|
@ -155,21 +239,19 @@ internal class CryptoImpl(
|
||||||
return messageDigest.digest()
|
return messageDigest.digest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("only for v1")
|
||||||
@Throws(IOException::class, GeneralSecurityException::class)
|
@Throws(IOException::class, GeneralSecurityException::class)
|
||||||
override fun newEncryptingStream(
|
override fun newEncryptingStreamV1(
|
||||||
outputStream: OutputStream,
|
outputStream: OutputStream,
|
||||||
associatedData: ByteArray,
|
associatedData: ByteArray,
|
||||||
): OutputStream {
|
): OutputStream = CoreCrypto.newEncryptingStream(keyV1, outputStream, associatedData)
|
||||||
return StreamCrypto.newEncryptingStream(key, outputStream, associatedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Deprecated("only for v1")
|
||||||
@Throws(IOException::class, GeneralSecurityException::class)
|
@Throws(IOException::class, GeneralSecurityException::class)
|
||||||
override fun newDecryptingStream(
|
override fun newDecryptingStreamV1(
|
||||||
inputStream: InputStream,
|
inputStream: InputStream,
|
||||||
associatedData: ByteArray,
|
associatedData: ByteArray,
|
||||||
): InputStream {
|
): InputStream = CoreCrypto.newDecryptingStream(keyV1, inputStream, associatedData)
|
||||||
return StreamCrypto.newDecryptingStream(key, inputStream, associatedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("Deprecation")
|
@Suppress("Deprecation")
|
||||||
@Throws(IOException::class, SecurityException::class)
|
@Throws(IOException::class, SecurityException::class)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package com.stevesoltys.seedvault.crypto
|
package com.stevesoltys.seedvault.crypto
|
||||||
|
|
||||||
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
|
|
||||||
|
@ -20,5 +21,5 @@ val cryptoModule = module {
|
||||||
}
|
}
|
||||||
KeyManagerImpl(keyStore)
|
KeyManagerImpl(keyStore)
|
||||||
}
|
}
|
||||||
single<Crypto> { CryptoImpl(get(), get(), get()) }
|
single<Crypto> { CryptoImpl(androidContext(), get(), get(), get()) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
|
||||||
if (version == 0.toByte()) return readMetadataV0(inputStream, expectedToken)
|
if (version == 0.toByte()) return readMetadataV0(inputStream, expectedToken)
|
||||||
|
|
||||||
val metadataBytes = try {
|
val metadataBytes = try {
|
||||||
crypto.newDecryptingStream(inputStream, getAD(version, expectedToken)).readBytes()
|
crypto.newDecryptingStreamV1(inputStream, getAD(version, expectedToken)).readBytes()
|
||||||
} catch (e: GeneralSecurityException) {
|
} catch (e: GeneralSecurityException) {
|
||||||
throw DecryptionFailedException(e)
|
throw DecryptionFailedException(e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ internal class MetadataWriterImpl(private val crypto: Crypto) : MetadataWriter {
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun write(metadata: BackupMetadata, outputStream: OutputStream) {
|
override fun write(metadata: BackupMetadata, outputStream: OutputStream) {
|
||||||
outputStream.write(ByteArray(1).apply { this[0] = metadata.version })
|
outputStream.write(ByteArray(1).apply { this[0] = metadata.version })
|
||||||
crypto.newEncryptingStream(outputStream, getAD(metadata.version, metadata.token)).use {
|
crypto.newEncryptingStreamV1(outputStream, getAD(metadata.version, metadata.token)).use {
|
||||||
it.write(encode(metadata))
|
it.write(encode(metadata))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,7 +139,7 @@ internal class FullBackup(
|
||||||
// store version header
|
// store version header
|
||||||
val state = this.state ?: throw AssertionError()
|
val state = this.state ?: throw AssertionError()
|
||||||
outputStream.write(ByteArray(1) { VERSION })
|
outputStream.write(ByteArray(1) { VERSION })
|
||||||
crypto.newEncryptingStream(outputStream, getADForFull(VERSION, state.packageName))
|
crypto.newEncryptingStreamV1(outputStream, getADForFull(VERSION, state.packageName))
|
||||||
} // this lambda is only called before we actually write backup data the first time
|
} // this lambda is only called before we actually write backup data the first time
|
||||||
return TRANSPORT_OK
|
return TRANSPORT_OK
|
||||||
}
|
}
|
||||||
|
|
|
@ -259,7 +259,7 @@ internal class KVBackup(
|
||||||
backend.save(handle).use { outputStream ->
|
backend.save(handle).use { outputStream ->
|
||||||
outputStream.write(ByteArray(1) { VERSION })
|
outputStream.write(ByteArray(1) { VERSION })
|
||||||
val ad = getADForKV(VERSION, packageName)
|
val ad = getADForKV(VERSION, packageName)
|
||||||
crypto.newEncryptingStream(outputStream, ad).use { encryptedStream ->
|
crypto.newEncryptingStreamV1(outputStream, ad).use { encryptedStream ->
|
||||||
GZIPOutputStream(encryptedStream).use { gZipStream ->
|
GZIPOutputStream(encryptedStream).use { gZipStream ->
|
||||||
dbManager.getDbInputStream(packageName).use { inputStream ->
|
dbManager.getDbInputStream(packageName).use { inputStream ->
|
||||||
inputStream.copyTo(gZipStream)
|
inputStream.copyTo(gZipStream)
|
||||||
|
|
|
@ -119,7 +119,7 @@ internal class FullRestore(
|
||||||
val inputStream = backend.load(handle)
|
val inputStream = backend.load(handle)
|
||||||
val version = headerReader.readVersion(inputStream, state.version)
|
val version = headerReader.readVersion(inputStream, state.version)
|
||||||
val ad = getADForFull(version, packageName)
|
val ad = getADForFull(version, packageName)
|
||||||
state.inputStream = crypto.newDecryptingStream(inputStream, ad)
|
state.inputStream = crypto.newDecryptingStreamV1(inputStream, ad)
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.w(TAG, "Error getting input stream for $packageName", e)
|
Log.w(TAG, "Error getting input stream for $packageName", e)
|
||||||
|
|
|
@ -161,7 +161,7 @@ internal class KVRestore(
|
||||||
backend.load(handle).use { inputStream ->
|
backend.load(handle).use { inputStream ->
|
||||||
headerReader.readVersion(inputStream, state.version)
|
headerReader.readVersion(inputStream, state.version)
|
||||||
val ad = getADForKV(VERSION, packageName)
|
val ad = getADForKV(VERSION, packageName)
|
||||||
crypto.newDecryptingStream(inputStream, ad).use { decryptedStream ->
|
crypto.newDecryptingStreamV1(inputStream, ad).use { decryptedStream ->
|
||||||
GZIPInputStream(decryptedStream).use { gzipStream ->
|
GZIPInputStream(decryptedStream).use { gzipStream ->
|
||||||
dbManager.getDbOutputStream(packageName).use { outputStream ->
|
dbManager.getDbOutputStream(packageName).use { outputStream ->
|
||||||
gzipStream.copyTo(outputStream)
|
gzipStream.copyTo(outputStream)
|
||||||
|
|
|
@ -49,7 +49,7 @@ internal class IconManager(
|
||||||
fun uploadIcons(token: Long, outputStream: OutputStream) {
|
fun uploadIcons(token: Long, outputStream: OutputStream) {
|
||||||
Log.d(TAG, "Start uploading icons")
|
Log.d(TAG, "Start uploading icons")
|
||||||
val packageManager = context.packageManager
|
val packageManager = context.packageManager
|
||||||
crypto.newEncryptingStream(outputStream, getAD(VERSION, token)).use { cryptoStream ->
|
crypto.newEncryptingStreamV1(outputStream, getAD(VERSION, token)).use { cryptoStream ->
|
||||||
ZipOutputStream(cryptoStream).use { zip ->
|
ZipOutputStream(cryptoStream).use { zip ->
|
||||||
zip.setLevel(BEST_SPEED)
|
zip.setLevel(BEST_SPEED)
|
||||||
val entries = mutableSetOf<String>()
|
val entries = mutableSetOf<String>()
|
||||||
|
@ -89,7 +89,7 @@ internal class IconManager(
|
||||||
if (!folder.isDirectory && !folder.mkdirs())
|
if (!folder.isDirectory && !folder.mkdirs())
|
||||||
throw IOException("Can't create cache folder for icons")
|
throw IOException("Can't create cache folder for icons")
|
||||||
val set = mutableSetOf<String>()
|
val set = mutableSetOf<String>()
|
||||||
crypto.newDecryptingStream(inputStream, getAD(version, token)).use { cryptoStream ->
|
crypto.newDecryptingStreamV1(inputStream, getAD(version, token)).use { cryptoStream ->
|
||||||
ZipInputStream(cryptoStream).use { zip ->
|
ZipInputStream(cryptoStream).use { zip ->
|
||||||
var entry = zip.nextEntry
|
var entry = zip.nextEntry
|
||||||
while (entry != null) {
|
while (entry != null) {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package com.stevesoltys.seedvault
|
package com.stevesoltys.seedvault
|
||||||
|
|
||||||
|
import com.stevesoltys.seedvault.backend.saf.storagePluginModuleSaf
|
||||||
import com.stevesoltys.seedvault.crypto.CipherFactory
|
import com.stevesoltys.seedvault.crypto.CipherFactory
|
||||||
import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
|
import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
|
||||||
import com.stevesoltys.seedvault.crypto.Crypto
|
import com.stevesoltys.seedvault.crypto.Crypto
|
||||||
|
@ -13,7 +14,6 @@ import com.stevesoltys.seedvault.crypto.KeyManager
|
||||||
import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl
|
import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl
|
||||||
import com.stevesoltys.seedvault.header.headerModule
|
import com.stevesoltys.seedvault.header.headerModule
|
||||||
import com.stevesoltys.seedvault.metadata.metadataModule
|
import com.stevesoltys.seedvault.metadata.metadataModule
|
||||||
import com.stevesoltys.seedvault.backend.saf.storagePluginModuleSaf
|
|
||||||
import com.stevesoltys.seedvault.restore.install.installModule
|
import com.stevesoltys.seedvault.restore.install.installModule
|
||||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.transport.backup.PackageService
|
import com.stevesoltys.seedvault.transport.backup.PackageService
|
||||||
|
@ -33,7 +33,7 @@ class TestApp : App() {
|
||||||
private val testCryptoModule = module {
|
private val testCryptoModule = module {
|
||||||
factory<CipherFactory> { CipherFactoryImpl(get()) }
|
factory<CipherFactory> { CipherFactoryImpl(get()) }
|
||||||
single<KeyManager> { KeyManagerTestImpl() }
|
single<KeyManager> { KeyManagerTestImpl() }
|
||||||
single<Crypto> { CryptoImpl(get(), get(), get()) }
|
single<Crypto> { CryptoImpl(this@TestApp, get(), get(), get()) }
|
||||||
}
|
}
|
||||||
private val packageService: PackageService = mockk()
|
private val packageService: PackageService = mockk()
|
||||||
private val appModule = module {
|
private val appModule = module {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package com.stevesoltys.seedvault.crypto
|
package com.stevesoltys.seedvault.crypto
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import com.stevesoltys.seedvault.getRandomBase64
|
import com.stevesoltys.seedvault.getRandomBase64
|
||||||
import com.stevesoltys.seedvault.getRandomString
|
import com.stevesoltys.seedvault.getRandomString
|
||||||
import com.stevesoltys.seedvault.header.HeaderReaderImpl
|
import com.stevesoltys.seedvault.header.HeaderReaderImpl
|
||||||
|
@ -19,14 +20,16 @@ import org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
@TestInstance(PER_METHOD)
|
@TestInstance(PER_METHOD)
|
||||||
class CryptoImplTest {
|
class CryptoImplTest {
|
||||||
|
|
||||||
|
private val context = mockk<Context>()
|
||||||
private val keyManager = mockk<KeyManager>()
|
private val keyManager = mockk<KeyManager>()
|
||||||
private val cipherFactory = mockk<CipherFactory>()
|
private val cipherFactory = mockk<CipherFactory>()
|
||||||
private val headerReader = HeaderReaderImpl()
|
private val headerReader = HeaderReaderImpl()
|
||||||
|
|
||||||
private val crypto = CryptoImpl(keyManager, cipherFactory, headerReader)
|
private val crypto = CryptoImpl(context, keyManager, cipherFactory, headerReader)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `decrypting multiple segments on empty stream throws`() {
|
fun `decrypting multiple segments on empty stream throws`() {
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
|
|
||||||
package com.stevesoltys.seedvault.crypto
|
package com.stevesoltys.seedvault.crypto
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import com.stevesoltys.seedvault.assertReadEquals
|
import com.stevesoltys.seedvault.assertReadEquals
|
||||||
import com.stevesoltys.seedvault.header.HeaderReaderImpl
|
import com.stevesoltys.seedvault.header.HeaderReaderImpl
|
||||||
|
import io.mockk.mockk
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
import org.hamcrest.Matchers.equalTo
|
import org.hamcrest.Matchers.equalTo
|
||||||
import org.hamcrest.Matchers.not
|
import org.hamcrest.Matchers.not
|
||||||
|
@ -19,13 +21,15 @@ import java.io.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
@TestInstance(PER_METHOD)
|
@TestInstance(PER_METHOD)
|
||||||
class CryptoIntegrationTest {
|
class CryptoIntegrationTest {
|
||||||
|
|
||||||
|
private val context = mockk<Context>()
|
||||||
private val keyManager = KeyManagerTestImpl()
|
private val keyManager = KeyManagerTestImpl()
|
||||||
private val cipherFactory = CipherFactoryImpl(keyManager)
|
private val cipherFactory = CipherFactoryImpl(keyManager)
|
||||||
private val headerReader = HeaderReaderImpl()
|
private val headerReader = HeaderReaderImpl()
|
||||||
private val crypto = CryptoImpl(keyManager, cipherFactory, headerReader)
|
private val crypto = CryptoImpl(context, keyManager, cipherFactory, headerReader)
|
||||||
|
|
||||||
private val cleartext = Random.nextBytes(Random.nextInt(1, 422300))
|
private val cleartext = Random.nextBytes(Random.nextInt(1, 422300))
|
||||||
|
|
||||||
|
@ -38,7 +42,18 @@ class CryptoIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `decrypting encrypted cleartext works`() {
|
fun `decrypting encrypted cleartext works v1`() {
|
||||||
|
val ad = Random.nextBytes(42)
|
||||||
|
val outputStream = ByteArrayOutputStream()
|
||||||
|
crypto.newEncryptingStreamV1(outputStream, ad).use { it.write(cleartext) }
|
||||||
|
val inputStream = ByteArrayInputStream(outputStream.toByteArray())
|
||||||
|
crypto.newDecryptingStreamV1(inputStream, ad).use {
|
||||||
|
assertReadEquals(cleartext, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `decrypting encrypted cleartext works v2`() {
|
||||||
val ad = Random.nextBytes(42)
|
val ad = Random.nextBytes(42)
|
||||||
val outputStream = ByteArrayOutputStream()
|
val outputStream = ByteArrayOutputStream()
|
||||||
crypto.newEncryptingStream(outputStream, ad).use { it.write(cleartext) }
|
crypto.newEncryptingStream(outputStream, ad).use { it.write(cleartext) }
|
||||||
|
@ -49,7 +64,19 @@ class CryptoIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `decrypting encrypted cleartext fails with different AD`() {
|
fun `decrypting encrypted cleartext fails with different AD v1`() {
|
||||||
|
val outputStream = ByteArrayOutputStream()
|
||||||
|
crypto.newEncryptingStreamV1(outputStream, Random.nextBytes(42)).use { it.write(cleartext) }
|
||||||
|
val inputStream = ByteArrayInputStream(outputStream.toByteArray())
|
||||||
|
assertThrows(IOException::class.java) {
|
||||||
|
crypto.newDecryptingStreamV1(inputStream, Random.nextBytes(41)).use {
|
||||||
|
it.read()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `decrypting encrypted cleartext fails with different AD v2`() {
|
||||||
val outputStream = ByteArrayOutputStream()
|
val outputStream = ByteArrayOutputStream()
|
||||||
crypto.newEncryptingStream(outputStream, Random.nextBytes(42)).use { it.write(cleartext) }
|
crypto.newEncryptingStream(outputStream, Random.nextBytes(42)).use { it.write(cleartext) }
|
||||||
val inputStream = ByteArrayInputStream(outputStream.toByteArray())
|
val inputStream = ByteArrayInputStream(outputStream.toByteArray())
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package com.stevesoltys.seedvault.crypto
|
package com.stevesoltys.seedvault.crypto
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import com.stevesoltys.seedvault.assertContains
|
import com.stevesoltys.seedvault.assertContains
|
||||||
import com.stevesoltys.seedvault.getRandomByteArray
|
import com.stevesoltys.seedvault.getRandomByteArray
|
||||||
import com.stevesoltys.seedvault.getRandomString
|
import com.stevesoltys.seedvault.getRandomString
|
||||||
|
@ -36,11 +37,12 @@ import kotlin.random.Random
|
||||||
@TestInstance(PER_METHOD)
|
@TestInstance(PER_METHOD)
|
||||||
class CryptoTest {
|
class CryptoTest {
|
||||||
|
|
||||||
|
private val context = mockk<Context>()
|
||||||
private val keyManager = mockk<KeyManager>()
|
private val keyManager = mockk<KeyManager>()
|
||||||
private val cipherFactory = mockk<CipherFactory>()
|
private val cipherFactory = mockk<CipherFactory>()
|
||||||
private val headerReader = mockk<HeaderReader>()
|
private val headerReader = mockk<HeaderReader>()
|
||||||
|
|
||||||
private val crypto = CryptoImpl(keyManager, cipherFactory, headerReader)
|
private val crypto = CryptoImpl(context, keyManager, cipherFactory, headerReader)
|
||||||
|
|
||||||
private val cipher = mockk<Cipher>()
|
private val cipher = mockk<Cipher>()
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package com.stevesoltys.seedvault.metadata
|
package com.stevesoltys.seedvault.metadata
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
|
import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
|
||||||
import com.stevesoltys.seedvault.crypto.CryptoImpl
|
import com.stevesoltys.seedvault.crypto.CryptoImpl
|
||||||
import com.stevesoltys.seedvault.crypto.KEY_SIZE_BYTES
|
import com.stevesoltys.seedvault.crypto.KEY_SIZE_BYTES
|
||||||
|
@ -15,6 +16,7 @@ import com.stevesoltys.seedvault.header.HeaderReaderImpl
|
||||||
import com.stevesoltys.seedvault.header.VERSION
|
import com.stevesoltys.seedvault.header.VERSION
|
||||||
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
|
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
|
||||||
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
|
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
|
||||||
|
import io.mockk.mockk
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.TestInstance
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
@ -30,10 +32,11 @@ internal class MetadataReadWriteTest {
|
||||||
private val secretKey = SecretKeySpec(
|
private val secretKey = SecretKeySpec(
|
||||||
"This is a legacy backup key 1234".toByteArray(), 0, KEY_SIZE_BYTES, "AES"
|
"This is a legacy backup key 1234".toByteArray(), 0, KEY_SIZE_BYTES, "AES"
|
||||||
)
|
)
|
||||||
|
private val context = mockk<Context>()
|
||||||
private val keyManager = KeyManagerTestImpl(secretKey)
|
private val keyManager = KeyManagerTestImpl(secretKey)
|
||||||
private val cipherFactory = CipherFactoryImpl(keyManager)
|
private val cipherFactory = CipherFactoryImpl(keyManager)
|
||||||
private val headerReader = HeaderReaderImpl()
|
private val headerReader = HeaderReaderImpl()
|
||||||
private val cryptoImpl = CryptoImpl(keyManager, cipherFactory, headerReader)
|
private val cryptoImpl = CryptoImpl(context, keyManager, cipherFactory, headerReader)
|
||||||
|
|
||||||
private val writer = MetadataWriterImpl(cryptoImpl)
|
private val writer = MetadataWriterImpl(cryptoImpl)
|
||||||
private val reader = MetadataReaderImpl(cryptoImpl)
|
private val reader = MetadataReaderImpl(cryptoImpl)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package com.stevesoltys.seedvault.metadata
|
package com.stevesoltys.seedvault.metadata
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
|
import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
|
||||||
import com.stevesoltys.seedvault.crypto.CryptoImpl
|
import com.stevesoltys.seedvault.crypto.CryptoImpl
|
||||||
import com.stevesoltys.seedvault.crypto.KEY_SIZE_BYTES
|
import com.stevesoltys.seedvault.crypto.KEY_SIZE_BYTES
|
||||||
|
@ -13,6 +14,7 @@ import com.stevesoltys.seedvault.header.HeaderReaderImpl
|
||||||
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
|
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
|
||||||
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
|
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
|
||||||
import com.stevesoltys.seedvault.toByteArrayFromHex
|
import com.stevesoltys.seedvault.toByteArrayFromHex
|
||||||
|
import io.mockk.mockk
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.TestInstance
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
@ -29,10 +31,11 @@ internal class MetadataV0ReadTest {
|
||||||
private val secretKey = SecretKeySpec(
|
private val secretKey = SecretKeySpec(
|
||||||
"This is a legacy backup key 1234".toByteArray(), 0, KEY_SIZE_BYTES, "AES"
|
"This is a legacy backup key 1234".toByteArray(), 0, KEY_SIZE_BYTES, "AES"
|
||||||
)
|
)
|
||||||
|
private val context = mockk<Context>()
|
||||||
private val keyManager = KeyManagerTestImpl(secretKey)
|
private val keyManager = KeyManagerTestImpl(secretKey)
|
||||||
private val cipherFactory = CipherFactoryImpl(keyManager)
|
private val cipherFactory = CipherFactoryImpl(keyManager)
|
||||||
private val headerReader = HeaderReaderImpl()
|
private val headerReader = HeaderReaderImpl()
|
||||||
private val cryptoImpl = CryptoImpl(keyManager, cipherFactory, headerReader)
|
private val cryptoImpl = CryptoImpl(context, keyManager, cipherFactory, headerReader)
|
||||||
|
|
||||||
private val reader = MetadataReaderImpl(cryptoImpl)
|
private val reader = MetadataReaderImpl(cryptoImpl)
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ import android.app.backup.BackupTransport.TRANSPORT_OK
|
||||||
import android.app.backup.RestoreDescription
|
import android.app.backup.RestoreDescription
|
||||||
import android.app.backup.RestoreDescription.TYPE_FULL_STREAM
|
import android.app.backup.RestoreDescription.TYPE_FULL_STREAM
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
|
import com.stevesoltys.seedvault.backend.BackendManager
|
||||||
|
import com.stevesoltys.seedvault.backend.LegacyStoragePlugin
|
||||||
import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
|
import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
|
||||||
import com.stevesoltys.seedvault.crypto.CryptoImpl
|
import com.stevesoltys.seedvault.crypto.CryptoImpl
|
||||||
import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl
|
import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl
|
||||||
|
@ -20,8 +22,6 @@ import com.stevesoltys.seedvault.header.MAX_SEGMENT_CLEARTEXT_LENGTH
|
||||||
import com.stevesoltys.seedvault.metadata.BackupType
|
import com.stevesoltys.seedvault.metadata.BackupType
|
||||||
import com.stevesoltys.seedvault.metadata.MetadataReaderImpl
|
import com.stevesoltys.seedvault.metadata.MetadataReaderImpl
|
||||||
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
||||||
import com.stevesoltys.seedvault.backend.LegacyStoragePlugin
|
|
||||||
import com.stevesoltys.seedvault.backend.BackendManager
|
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
|
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
|
||||||
import com.stevesoltys.seedvault.transport.backup.FullBackup
|
import com.stevesoltys.seedvault.transport.backup.FullBackup
|
||||||
import com.stevesoltys.seedvault.transport.backup.InputFactory
|
import com.stevesoltys.seedvault.transport.backup.InputFactory
|
||||||
|
@ -59,7 +59,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
private val keyManager = KeyManagerTestImpl()
|
private val keyManager = KeyManagerTestImpl()
|
||||||
private val cipherFactory = CipherFactoryImpl(keyManager)
|
private val cipherFactory = CipherFactoryImpl(keyManager)
|
||||||
private val headerReader = HeaderReaderImpl()
|
private val headerReader = HeaderReaderImpl()
|
||||||
private val cryptoImpl = CryptoImpl(keyManager, cipherFactory, headerReader)
|
private val cryptoImpl = CryptoImpl(context, keyManager, cipherFactory, headerReader)
|
||||||
private val metadataReader = MetadataReaderImpl(cryptoImpl)
|
private val metadataReader = MetadataReaderImpl(cryptoImpl)
|
||||||
private val notificationManager = mockk<BackupNotificationManager>()
|
private val notificationManager = mockk<BackupNotificationManager>()
|
||||||
private val dbManager = TestKvDbManager()
|
private val dbManager = TestKvDbManager()
|
||||||
|
|
|
@ -150,7 +150,7 @@ internal class FullBackupTest : BackupTest() {
|
||||||
every { inputFactory.getInputStream(data) } returns inputStream
|
every { inputFactory.getInputStream(data) } returns inputStream
|
||||||
expectInitializeOutputStream()
|
expectInitializeOutputStream()
|
||||||
every { settingsManager.isQuotaUnlimited() } returns false
|
every { settingsManager.isQuotaUnlimited() } returns false
|
||||||
every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream
|
every { crypto.newEncryptingStreamV1(outputStream, ad) } returns encryptedOutputStream
|
||||||
every { inputStream.read(any(), any(), bytes.size) } throws IOException()
|
every { inputStream.read(any(), any(), bytes.size) } throws IOException()
|
||||||
expectClearState()
|
expectClearState()
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ internal class FullBackupTest : BackupTest() {
|
||||||
every { inputFactory.getInputStream(data) } returns inputStream
|
every { inputFactory.getInputStream(data) } returns inputStream
|
||||||
expectInitializeOutputStream()
|
expectInitializeOutputStream()
|
||||||
every { settingsManager.isQuotaUnlimited() } returns false
|
every { settingsManager.isQuotaUnlimited() } returns false
|
||||||
every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream
|
every { crypto.newEncryptingStreamV1(outputStream, ad) } returns encryptedOutputStream
|
||||||
every { inputStream.read(any(), any(), bytes.size) } returns bytes.size
|
every { inputStream.read(any(), any(), bytes.size) } returns bytes.size
|
||||||
every { encryptedOutputStream.write(any<ByteArray>()) } throws IOException()
|
every { encryptedOutputStream.write(any<ByteArray>()) } throws IOException()
|
||||||
expectClearState()
|
expectClearState()
|
||||||
|
@ -345,7 +345,7 @@ internal class FullBackupTest : BackupTest() {
|
||||||
|
|
||||||
private fun expectSendData(numBytes: Int, readBytes: Int = numBytes) {
|
private fun expectSendData(numBytes: Int, readBytes: Int = numBytes) {
|
||||||
every { inputStream.read(any(), any(), numBytes) } returns readBytes
|
every { inputStream.read(any(), any(), numBytes) } returns readBytes
|
||||||
every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream
|
every { crypto.newEncryptingStreamV1(outputStream, ad) } returns encryptedOutputStream
|
||||||
every { encryptedOutputStream.write(any<ByteArray>()) } just Runs
|
every { encryptedOutputStream.write(any<ByteArray>()) } just Runs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -233,7 +233,7 @@ internal class KVBackupTest : BackupTest() {
|
||||||
coEvery { backend.save(handle) } returns outputStream
|
coEvery { backend.save(handle) } returns outputStream
|
||||||
every { outputStream.write(ByteArray(1) { VERSION }) } just Runs
|
every { outputStream.write(ByteArray(1) { VERSION }) } just Runs
|
||||||
val ad = getADForKV(VERSION, packageInfo.packageName)
|
val ad = getADForKV(VERSION, packageInfo.packageName)
|
||||||
every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream
|
every { crypto.newEncryptingStreamV1(outputStream, ad) } returns encryptedOutputStream
|
||||||
every { encryptedOutputStream.write(any<ByteArray>()) } throws IOException()
|
every { encryptedOutputStream.write(any<ByteArray>()) } throws IOException()
|
||||||
|
|
||||||
assertEquals(TRANSPORT_ERROR, backup.finishBackup())
|
assertEquals(TRANSPORT_ERROR, backup.finishBackup())
|
||||||
|
@ -304,7 +304,7 @@ internal class KVBackupTest : BackupTest() {
|
||||||
coEvery { backend.save(handle) } returns outputStream
|
coEvery { backend.save(handle) } returns outputStream
|
||||||
every { outputStream.write(ByteArray(1) { VERSION }) } just Runs
|
every { outputStream.write(ByteArray(1) { VERSION }) } just Runs
|
||||||
val ad = getADForKV(VERSION, packageInfo.packageName)
|
val ad = getADForKV(VERSION, packageInfo.packageName)
|
||||||
every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream
|
every { crypto.newEncryptingStreamV1(outputStream, ad) } returns encryptedOutputStream
|
||||||
every { encryptedOutputStream.write(any<ByteArray>()) } just Runs // gzip header
|
every { encryptedOutputStream.write(any<ByteArray>()) } just Runs // gzip header
|
||||||
every { encryptedOutputStream.write(any(), any(), any()) } just Runs // stream copy
|
every { encryptedOutputStream.write(any(), any(), any()) } just Runs // stream copy
|
||||||
every { dbManager.getDbInputStream(packageName) } returns inputStream
|
every { dbManager.getDbInputStream(packageName) } returns inputStream
|
||||||
|
|
|
@ -9,6 +9,8 @@ import android.app.backup.BackupTransport.NO_MORE_DATA
|
||||||
import android.app.backup.BackupTransport.TRANSPORT_ERROR
|
import android.app.backup.BackupTransport.TRANSPORT_ERROR
|
||||||
import android.app.backup.BackupTransport.TRANSPORT_OK
|
import android.app.backup.BackupTransport.TRANSPORT_OK
|
||||||
import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
|
import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
|
||||||
|
import com.stevesoltys.seedvault.backend.BackendManager
|
||||||
|
import com.stevesoltys.seedvault.backend.LegacyStoragePlugin
|
||||||
import com.stevesoltys.seedvault.coAssertThrows
|
import com.stevesoltys.seedvault.coAssertThrows
|
||||||
import com.stevesoltys.seedvault.getRandomByteArray
|
import com.stevesoltys.seedvault.getRandomByteArray
|
||||||
import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH
|
import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH
|
||||||
|
@ -16,8 +18,6 @@ import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
||||||
import com.stevesoltys.seedvault.header.VERSION
|
import com.stevesoltys.seedvault.header.VERSION
|
||||||
import com.stevesoltys.seedvault.header.VersionHeader
|
import com.stevesoltys.seedvault.header.VersionHeader
|
||||||
import com.stevesoltys.seedvault.header.getADForFull
|
import com.stevesoltys.seedvault.header.getADForFull
|
||||||
import com.stevesoltys.seedvault.backend.LegacyStoragePlugin
|
|
||||||
import com.stevesoltys.seedvault.backend.BackendManager
|
|
||||||
import io.mockk.CapturingSlot
|
import io.mockk.CapturingSlot
|
||||||
import io.mockk.Runs
|
import io.mockk.Runs
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
|
@ -135,7 +135,7 @@ internal class FullRestoreTest : RestoreTest() {
|
||||||
|
|
||||||
coEvery { backend.load(handle) } returns inputStream
|
coEvery { backend.load(handle) } returns inputStream
|
||||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||||
every { crypto.newDecryptingStream(inputStream, ad) } throws IOException()
|
every { crypto.newDecryptingStreamV1(inputStream, ad) } throws IOException()
|
||||||
every { fileDescriptor.close() } just Runs
|
every { fileDescriptor.close() } just Runs
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -151,7 +151,9 @@ internal class FullRestoreTest : RestoreTest() {
|
||||||
|
|
||||||
coEvery { backend.load(handle) } returns inputStream
|
coEvery { backend.load(handle) } returns inputStream
|
||||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||||
every { crypto.newDecryptingStream(inputStream, ad) } throws GeneralSecurityException()
|
every {
|
||||||
|
crypto.newDecryptingStreamV1(inputStream, ad)
|
||||||
|
} throws GeneralSecurityException()
|
||||||
every { fileDescriptor.close() } just Runs
|
every { fileDescriptor.close() } just Runs
|
||||||
|
|
||||||
assertEquals(TRANSPORT_ERROR, restore.getNextFullRestoreDataChunk(fileDescriptor))
|
assertEquals(TRANSPORT_ERROR, restore.getNextFullRestoreDataChunk(fileDescriptor))
|
||||||
|
@ -217,7 +219,7 @@ internal class FullRestoreTest : RestoreTest() {
|
||||||
|
|
||||||
coEvery { backend.load(handle) } returns inputStream
|
coEvery { backend.load(handle) } returns inputStream
|
||||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||||
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream
|
every { crypto.newDecryptingStreamV1(inputStream, ad) } returns decryptedInputStream
|
||||||
every { outputFactory.getOutputStream(fileDescriptor) } returns outputStream
|
every { outputFactory.getOutputStream(fileDescriptor) } returns outputStream
|
||||||
every { fileDescriptor.close() } just Runs
|
every { fileDescriptor.close() } just Runs
|
||||||
every { inputStream.close() } just Runs
|
every { inputStream.close() } just Runs
|
||||||
|
@ -250,7 +252,7 @@ internal class FullRestoreTest : RestoreTest() {
|
||||||
private fun initInputStream() {
|
private fun initInputStream() {
|
||||||
coEvery { backend.load(handle) } returns inputStream
|
coEvery { backend.load(handle) } returns inputStream
|
||||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||||
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream
|
every { crypto.newDecryptingStreamV1(inputStream, ad) } returns decryptedInputStream
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readAndEncryptInputStream(encryptedBytes: ByteArray) {
|
private fun readAndEncryptInputStream(encryptedBytes: ByteArray) {
|
||||||
|
|
|
@ -105,7 +105,7 @@ internal class KVRestoreTest : RestoreTest() {
|
||||||
|
|
||||||
coEvery { backend.load(handle) } returns inputStream
|
coEvery { backend.load(handle) } returns inputStream
|
||||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||||
every { crypto.newDecryptingStream(inputStream, ad) } throws GeneralSecurityException()
|
every { crypto.newDecryptingStreamV1(inputStream, ad) } throws GeneralSecurityException()
|
||||||
every { dbManager.deleteDb(packageInfo.packageName, true) } returns true
|
every { dbManager.deleteDb(packageInfo.packageName, true) } returns true
|
||||||
streamsGetClosed()
|
streamsGetClosed()
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ internal class KVRestoreTest : RestoreTest() {
|
||||||
|
|
||||||
coEvery { backend.load(handle) } returns inputStream
|
coEvery { backend.load(handle) } returns inputStream
|
||||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||||
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptInputStream
|
every { crypto.newDecryptingStreamV1(inputStream, ad) } returns decryptInputStream
|
||||||
every {
|
every {
|
||||||
dbManager.getDbOutputStream(packageInfo.packageName)
|
dbManager.getDbOutputStream(packageInfo.packageName)
|
||||||
} returns ByteArrayOutputStream()
|
} returns ByteArrayOutputStream()
|
||||||
|
@ -148,7 +148,7 @@ internal class KVRestoreTest : RestoreTest() {
|
||||||
|
|
||||||
coEvery { backend.load(handle) } returns inputStream
|
coEvery { backend.load(handle) } returns inputStream
|
||||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||||
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptInputStream
|
every { crypto.newDecryptingStreamV1(inputStream, ad) } returns decryptInputStream
|
||||||
every {
|
every {
|
||||||
dbManager.getDbOutputStream(packageInfo.packageName)
|
dbManager.getDbOutputStream(packageInfo.packageName)
|
||||||
} returns ByteArrayOutputStream()
|
} returns ByteArrayOutputStream()
|
||||||
|
|
|
@ -11,6 +11,8 @@ import android.app.backup.BackupTransport.TRANSPORT_OK
|
||||||
import android.app.backup.RestoreDescription
|
import android.app.backup.RestoreDescription
|
||||||
import android.app.backup.RestoreDescription.TYPE_FULL_STREAM
|
import android.app.backup.RestoreDescription.TYPE_FULL_STREAM
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
|
import com.stevesoltys.seedvault.backend.BackendManager
|
||||||
|
import com.stevesoltys.seedvault.backend.LegacyStoragePlugin
|
||||||
import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
|
import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
|
||||||
import com.stevesoltys.seedvault.crypto.CryptoImpl
|
import com.stevesoltys.seedvault.crypto.CryptoImpl
|
||||||
import com.stevesoltys.seedvault.crypto.KEY_SIZE_BYTES
|
import com.stevesoltys.seedvault.crypto.KEY_SIZE_BYTES
|
||||||
|
@ -18,8 +20,6 @@ import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl
|
||||||
import com.stevesoltys.seedvault.encodeBase64
|
import com.stevesoltys.seedvault.encodeBase64
|
||||||
import com.stevesoltys.seedvault.header.HeaderReaderImpl
|
import com.stevesoltys.seedvault.header.HeaderReaderImpl
|
||||||
import com.stevesoltys.seedvault.metadata.MetadataReaderImpl
|
import com.stevesoltys.seedvault.metadata.MetadataReaderImpl
|
||||||
import com.stevesoltys.seedvault.backend.LegacyStoragePlugin
|
|
||||||
import com.stevesoltys.seedvault.backend.BackendManager
|
|
||||||
import com.stevesoltys.seedvault.toByteArrayFromHex
|
import com.stevesoltys.seedvault.toByteArrayFromHex
|
||||||
import com.stevesoltys.seedvault.transport.TransportTest
|
import com.stevesoltys.seedvault.transport.TransportTest
|
||||||
import com.stevesoltys.seedvault.transport.backup.KvDbManager
|
import com.stevesoltys.seedvault.transport.backup.KvDbManager
|
||||||
|
@ -50,7 +50,7 @@ internal class RestoreV0IntegrationTest : TransportTest() {
|
||||||
private val keyManager = KeyManagerTestImpl(secretKey)
|
private val keyManager = KeyManagerTestImpl(secretKey)
|
||||||
private val cipherFactory = CipherFactoryImpl(keyManager)
|
private val cipherFactory = CipherFactoryImpl(keyManager)
|
||||||
private val headerReader = HeaderReaderImpl()
|
private val headerReader = HeaderReaderImpl()
|
||||||
private val cryptoImpl = CryptoImpl(keyManager, cipherFactory, headerReader)
|
private val cryptoImpl = CryptoImpl(context, keyManager, cipherFactory, headerReader)
|
||||||
private val dbManager = mockk<KvDbManager>()
|
private val dbManager = mockk<KvDbManager>()
|
||||||
private val metadataReader = MetadataReaderImpl(cryptoImpl)
|
private val metadataReader = MetadataReaderImpl(cryptoImpl)
|
||||||
private val notificationManager = mockk<BackupNotificationManager>()
|
private val notificationManager = mockk<BackupNotificationManager>()
|
||||||
|
|
|
@ -14,6 +14,7 @@ android_library {
|
||||||
"src/main/java/org/calyxos/seedvault/core/backends/BackendTest.kt",
|
"src/main/java/org/calyxos/seedvault/core/backends/BackendTest.kt",
|
||||||
],
|
],
|
||||||
static_libs: [
|
static_libs: [
|
||||||
|
"seedvault-lib-tink-android",
|
||||||
"androidx.core_core-ktx",
|
"androidx.core_core-ktx",
|
||||||
"androidx.documentfile_documentfile",
|
"androidx.documentfile_documentfile",
|
||||||
"kotlinx-coroutines-android",
|
"kotlinx-coroutines-android",
|
||||||
|
|
|
@ -41,6 +41,7 @@ dependencies {
|
||||||
implementation(libs.bundles.coroutines)
|
implementation(libs.bundles.coroutines)
|
||||||
implementation(libs.androidx.documentfile)
|
implementation(libs.androidx.documentfile)
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
|
implementation(libs.google.tink.android)
|
||||||
implementation(fileTree("${rootProject.rootDir}/libs/dav4jvm").include("*.jar"))
|
implementation(fileTree("${rootProject.rootDir}/libs/dav4jvm").include("*.jar"))
|
||||||
implementation(libs.squareup.okio)
|
implementation(libs.squareup.okio)
|
||||||
implementation(libs.kotlin.logging)
|
implementation(libs.kotlin.logging)
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.calyxos.seedvault.core.crypto
|
||||||
|
|
||||||
|
import com.google.crypto.tink.subtle.AesGcmHkdfStreaming
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.security.GeneralSecurityException
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
|
||||||
|
public object CoreCrypto {
|
||||||
|
|
||||||
|
private const val KEY_SIZE = 256
|
||||||
|
private const val SIZE_SEGMENT = 1 shl 20 // 1024 * 1024
|
||||||
|
public const val KEY_SIZE_BYTES: Int = KEY_SIZE / 8
|
||||||
|
public const val ALGORITHM_HMAC: String = "HmacSHA256"
|
||||||
|
|
||||||
|
@Throws(GeneralSecurityException::class)
|
||||||
|
public fun deriveKey(mainKey: SecretKey, info: ByteArray): ByteArray = Hkdf.expand(
|
||||||
|
secretKey = mainKey,
|
||||||
|
info = info,
|
||||||
|
outLengthBytes = KEY_SIZE_BYTES,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a [AesGcmHkdfStreaming] encrypting stream
|
||||||
|
* that gets encrypted with the given secret.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class, GeneralSecurityException::class)
|
||||||
|
public fun newEncryptingStream(
|
||||||
|
secret: ByteArray,
|
||||||
|
outputStream: OutputStream,
|
||||||
|
associatedData: ByteArray = ByteArray(0),
|
||||||
|
): OutputStream {
|
||||||
|
return AesGcmHkdfStreaming(
|
||||||
|
secret,
|
||||||
|
ALGORITHM_HMAC,
|
||||||
|
KEY_SIZE_BYTES,
|
||||||
|
SIZE_SEGMENT,
|
||||||
|
0,
|
||||||
|
).newEncryptingStream(outputStream, associatedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class, GeneralSecurityException::class)
|
||||||
|
public fun newDecryptingStream(
|
||||||
|
secret: ByteArray,
|
||||||
|
inputStream: InputStream,
|
||||||
|
associatedData: ByteArray = ByteArray(0),
|
||||||
|
): InputStream {
|
||||||
|
return AesGcmHkdfStreaming(
|
||||||
|
secret,
|
||||||
|
ALGORITHM_HMAC,
|
||||||
|
KEY_SIZE_BYTES,
|
||||||
|
SIZE_SEGMENT,
|
||||||
|
0,
|
||||||
|
).newDecryptingStream(inputStream, associatedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,8 +3,9 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.calyxos.backup.storage.crypto
|
package org.calyxos.seedvault.core.crypto
|
||||||
|
|
||||||
|
import org.calyxos.seedvault.core.crypto.CoreCrypto.ALGORITHM_HMAC
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.security.GeneralSecurityException
|
import java.security.GeneralSecurityException
|
||||||
import javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
|
@ -14,19 +15,17 @@ import kotlin.math.min
|
||||||
|
|
||||||
internal object Hkdf {
|
internal object Hkdf {
|
||||||
|
|
||||||
private const val KEY_SIZE = 256
|
|
||||||
internal const val KEY_SIZE_BYTES = KEY_SIZE / 8
|
|
||||||
internal const val ALGORITHM_HMAC = "HmacSHA256"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Step 2 of RFC 5869.
|
* Step 2 of RFC 5869.
|
||||||
*
|
*
|
||||||
* Based on the Apache2 licensed HKDF library by Patrick Favre-Bulle.
|
* Based on the Apache2 licensed HKDF library by Patrick Favre-Bulle.
|
||||||
* Link: https://github.com/patrickfav/hkdf
|
* Link: https://github.com/patrickfav/hkdf
|
||||||
*
|
*
|
||||||
* @param secretKey a pseudorandom key of at least hmac hash length in bytes (usually, the output from the extract step)
|
* @param secretKey a pseudorandom key of at least hmac hash length in bytes
|
||||||
|
* (usually, the output from the extract step)
|
||||||
* @param info optional context and application specific information; may be null
|
* @param info optional context and application specific information; may be null
|
||||||
* @param outLengthBytes length of output keying material in bytes (must be <= 255 * mac hash length)
|
* @param outLengthBytes length of output keying material in bytes
|
||||||
|
* (must be <= 255 * mac hash length)
|
||||||
* @return new byte array of output keying material (OKM)
|
* @return new byte array of output keying material (OKM)
|
||||||
*/
|
*/
|
||||||
@Throws(GeneralSecurityException::class)
|
@Throws(GeneralSecurityException::class)
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.calyxos.backup.storage.crypto
|
package org.calyxos.seedvault.core.crypto
|
||||||
|
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
|
@ -20,7 +20,6 @@ android_library {
|
||||||
},
|
},
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"seedvault-lib-core",
|
"seedvault-lib-core",
|
||||||
"seedvault-lib-tink-android",
|
|
||||||
"libprotobuf-java-lite",
|
"libprotobuf-java-lite",
|
||||||
"androidx.core_core-ktx",
|
"androidx.core_core-ktx",
|
||||||
"androidx.fragment_fragment-ktx",
|
"androidx.fragment_fragment-ktx",
|
||||||
|
|
|
@ -93,7 +93,6 @@ dependencies {
|
||||||
implementation(libs.google.material)
|
implementation(libs.google.material)
|
||||||
implementation(libs.androidx.room.runtime)
|
implementation(libs.androidx.room.runtime)
|
||||||
implementation(libs.google.protobuf.javalite)
|
implementation(libs.google.protobuf.javalite)
|
||||||
implementation(libs.google.tink.android)
|
|
||||||
|
|
||||||
ksp(group = "androidx.room", name = "room-compiler", version = libs.versions.room.get())
|
ksp(group = "androidx.room", name = "room-compiler", version = libs.versions.room.get())
|
||||||
lintChecks(libs.thirdegg.lint.rules)
|
lintChecks(libs.thirdegg.lint.rules)
|
||||||
|
|
|
@ -6,8 +6,9 @@
|
||||||
package org.calyxos.backup.storage.crypto
|
package org.calyxos.backup.storage.crypto
|
||||||
|
|
||||||
import org.calyxos.backup.storage.backup.Chunker
|
import org.calyxos.backup.storage.backup.Chunker
|
||||||
import org.calyxos.backup.storage.crypto.Hkdf.ALGORITHM_HMAC
|
import org.calyxos.seedvault.core.crypto.CoreCrypto
|
||||||
import org.calyxos.backup.storage.crypto.Hkdf.KEY_SIZE_BYTES
|
import org.calyxos.seedvault.core.crypto.CoreCrypto.ALGORITHM_HMAC
|
||||||
|
import org.calyxos.seedvault.core.crypto.CoreCrypto.KEY_SIZE_BYTES
|
||||||
import java.security.GeneralSecurityException
|
import java.security.GeneralSecurityException
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
|
@ -25,12 +26,11 @@ internal object ChunkCrypto {
|
||||||
*/
|
*/
|
||||||
@Throws(GeneralSecurityException::class)
|
@Throws(GeneralSecurityException::class)
|
||||||
fun deriveChunkIdKey(
|
fun deriveChunkIdKey(
|
||||||
masterKey: SecretKey,
|
mainKey: SecretKey,
|
||||||
info: ByteArray = INFO_CHUNK_ID.toByteArray(),
|
info: ByteArray = INFO_CHUNK_ID.toByteArray(),
|
||||||
): ByteArray = Hkdf.expand(
|
): ByteArray = CoreCrypto.deriveKey(
|
||||||
secretKey = masterKey,
|
mainKey = mainKey,
|
||||||
info = info,
|
info = info,
|
||||||
outLengthBytes = KEY_SIZE_BYTES
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,10 +5,9 @@
|
||||||
|
|
||||||
package org.calyxos.backup.storage.crypto
|
package org.calyxos.backup.storage.crypto
|
||||||
|
|
||||||
import com.google.crypto.tink.subtle.AesGcmHkdfStreaming
|
|
||||||
import org.calyxos.backup.storage.backup.Backup.Companion.VERSION
|
import org.calyxos.backup.storage.backup.Backup.Companion.VERSION
|
||||||
import org.calyxos.backup.storage.crypto.Hkdf.ALGORITHM_HMAC
|
import org.calyxos.seedvault.core.crypto.CoreCrypto
|
||||||
import org.calyxos.backup.storage.crypto.Hkdf.KEY_SIZE_BYTES
|
import org.calyxos.seedvault.core.crypto.CoreCrypto.KEY_SIZE_BYTES
|
||||||
import org.calyxos.seedvault.core.toByteArrayFromHex
|
import org.calyxos.seedvault.core.toByteArrayFromHex
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
@ -20,18 +19,16 @@ import javax.crypto.SecretKey
|
||||||
public object StreamCrypto {
|
public object StreamCrypto {
|
||||||
|
|
||||||
private const val INFO_STREAM_KEY = "stream key"
|
private const val INFO_STREAM_KEY = "stream key"
|
||||||
private const val SIZE_SEGMENT = 1 shl 20 // 1024 * 1024
|
|
||||||
private const val TYPE_CHUNK: Byte = 0x00
|
private const val TYPE_CHUNK: Byte = 0x00
|
||||||
private const val TYPE_SNAPSHOT: Byte = 0x01
|
private const val TYPE_SNAPSHOT: Byte = 0x01
|
||||||
|
|
||||||
@Throws(GeneralSecurityException::class)
|
@Throws(GeneralSecurityException::class)
|
||||||
public fun deriveStreamKey(
|
public fun deriveStreamKey(
|
||||||
masterKey: SecretKey,
|
mainKey: SecretKey,
|
||||||
info: ByteArray = INFO_STREAM_KEY.toByteArray(),
|
info: ByteArray = INFO_STREAM_KEY.toByteArray(),
|
||||||
): ByteArray = Hkdf.expand(
|
): ByteArray = CoreCrypto.deriveKey(
|
||||||
secretKey = masterKey,
|
mainKey = mainKey,
|
||||||
info = info,
|
info = info,
|
||||||
outLengthBytes = KEY_SIZE_BYTES
|
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun getAssociatedDataForChunk(chunkId: String, version: Byte = VERSION): ByteArray =
|
internal fun getAssociatedDataForChunk(chunkId: String, version: Byte = VERSION): ByteArray =
|
||||||
|
@ -48,39 +45,19 @@ public object StreamCrypto {
|
||||||
.put(timestamp.toByteArray())
|
.put(timestamp.toByteArray())
|
||||||
.array()
|
.array()
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a [AesGcmHkdfStreaming] encrypting stream
|
|
||||||
* that gets encrypted with the given secret.
|
|
||||||
*/
|
|
||||||
@Throws(IOException::class, GeneralSecurityException::class)
|
@Throws(IOException::class, GeneralSecurityException::class)
|
||||||
public fun newEncryptingStream(
|
public fun newEncryptingStream(
|
||||||
secret: ByteArray,
|
secret: ByteArray,
|
||||||
outputStream: OutputStream,
|
outputStream: OutputStream,
|
||||||
associatedData: ByteArray = ByteArray(0),
|
associatedData: ByteArray = ByteArray(0),
|
||||||
): OutputStream {
|
): OutputStream = CoreCrypto.newEncryptingStream(secret, outputStream, associatedData)
|
||||||
return AesGcmHkdfStreaming(
|
|
||||||
secret,
|
|
||||||
ALGORITHM_HMAC,
|
|
||||||
KEY_SIZE_BYTES,
|
|
||||||
SIZE_SEGMENT,
|
|
||||||
0
|
|
||||||
).newEncryptingStream(outputStream, associatedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class, GeneralSecurityException::class)
|
@Throws(IOException::class, GeneralSecurityException::class)
|
||||||
public fun newDecryptingStream(
|
public fun newDecryptingStream(
|
||||||
secret: ByteArray,
|
secret: ByteArray,
|
||||||
inputStream: InputStream,
|
inputStream: InputStream,
|
||||||
associatedData: ByteArray = ByteArray(0),
|
associatedData: ByteArray = ByteArray(0),
|
||||||
): InputStream {
|
): InputStream = CoreCrypto.newDecryptingStream(secret, inputStream, associatedData)
|
||||||
return AesGcmHkdfStreaming(
|
|
||||||
secret,
|
|
||||||
ALGORITHM_HMAC,
|
|
||||||
KEY_SIZE_BYTES,
|
|
||||||
SIZE_SEGMENT,
|
|
||||||
0
|
|
||||||
).newDecryptingStream(inputStream, associatedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun Long.toByteArray(): ByteArray = ByteArray(8).apply {
|
public fun Long.toByteArray(): ByteArray = ByteArray(8).apply {
|
||||||
var l = this@toByteArray
|
var l = this@toByteArray
|
||||||
|
|
|
@ -33,8 +33,6 @@ import org.calyxos.backup.storage.backup.ChunksCacheRepopulater
|
||||||
import org.calyxos.backup.storage.content.ContentFile
|
import org.calyxos.backup.storage.content.ContentFile
|
||||||
import org.calyxos.backup.storage.content.DocFile
|
import org.calyxos.backup.storage.content.DocFile
|
||||||
import org.calyxos.backup.storage.content.MediaFile
|
import org.calyxos.backup.storage.content.MediaFile
|
||||||
import org.calyxos.backup.storage.crypto.Hkdf.ALGORITHM_HMAC
|
|
||||||
import org.calyxos.backup.storage.crypto.Hkdf.KEY_SIZE_BYTES
|
|
||||||
import org.calyxos.backup.storage.db.CachedChunk
|
import org.calyxos.backup.storage.db.CachedChunk
|
||||||
import org.calyxos.backup.storage.db.CachedFile
|
import org.calyxos.backup.storage.db.CachedFile
|
||||||
import org.calyxos.backup.storage.db.ChunksCache
|
import org.calyxos.backup.storage.db.ChunksCache
|
||||||
|
@ -48,6 +46,8 @@ import org.calyxos.backup.storage.scanner.FileScannerResult
|
||||||
import org.calyxos.seedvault.core.backends.Backend
|
import org.calyxos.seedvault.core.backends.Backend
|
||||||
import org.calyxos.seedvault.core.backends.FileBackupFileType.Blob
|
import org.calyxos.seedvault.core.backends.FileBackupFileType.Blob
|
||||||
import org.calyxos.seedvault.core.backends.FileBackupFileType.Snapshot
|
import org.calyxos.seedvault.core.backends.FileBackupFileType.Snapshot
|
||||||
|
import org.calyxos.seedvault.core.crypto.CoreCrypto.ALGORITHM_HMAC
|
||||||
|
import org.calyxos.seedvault.core.crypto.CoreCrypto.KEY_SIZE_BYTES
|
||||||
import org.calyxos.seedvault.core.crypto.KeyManager
|
import org.calyxos.seedvault.core.crypto.KeyManager
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
|
|
|
@ -13,13 +13,13 @@ import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.calyxos.backup.storage.backup.Backup.Companion.VERSION
|
import org.calyxos.backup.storage.backup.Backup.Companion.VERSION
|
||||||
import org.calyxos.backup.storage.crypto.Hkdf.KEY_SIZE_BYTES
|
|
||||||
import org.calyxos.backup.storage.crypto.StreamCrypto
|
import org.calyxos.backup.storage.crypto.StreamCrypto
|
||||||
import org.calyxos.backup.storage.db.ChunksCache
|
import org.calyxos.backup.storage.db.ChunksCache
|
||||||
import org.calyxos.backup.storage.getRandomString
|
import org.calyxos.backup.storage.getRandomString
|
||||||
import org.calyxos.backup.storage.mockLog
|
import org.calyxos.backup.storage.mockLog
|
||||||
import org.calyxos.seedvault.core.backends.Backend
|
import org.calyxos.seedvault.core.backends.Backend
|
||||||
import org.calyxos.seedvault.core.backends.FileBackupFileType.Blob
|
import org.calyxos.seedvault.core.backends.FileBackupFileType.Blob
|
||||||
|
import org.calyxos.seedvault.core.crypto.CoreCrypto.KEY_SIZE_BYTES
|
||||||
import org.calyxos.seedvault.core.toHexString
|
import org.calyxos.seedvault.core.toHexString
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
package org.calyxos.backup.storage.backup
|
package org.calyxos.backup.storage.backup
|
||||||
|
|
||||||
import org.calyxos.backup.storage.crypto.ChunkCrypto
|
import org.calyxos.backup.storage.crypto.ChunkCrypto
|
||||||
import org.calyxos.backup.storage.crypto.Hkdf.KEY_SIZE_BYTES
|
import org.calyxos.seedvault.core.crypto.CoreCrypto.KEY_SIZE_BYTES
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
|
|
|
@ -14,7 +14,6 @@ import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.calyxos.backup.storage.api.BackupObserver
|
import org.calyxos.backup.storage.api.BackupObserver
|
||||||
import org.calyxos.backup.storage.crypto.Hkdf.KEY_SIZE_BYTES
|
|
||||||
import org.calyxos.backup.storage.crypto.StreamCrypto
|
import org.calyxos.backup.storage.crypto.StreamCrypto
|
||||||
import org.calyxos.backup.storage.db.CachedChunk
|
import org.calyxos.backup.storage.db.CachedChunk
|
||||||
import org.calyxos.backup.storage.db.ChunksCache
|
import org.calyxos.backup.storage.db.ChunksCache
|
||||||
|
@ -23,6 +22,7 @@ import org.calyxos.backup.storage.getRandomDocFile
|
||||||
import org.calyxos.backup.storage.getRandomString
|
import org.calyxos.backup.storage.getRandomString
|
||||||
import org.calyxos.backup.storage.mockLog
|
import org.calyxos.backup.storage.mockLog
|
||||||
import org.calyxos.seedvault.core.backends.Backend
|
import org.calyxos.seedvault.core.backends.Backend
|
||||||
|
import org.calyxos.seedvault.core.crypto.CoreCrypto.KEY_SIZE_BYTES
|
||||||
import org.calyxos.seedvault.core.toHexString
|
import org.calyxos.seedvault.core.toHexString
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
@ -13,22 +13,22 @@ import io.mockk.mockk
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
import io.mockk.slot
|
import io.mockk.slot
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.calyxos.backup.storage.SnapshotRetriever
|
||||||
import org.calyxos.backup.storage.api.StoredSnapshot
|
import org.calyxos.backup.storage.api.StoredSnapshot
|
||||||
import org.calyxos.backup.storage.backup.BackupDocumentFile
|
import org.calyxos.backup.storage.backup.BackupDocumentFile
|
||||||
import org.calyxos.backup.storage.backup.BackupMediaFile
|
import org.calyxos.backup.storage.backup.BackupMediaFile
|
||||||
import org.calyxos.backup.storage.backup.BackupSnapshot
|
import org.calyxos.backup.storage.backup.BackupSnapshot
|
||||||
import org.calyxos.backup.storage.crypto.Hkdf.ALGORITHM_HMAC
|
|
||||||
import org.calyxos.backup.storage.crypto.Hkdf.KEY_SIZE_BYTES
|
|
||||||
import org.calyxos.backup.storage.crypto.StreamCrypto
|
import org.calyxos.backup.storage.crypto.StreamCrypto
|
||||||
import org.calyxos.backup.storage.db.CachedChunk
|
import org.calyxos.backup.storage.db.CachedChunk
|
||||||
import org.calyxos.backup.storage.db.ChunksCache
|
import org.calyxos.backup.storage.db.ChunksCache
|
||||||
import org.calyxos.backup.storage.db.Db
|
import org.calyxos.backup.storage.db.Db
|
||||||
|
import org.calyxos.backup.storage.getCurrentBackupSnapshots
|
||||||
import org.calyxos.backup.storage.getRandomString
|
import org.calyxos.backup.storage.getRandomString
|
||||||
import org.calyxos.backup.storage.mockLog
|
import org.calyxos.backup.storage.mockLog
|
||||||
import org.calyxos.backup.storage.SnapshotRetriever
|
|
||||||
import org.calyxos.backup.storage.getCurrentBackupSnapshots
|
|
||||||
import org.calyxos.seedvault.core.backends.Backend
|
import org.calyxos.seedvault.core.backends.Backend
|
||||||
import org.calyxos.seedvault.core.backends.FileBackupFileType.Blob
|
import org.calyxos.seedvault.core.backends.FileBackupFileType.Blob
|
||||||
|
import org.calyxos.seedvault.core.crypto.CoreCrypto.ALGORITHM_HMAC
|
||||||
|
import org.calyxos.seedvault.core.crypto.CoreCrypto.KEY_SIZE_BYTES
|
||||||
import org.calyxos.seedvault.core.crypto.KeyManager
|
import org.calyxos.seedvault.core.crypto.KeyManager
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
|
@ -48,15 +48,15 @@ internal class PrunerTest {
|
||||||
private val retentionManager: RetentionManager = mockk()
|
private val retentionManager: RetentionManager = mockk()
|
||||||
private val streamCrypto: StreamCrypto = mockk()
|
private val streamCrypto: StreamCrypto = mockk()
|
||||||
private val streamKey = "This is a backup key for testing".toByteArray()
|
private val streamKey = "This is a backup key for testing".toByteArray()
|
||||||
private val masterKey = SecretKeySpec(streamKey, 0, KEY_SIZE_BYTES, ALGORITHM_HMAC)
|
private val mainKey = SecretKeySpec(streamKey, 0, KEY_SIZE_BYTES, ALGORITHM_HMAC)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mockLog(false)
|
mockLog(false)
|
||||||
mockkStatic("org.calyxos.backup.storage.SnapshotRetrieverKt")
|
mockkStatic("org.calyxos.backup.storage.SnapshotRetrieverKt")
|
||||||
every { backendGetter() } returns backend
|
every { backendGetter() } returns backend
|
||||||
every { db.getChunksCache() } returns chunksCache
|
every { db.getChunksCache() } returns chunksCache
|
||||||
every { keyManager.getMainKey() } returns masterKey
|
every { keyManager.getMainKey() } returns mainKey
|
||||||
every { streamCrypto.deriveStreamKey(masterKey) } returns streamKey
|
every { streamCrypto.deriveStreamKey(mainKey) } returns streamKey
|
||||||
}
|
}
|
||||||
|
|
||||||
private val pruner = Pruner(
|
private val pruner = Pruner(
|
||||||
|
|
Loading…
Reference in a new issue