diff --git a/app/build.gradle b/app/build.gradle index 3ae285e..a9be92a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/androidTest/java/com/example/bump/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/bump/ExampleInstrumentedTest.kt index 481b278..5bc4f78 100644 --- a/app/src/androidTest/java/com/example/bump/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/example/bump/ExampleInstrumentedTest.kt @@ -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. @@ -21,4 +26,83 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.maenle.bump", appContext.packageName) } -} \ No newline at end of file +} + +@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) + + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4610afc..baf73e4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,12 +1,16 @@ + diff --git a/app/src/main/java/com/example/bump/FirstFragment.kt b/app/src/main/java/com/example/bump/FirstFragment.kt index d6a70fb..8158923 100644 --- a/app/src/main/java/com/example/bump/FirstFragment.kt +++ b/app/src/main/java/com/example/bump/FirstFragment.kt @@ -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() { diff --git a/app/src/main/java/com/example/bump/MainApplication.kt b/app/src/main/java/com/example/bump/MainApplication.kt new file mode 100644 index 0000000..8fcb770 --- /dev/null +++ b/app/src/main/java/com/example/bump/MainApplication.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bump/MessageProcessor.kt b/app/src/main/java/com/example/bump/MessageProcessor.kt index e9af93e..d928cfc 100644 --- a/app/src/main/java/com/example/bump/MessageProcessor.kt +++ b/app/src/main/java/com/example/bump/MessageProcessor.kt @@ -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 = 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 = 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 = object : StringValidator { - override fun getTimeToLive(): TemporalAmount { - return Duration.ofHours(196) - } + init { + splitMessage(messageRaw) + } + + 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 + val saltedKey = Base64.getUrlEncoder().encodeToString(key) + + return Key(saltedKey) + } + + fun validateAndDecrypt(fernetKey: Key, timeToLive: Long = 24*365*1000): String { + + val validator: Validator = object : StringValidator { + override fun getTimeToLive(): TemporalAmount { + return Duration.ofHours(timeToLive) + } + } + return token.validateAndDecrypt(fernetKey, validator) } - val data = token.validateAndDecrypt(fernetKey, validator) - Log.d(TAG, data ) - } - - fun deriveKey(password: String, salt: ByteArray, iterations : Int): String { - 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) } + @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 } diff --git a/app/src/main/java/com/example/bump/RestSingleton.kt b/app/src/main/java/com/example/bump/RestSingleton.kt new file mode 100644 index 0000000..a89d17e --- /dev/null +++ b/app/src/main/java/com/example/bump/RestSingleton.kt @@ -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 addToRequestQueue(req: Request) { + requestQueue.add(req) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_camera.xml b/app/src/main/res/layout/fragment_camera.xml index ece0ba9..17766f0 100644 --- a/app/src/main/res/layout/fragment_camera.xml +++ b/app/src/main/res/layout/fragment_camera.xml @@ -6,9 +6,9 @@ android:layout_height="match_parent" tools:context=".SecondFragment"> - + android:layout_height="match_parent" /--> \ No newline at end of file