Skip to content

Commit

Permalink
Merge branch 'theo/stream_encryption' into 'master'
Browse files Browse the repository at this point in the history
theo/stream_encryption

See merge request Tanker/sdk-android!84
  • Loading branch information
theodelrieu committed Aug 14, 2019
2 parents 27b5ded + e86b40f commit 49d95d4
Show file tree
Hide file tree
Showing 22 changed files with 785 additions and 12 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
*.apk
*.ap_
*.dex
*.log

# Java build artifacts class files
*.class
Expand Down
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ org.gradle.jvmargs=-Xmx1024m
# The Android Gradle Plugin uses deprecated APIs, they have until Gradle 6.0 to fix their crap.
# https://android.googlesource.com/platform/tools/base/+/studio-master-dev/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/scope/BuildArtifactsHolder.kt
org.gradle.warning.mode=none
android.enableJetifier=true
android.useAndroidX=true
7 changes: 4 additions & 3 deletions tanker-bindings/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,12 @@ dependencies {
api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.2'
api 'net.java.dev.jna:jna:4.5.2@aar'
implementation 'com.android.support:support-compat:28.0.0'
implementation 'androidx.core:core:1.0.0'
testImplementation 'io.kotlintest:kotlintest-core:3.3.2'
testImplementation 'io.kotlintest:kotlintest-runner-junit5:3.3.2'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestImplementation 'androidx.annotation:annotation:1.0.0'
androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
testImplementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.9.9'
testImplementation 'org.slf4j:slf4j-nop:1.7.26'
testImplementation files('jna.jar')
Expand Down
3 changes: 0 additions & 3 deletions tanker-bindings/proguard-rules.pro

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.tanker.api;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.tanker.api

import androidx.annotation.RequiresApi
import java.nio.ByteBuffer
import java.nio.channels.AsynchronousByteChannel
import java.nio.channels.CompletionHandler

@RequiresApi(26)
internal class AsynchronousByteChannelWrapper(private val channel: AsynchronousByteChannel) : TankerAsynchronousByteChannel {
override fun close() {
channel.close()
}

override fun isOpen(): Boolean {
return channel.isOpen
}

override fun <A> read(dst: ByteBuffer, attachment: A, handler: TankerCompletionHandler<Int, in A>) {
// CompletionHandler is API 26 only, hence the boilerplate
channel.read(dst, attachment, object : CompletionHandler<Int, A> {
override fun completed(result: Int, attachment: A) {
handler.completed(result, attachment)
}

override fun failed(exc: Throwable, attachment: A) {
handler.failed(exc, attachment)
}
})
}
}
1 change: 1 addition & 0 deletions tanker-bindings/src/main/kotlin/io/tanker/api/ErrorCode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ enum class ErrorCode(val value: Int) {
INVALID_VERIFICATION(8),
TOO_MANY_ATTEMPTS(9),
EXPIRED_VERIFICATION(10),
IO_ERROR(11),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.tanker.api

import java.io.IOException
import java.io.InputStream
import java.nio.ByteBuffer

class InputStreamWrapper(private var inputStream: InputStream?) : TankerAsynchronousByteChannel {
override fun <A> read(dst: ByteBuffer, attachment: A, handler: TankerCompletionHandler<Int, in A>) {
TankerFuture.threadPool.execute {
if (!isOpen)
handler.failed(IOException("Stream is closed"), attachment)
else {
try {
val b = ByteArray(dst.remaining())
val nbRead = inputStream!!.read(b)
if (nbRead != -1) {
dst.put(b, 0, nbRead)
}
handler.completed(nbRead, attachment)
} catch (e: Throwable) {
handler.failed(e, attachment)
}
}
}
}

override fun isOpen(): Boolean {
return inputStream != null
}

override fun close() {
if (inputStream != null) {
inputStream!!.close()
inputStream = null
}
}
}
33 changes: 31 additions & 2 deletions tanker-bindings/src/main/kotlin/io/tanker/api/Tanker.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.tanker.api

import androidx.annotation.RequiresApi
import android.util.Log
import com.sun.jna.Memory
import com.sun.jna.Pointer
Expand All @@ -15,7 +16,7 @@ class Tanker(tankerOptions: TankerOptions) {
private const val LOG_TAG = "io.tanker.sdk"
private const val TANKER_ANDROID_VERSION = "dev"

private val lib = TankerLib.create()
internal val lib = TankerLib.create()
@ProguardKeep
private var logCallbackLifeSupport: LogHandlerCallback? = null

Expand Down Expand Up @@ -270,11 +271,30 @@ class Tanker(tankerOptions: TankerOptions) {
val outBuf = Memory(encryptedSize)

val futurePtr = lib.tanker_encrypt(tanker, outBuf, inBuf, data.size.toLong(), options)
return TankerFuture<Unit>(futurePtr, Unit::class.java).andThen(TankerCallbackWithKeepAlive(keepAlive = inBuf) {
return TankerFuture<Unit>(futurePtr, Unit::class.java).andThen(TankerCallbackWithKeepAlive(keepAlive = inBuf) {
outBuf.getByteArray(0, encryptedSize.toInt())
})
}

fun encrypt(channel: TankerAsynchronousByteChannel, options: EncryptOptions?): TankerFuture<TankerAsynchronousByteChannel> {
val cb = TankerStreamInputSourceCallback(channel)
val futurePtr = lib.tanker_stream_encrypt(tanker, cb, null, options)
return TankerFuture<Pointer>(futurePtr, Pointer::class.java).andThen(TankerCallback {
TankerResourceChannel(it, cb)
})
}

fun encrypt(channel: TankerAsynchronousByteChannel): TankerFuture<TankerAsynchronousByteChannel> {
return encrypt(channel, null)
}

fun decrypt(channel: TankerAsynchronousByteChannel): TankerFuture<TankerAsynchronousByteChannel> {
val cb = TankerStreamInputSourceCallback(channel)
val futurePtr = lib.tanker_stream_decrypt(tanker, cb, null)
return TankerFuture<Pointer>(futurePtr, Pointer::class.java).andThen(TankerCallback {
TankerResourceChannel(it, cb)
})
}

/**
* Decrypts {@code data} with options, assuming the data was encrypted and shared beforehand.
Expand Down Expand Up @@ -314,6 +334,15 @@ class Tanker(tankerOptions: TankerOptions) {
return outString
}

/**
* Get the resource ID used for sharing encrypted data.
* @param channel Tanker channel returned either by {@code encrypt} or {@code decrypt}.
* @return The resource ID of the encrypted data (base64 encoded).
*/
fun getResourceID(channel: TankerAsynchronousByteChannel): String {
return (channel as TankerResourceChannel).resourceID
}

/**
* Shares the key for an encrypted resource with another Tanker user.
* @param resourceIDs The IDs of the encrypted resources to share (base64 encoded each).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.tanker.api

import java.nio.ByteBuffer
import java.nio.channels.Channel

// nio.channels.AsynchronousByteChannel requires API 26
// provide our own interface as a replacement

interface TankerAsynchronousByteChannel : Channel {
fun <A : Any?> read(dst: ByteBuffer, attachment: A, handler: TankerCompletionHandler<Int, in A>)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.tanker.api

import androidx.annotation.RequiresApi
import java.nio.ByteBuffer
import java.nio.channels.AsynchronousByteChannel
import java.nio.channels.CompletionHandler
import java.nio.channels.ReadPendingException
import java.util.concurrent.Future

@RequiresApi(26)
internal class TankerAsynchronousByteChannelWrapper(internal val streamChannel: TankerAsynchronousByteChannel) : AsynchronousByteChannel {
override fun read(dst: ByteBuffer?): Future<Int> {
throw UnsupportedOperationException()
}

override fun <A : Any?> read(dst: ByteBuffer, attachment: A, handler: CompletionHandler<Int, in A>) {
try {
return streamChannel.read(dst, attachment, object : TankerCompletionHandler<Int, A> {
override fun completed(result: Int, attachment: A) {
handler.completed(result, attachment)
}

override fun failed(exc: Throwable, attachment: A) {
handler.failed(exc, attachment)
}
})
} catch (exc: Throwable) {
if (exc is TankerPendingReadException)
throw ReadPendingException()
throw exc
}
}

override fun close() {
return streamChannel.close()
}

override fun write(src: ByteBuffer?): Future<Int> {
throw UnsupportedOperationException()
}

override fun <A : Any?> write(src: ByteBuffer?, attachment: A, handler: CompletionHandler<Int, in A>?) {
throw UnsupportedOperationException()
}


override fun isOpen(): Boolean {
return streamChannel.isOpen
}

}
32 changes: 32 additions & 0 deletions tanker-bindings/src/main/kotlin/io/tanker/api/TankerChannels.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.tanker.api

import androidx.annotation.RequiresApi
import java.io.InputStream
import java.nio.channels.AsynchronousByteChannel

class TankerChannels {

companion object {
@JvmStatic
fun toInputStream(channel: TankerAsynchronousByteChannel): InputStream {
return TankerInputStream(channel)
}

@JvmStatic
fun fromInputStream(stream: InputStream): TankerAsynchronousByteChannel {
return InputStreamWrapper(stream)
}

@RequiresApi(26)
@JvmStatic
fun toAsynchronousByteChannel(channel: TankerAsynchronousByteChannel): AsynchronousByteChannel {
return TankerAsynchronousByteChannelWrapper(channel)
}

@RequiresApi(26)
@JvmStatic
fun fromAsynchronousByteChannel(channel: AsynchronousByteChannel): TankerAsynchronousByteChannel {
return AsynchronousByteChannelWrapper(channel)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.tanker.api

// nio.channels.CompletionHandler requires API 26
// provide our own interface as a replacement

interface TankerCompletionHandler<V, A> {
fun completed(result: V, attachment: A)
fun failed(exc: Throwable, attachment: A)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.sun.jna.Pointer
import io.tanker.bindings.TankerLib
import java.lang.reflect.Type
import java.util.concurrent.Executors
import android.support.annotation.WorkerThread
import androidx.annotation.WorkerThread

class TankerFuture<T>(private var cfuture: Pointer, private var valueType: Type) {
private sealed class ThenResult {
Expand Down
66 changes: 66 additions & 0 deletions tanker-bindings/src/main/kotlin/io/tanker/api/TankerInputStream.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.tanker.api

import io.tanker.bindings.TankerError
import io.tanker.bindings.TankerLib
import java.io.IOException
import java.io.InputStream
import java.nio.ByteBuffer
import java.nio.channels.ClosedChannelException
import java.util.concurrent.Callable
import java.util.concurrent.FutureTask
import java.util.concurrent.ThreadPoolExecutor


internal class TankerInputStream constructor(private val channel: TankerAsynchronousByteChannel) : InputStream() {

override fun read(): Int {
val buffer = ByteArray(1)
if (read(buffer, 0, 1) == -1)
return -1
return buffer[0].toInt()
}

override fun read(b: ByteArray): Int {
return read(b, 0, b.size)
}

override fun read(b: ByteArray, off: Int, len: Int): Int {
val fut = FutureTask {}
var nbRead = 0
var err: Throwable? = null

val buffer = ByteBuffer.wrap(b, off, len)
channel.read(buffer, Unit, object : TankerCompletionHandler<Int, Unit> {
override fun completed(result: Int, attachment: Unit) {
nbRead = result
fut.run()
}

override fun failed(exc: Throwable, attachment: Unit) {
err = exc
fut.run()
}
})
fut.get()
if (err != null) {
if (err is ClosedChannelException) {
throw IOException("Stream is closed", err)
}
throw err!!
}
return nbRead
}

override fun markSupported(): Boolean {
return false
}

override fun available(): Int {
return 0
}

override fun close() {
channel.close()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.tanker.api

import java.lang.IllegalStateException

open class TankerPendingReadException : IllegalStateException() {
}
Loading

0 comments on commit 49d95d4

Please sign in to comment.