Skip to content

Commit

Permalink
streams: add stream encryption/decryption
Browse files Browse the repository at this point in the history
also provide an API < 26-compatible AsynchronousByteChannel et. al
  • Loading branch information
theodelrieu committed Aug 7, 2019
1 parent c56e9b2 commit 8f12160
Show file tree
Hide file tree
Showing 16 changed files with 840 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.tanker.api

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

@RequiresApi(26)
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>?) {
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,34 @@
package io.tanker.api

import java.io.InputStream
import java.nio.ByteBuffer
import java.util.concurrent.Future

class InputStreamWrapper(private val inputStream: InputStream) : TankerAsynchronousByteChannel {
companion object {
private var isClosed = false
}

override fun <A> read(dst: ByteBuffer?, attachment: A, handler: TankerCompletionHandler<Int, in A>?) {
TankerFuture.threadPool.execute {
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 !isClosed
}

override fun close() {
inputStream.close()
isClosed = true
}
}
85 changes: 83 additions & 2 deletions tanker-bindings/src/main/kotlin/io/tanker/api/Tanker.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package io.tanker.api

import androidx.annotation.RequiresApi
import android.util.Log
import com.sun.jna.Memory
import com.sun.jna.Pointer
import com.sun.jna.StringArray
import io.tanker.bindings.*
import io.tanker.jni.KVMx86Bug
import java.io.InputStream
import java.nio.channels.AsynchronousByteChannel

/**
* Main entry point for the Tanker SDK. Can open a TankerSession.
Expand All @@ -15,7 +18,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 +273,66 @@ 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())
})
}

@RequiresApi(26)
fun encrypt(channel: AsynchronousByteChannel): TankerFuture<TankerStreamChannelWrapper> {
return encrypt(channel, null)
}

@RequiresApi(26)
fun encrypt(channel: AsynchronousByteChannel, options: EncryptOptions?): TankerFuture<TankerStreamChannelWrapper> {
return encrypt(AsynchronousByteChannelWrapper(channel), options).andThen(TankerCallback {
TankerStreamChannelWrapper(it)
})
}

@RequiresApi(26)
fun decrypt(channel: AsynchronousByteChannel): TankerFuture<TankerStreamChannelWrapper> {
return decrypt(AsynchronousByteChannelWrapper(channel)).andThen(TankerCallback {
TankerStreamChannelWrapper(it)
})
}

fun encrypt(stream: InputStream): TankerFuture<TankerInputStream> {
return encrypt(stream, null)
}

fun encrypt(stream: InputStream, options: EncryptOptions?): TankerFuture<TankerInputStream> {
return encrypt(InputStreamWrapper(stream), options).andThen(TankerCallback {
TankerInputStream(it)
})
}

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

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

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

fun decrypt(stream: InputStream): TankerFuture<TankerInputStream> {
return decrypt(InputStreamWrapper(stream)).andThen(TankerCallback {
TankerInputStream(it)
})
}


/**
* Decrypts {@code data} with options, assuming the data was encrypted and shared beforehand.
Expand Down Expand Up @@ -314,6 +372,29 @@ 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: TankerStreamChannel): String {
return channel.resourceID
}

@RequiresApi(26)
fun getResourceID(channel: TankerStreamChannelWrapper): String {
return channel.streamChannel.resourceID
}

/**
* Get the resource ID used for sharing encrypted data.
* @param stream Tanker input stream returned either by {@code encrypt} or {@code decrypt}.
* @return The resource ID of the encrypted data (base64 encoded).
*/
fun getResourceID(stream: TankerInputStream): String {
return stream.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,13 @@
package io.tanker.api

import java.io.InputStream
import java.nio.ByteBuffer
import java.nio.channels.Channel
import java.util.concurrent.Future

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

interface TankerAsynchronousByteChannel : Channel {
public abstract fun <A : Any?> read(dst: ByteBuffer?, attachment: A, handler: TankerCompletionHandler<Int, in A>?)
}
13 changes: 13 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,13 @@
package io.tanker.api

import java.io.InputStream

class TankerChannels {

companion object {
@JvmStatic
public fun newInputStream(channel: TankerStreamChannel): InputStream {
return TankerInputStream(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> {
public abstract fun completed(result: V, attachment: A): Unit
public abstract fun failed(exc: Throwable, attachment: A): Unit
}
68 changes: 68 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,68 @@
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


class TankerInputStream internal constructor(private val channel: TankerStreamChannel) : InputStream() {
val resourceID = channel.resourceID

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 TankerReadPendingException : IllegalStateException() {
}
Loading

0 comments on commit 8f12160

Please sign in to comment.