adds rest communication
- test cases for push and pop - rest singleton instantiated in MainApplication access through context - pop and peek work, push needs encryption process still
This commit is contained in:
parent
3d42f0ca2e
commit
10ba4cf31b
@ -4,12 +4,12 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 32
|
||||
compileSdk 30
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.maenle.bump"
|
||||
minSdk 29
|
||||
targetSdk 32
|
||||
targetSdk 30
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
@ -37,6 +37,7 @@ android {
|
||||
dependencies {
|
||||
|
||||
implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:16.1.4'
|
||||
implementation 'com.android.volley:volley:1.2.0'
|
||||
def camerax_version = "1.0.2"
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
@ -47,7 +48,7 @@ dependencies {
|
||||
implementation "androidx.camera:camera-camera2:$camerax_version"
|
||||
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
||||
implementation 'com.google.zxing:core:3.3.0'
|
||||
implementation "androidx.camera:camera-view:1.0.0-alpha31"
|
||||
implementation "androidx.camera:camera-view:1.0.0-alpha20"
|
||||
implementation 'com.macasaet.fernet:fernet-java8:1.4.2'
|
||||
testImplementation 'junit:junit:4.+'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
|
@ -1,12 +1,17 @@
|
||||
package com.maenle.bump
|
||||
|
||||
import android.util.Log
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.example.bump.MessageProcessor
|
||||
import com.example.bump.RestSingleton
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
@ -22,3 +27,82 @@ class ExampleInstrumentedTest {
|
||||
assertEquals("com.maenle.bump", appContext.packageName)
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MessageProcessorTest {
|
||||
@Test
|
||||
fun decryptMessages() {
|
||||
val code = "dydoes-unknowledgeable-indiscretion-househusbands-pot-walloper-indiscretion-discophorous-transcriptions-dydoes-poodle-faker-transcriptions-budlike"
|
||||
val messageRaw = "M1dEAxKZ5HUHCJoRkgGOvAABhqCAAAAAAGG2eKTSlKXWLDQx5B_wssZsNwsanzQID2UyUm4KKuKYKgfwH5MG2N-qzt6K4mg3pfZmWPaiDB9PiqlX236k6zo9Yvvq"
|
||||
val data = decryptMessage(code, messageRaw)
|
||||
assertEquals(data, "hello")
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun decryptMessage(code: String, messageRaw: String): String {
|
||||
val message = MessageProcessor(code)
|
||||
val data = message.decrypt(messageRaw)
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RestTest{
|
||||
@Test
|
||||
fun getPeek() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val sender = "dydoes-unknowledgeable-indiscretion-househusbands"
|
||||
RestSingleton.getInstance(context).peek(sender) { i -> assertEquals(i, "hello") }
|
||||
|
||||
}
|
||||
}
|
||||
class IntegrationTest{
|
||||
@Test
|
||||
fun pushPopAndDecrypt() {
|
||||
val testMessage = "Hi There Sir"
|
||||
val sender = "dydoes-unknowledgeable-indiscretion-househusbands"
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val lock = CountDownLatch(1);
|
||||
|
||||
fun messageTester(encrypted: String) {
|
||||
val code = "dydoes-unknowledgeable-indiscretion-househusbands-pot-walloper-indiscretion-discophorous-transcriptions-dydoes-poodle-faker-transcriptions-budlike"
|
||||
Log.d("TEST", encrypted)
|
||||
val message = MessageProcessor(code)
|
||||
val data = message.decrypt(encrypted)
|
||||
Log.d("TEST", "data")
|
||||
assertEquals(data, testMessage)
|
||||
lock.countDown()
|
||||
}
|
||||
|
||||
RestSingleton.getInstance(context).push(sender, testMessage) {
|
||||
RestSingleton.getInstance(context).pop(sender) { i -> messageTester(i) }
|
||||
}
|
||||
Log.d("TEST", "waiting")
|
||||
lock.await(20000, TimeUnit.MILLISECONDS)
|
||||
Log.d("TEST", "done")
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun popAndDectypt() {
|
||||
val testMessage = "Hi There Sir"
|
||||
val sender = "raphael"
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val lock = CountDownLatch(1);
|
||||
|
||||
|
||||
fun messageTester(encrypted: String) {
|
||||
val code = "dydoes-unknowledgeable-indiscretion-househusbands-pot-walloper-indiscretion-discophorous-transcriptions-dydoes-poodle-faker-transcriptions-budlike"
|
||||
val message = MessageProcessor(code)
|
||||
val data = message.decrypt(encrypted)
|
||||
assertEquals(data, testMessage)
|
||||
lock.countDown()
|
||||
}
|
||||
|
||||
RestSingleton.getInstance(context).pop(sender) { i -> messageTester(i) }
|
||||
lock.await(20000, TimeUnit.MILLISECONDS)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.maenle.bump">
|
||||
|
||||
<uses-feature android:name="android.hardware.camera.any" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name="com.example.bump.MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
@ -14,6 +18,7 @@
|
||||
android:theme="@style/Theme.BumpForAndroid">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
tools:node="merge"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.BumpForAndroid.NoActionBar">
|
||||
|
@ -41,14 +41,6 @@ class FirstFragment : Fragment() {
|
||||
}
|
||||
|
||||
fun testDecryption() {
|
||||
val code = "dydoes-unknowledgeable-indiscretion-househusbands-pot-walloper-indiscretion-discophorous-transcriptions-dydoes-poodle-faker-transcriptions-budlike"
|
||||
var mp = MessageProcessor()
|
||||
if(mp.codeValid(code)) {
|
||||
mp.codeSave(code)
|
||||
}
|
||||
|
||||
mp.decrypt("M1dEAxKZ5HUHCJoRkgGOvAABhqCAAAAAAGG2eKTSlKXWLDQx5B_wssZsNwsanzQID2UyUm4KKuKYKgfwH5MG2N-qzt6K4mg3pfZmWPaiDB9PiqlX236k6zo9Yvvq")
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
31
app/src/main/java/com/example/bump/MainApplication.kt
Normal file
31
app/src/main/java/com/example/bump/MainApplication.kt
Normal file
@ -0,0 +1,31 @@
|
||||
package com.example.bump
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
|
||||
// Not object class. AndroidManifest.xml error happen.
|
||||
class MainApplication : Application() {
|
||||
lateinit var rest : RestSingleton
|
||||
|
||||
init {
|
||||
instance = this
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var instance: MainApplication? = null
|
||||
|
||||
fun applicationContext() : Context {
|
||||
return instance!!.applicationContext
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
// initialize for any
|
||||
|
||||
// Use ApplicationContext.
|
||||
// example: SharedPreferences etc...
|
||||
val context: Context = MainApplication.applicationContext()
|
||||
rest = RestSingleton(context)
|
||||
}
|
||||
}
|
@ -1,8 +1,5 @@
|
||||
package com.example.bump
|
||||
|
||||
import android.util.Log
|
||||
import com.google.zxing.common.StringUtils
|
||||
import java.security.SecureRandom
|
||||
import java.util.Base64
|
||||
import javax.crypto.spec.PBEKeySpec
|
||||
import javax.crypto.SecretKeyFactory
|
||||
@ -15,59 +12,97 @@ import java.math.BigInteger
|
||||
import java.time.Duration
|
||||
import java.time.temporal.TemporalAmount
|
||||
|
||||
class MessageProcessor {
|
||||
lateinit var sender:String
|
||||
lateinit var key: String
|
||||
class MessageProcessor(code: String) {
|
||||
private var sender: String
|
||||
private var password: String
|
||||
|
||||
fun codeValid(code: String): Boolean {
|
||||
init {
|
||||
codeValid(code).let {
|
||||
val codeSplit: List<String> = code.split("-")
|
||||
sender = codeSplit.subList(0, SENDER_LENGTH).joinToString("-")
|
||||
password = codeSplit.subList(SENDER_LENGTH, codeSplit.size).joinToString("-")
|
||||
}
|
||||
}
|
||||
|
||||
private fun codeValid(code: String): Boolean {
|
||||
return code.split("-").size >= KEY_LENGTH + SENDER_LENGTH
|
||||
}
|
||||
|
||||
fun codeSave(new_code: String) {
|
||||
var code: List<String> = new_code.split("-")
|
||||
sender = code.subList(0, SENDER_LENGTH).joinToString("-")
|
||||
key = code.subList(SENDER_LENGTH, code.size).joinToString("-")
|
||||
Log.d(TAG, sender)
|
||||
Log.d(TAG, key)
|
||||
|
||||
fun decrypt(messageRaw: String): String {
|
||||
val message = Message(messageRaw)
|
||||
val fernetKey: Key = message.deriveMessageKeyFromPassword(password)
|
||||
return message.validateAndDecrypt(fernetKey)
|
||||
}
|
||||
|
||||
fun decrypt(message : String) {
|
||||
// Data from encryption
|
||||
val decoded : ByteArray = Base64.getUrlDecoder().decode(message)
|
||||
val salt = decoded.copyOfRange(0, 16)
|
||||
val iter : Int = BigInteger(decoded.copyOfRange(16, 20)).toInt()
|
||||
val str_token = String(Base64.getUrlEncoder().encode(decoded.copyOfRange(20, decoded.size)))
|
||||
|
||||
// Derive Fernet key
|
||||
val saltedKey = deriveKey(key, salt, iter)
|
||||
val fernetKey = Key(saltedKey)
|
||||
private class Message(messageRaw : String) {
|
||||
lateinit var salt : ByteArray
|
||||
var iterations: Int = 100_00
|
||||
lateinit var token : Token
|
||||
|
||||
val token =
|
||||
Token.fromString(str_token);
|
||||
|
||||
|
||||
// Decrypt
|
||||
val validator: Validator<String> = object : StringValidator {
|
||||
override fun getTimeToLive(): TemporalAmount {
|
||||
return Duration.ofHours(196)
|
||||
}
|
||||
}
|
||||
val data = token.validateAndDecrypt(fernetKey, validator)
|
||||
Log.d(TAG, data )
|
||||
init {
|
||||
splitMessage(messageRaw)
|
||||
}
|
||||
|
||||
fun deriveKey(password: String, salt: ByteArray, iterations : Int): String {
|
||||
private fun splitMessage(message : String) {
|
||||
val decodedMessage : ByteArray = Base64.getUrlDecoder().decode(message)
|
||||
val iterationsDecoded = decodedMessage.copyOfRange(SALT_LENGTH, SALT_LENGTH+ ITERATIONS_LENGTH)
|
||||
val tokenString = String(Base64.getUrlEncoder().encode(decodedMessage.copyOfRange(20, decodedMessage.size)))
|
||||
|
||||
iterations = BigInteger(iterationsDecoded).toInt()
|
||||
salt = decodedMessage.copyOfRange(0, SALT_LENGTH)
|
||||
token = Token.fromString(tokenString)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Message
|
||||
|
||||
if (!salt.contentEquals(other.salt)) return false
|
||||
if (iterations != other.iterations) return false
|
||||
if (token != other.token) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = salt.contentHashCode()
|
||||
result = 31 * result + iterations
|
||||
result = 31 * result + token.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
fun deriveMessageKeyFromPassword(password: String): Key{
|
||||
val derivedKeyLength = 256
|
||||
val spec = PBEKeySpec(password.toCharArray(), salt, iterations, derivedKeyLength)
|
||||
val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
||||
val key = secretKeyFactory.generateSecret(spec).encoded
|
||||
return Base64.getUrlEncoder().encodeToString(key)
|
||||
val saltedKey = Base64.getUrlEncoder().encodeToString(key)
|
||||
|
||||
return Key(saltedKey)
|
||||
}
|
||||
|
||||
fun validateAndDecrypt(fernetKey: Key, timeToLive: Long = 24*365*1000): String {
|
||||
|
||||
val validator: Validator<String> = object : StringValidator {
|
||||
override fun getTimeToLive(): TemporalAmount {
|
||||
return Duration.ofHours(timeToLive)
|
||||
}
|
||||
}
|
||||
return token.validateAndDecrypt(fernetKey, validator)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
companion object {
|
||||
private val TAG = MainActivity::class.java.simpleName
|
||||
val KEY_LENGTH = 8
|
||||
val SENDER_LENGTH = 4
|
||||
const val KEY_LENGTH = 8
|
||||
const val SENDER_LENGTH = 4
|
||||
const val SALT_LENGTH = 16
|
||||
const val ITERATIONS_LENGTH = 4
|
||||
}
|
||||
|
||||
|
||||
|
78
app/src/main/java/com/example/bump/RestSingleton.kt
Normal file
78
app/src/main/java/com/example/bump/RestSingleton.kt
Normal file
@ -0,0 +1,78 @@
|
||||
package com.example.bump
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.android.volley.Request
|
||||
import com.android.volley.RequestQueue
|
||||
import com.android.volley.toolbox.*
|
||||
import com.maenle.bump.MainActivity
|
||||
|
||||
import org.json.JSONObject
|
||||
|
||||
class RestSingleton constructor(context: Context){
|
||||
|
||||
init {
|
||||
MessageProcessor
|
||||
}
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: RestSingleton? = null
|
||||
fun getInstance(context: Context) =
|
||||
INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: RestSingleton(context).also {
|
||||
INSTANCE = it
|
||||
}
|
||||
}
|
||||
|
||||
private val TAG = MainActivity::class.java.simpleName
|
||||
private const val URL = "http://192.168.68.127:4000/api/"
|
||||
}
|
||||
|
||||
fun peek(sender: String, callback: (String) -> Unit){
|
||||
val url = URL + "peek/"
|
||||
val data = JSONObject()
|
||||
data.put("sender", sender)
|
||||
val jsonRequest = JsonObjectRequest(Request.Method.POST, url,
|
||||
data,
|
||||
{ response -> callback(response.toString()) },
|
||||
{callback("")})
|
||||
|
||||
requestQueue.add(jsonRequest)
|
||||
}
|
||||
|
||||
fun push(sender: String, message: String, function: () -> Unit){
|
||||
val url = URL + "push/"
|
||||
val data = JSONObject()
|
||||
data.put("sender", sender)
|
||||
data.put("data", message)
|
||||
val stringRequest = JsonObjectRequest(Request.Method.POST, url,
|
||||
data,
|
||||
{_ -> function()},
|
||||
{Log.d(TAG, "none")}
|
||||
)
|
||||
|
||||
requestQueue.add(stringRequest)
|
||||
}
|
||||
|
||||
fun pop(sender: String,callback: (String) -> Unit){
|
||||
val url = URL + "pop/"
|
||||
val data = JSONObject()
|
||||
data.put("sender", sender)
|
||||
val stringRequest = JsonObjectRequest(Request.Method.POST, url,
|
||||
data,
|
||||
{ response -> callback(response.toString()) },
|
||||
{callback("")})
|
||||
|
||||
requestQueue.add(stringRequest)
|
||||
}
|
||||
|
||||
|
||||
private val requestQueue: RequestQueue by lazy {
|
||||
// applicationContext is key, it keeps you from leaking the
|
||||
// Activity or BroadcastReceiver if someone passes one in.
|
||||
Volley.newRequestQueue(context.applicationContext)
|
||||
}
|
||||
fun <T> addToRequestQueue(req: Request<T>) {
|
||||
requestQueue.add(req)
|
||||
}
|
||||
}
|
@ -6,9 +6,9 @@
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".SecondFragment">
|
||||
|
||||
<androidx.camera.view.PreviewView
|
||||
<!--androidx.camera.view.PreviewView
|
||||
android:id="@+id/viewFinder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent" /-->
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in New Issue
Block a user