diff --git a/app/src/main/java/sushi/hardcore/aira/AIRADatabase.kt b/app/src/main/java/sushi/hardcore/aira/AIRADatabase.kt index 834d733..2e0e813 100644 --- a/app/src/main/java/sushi/hardcore/aira/AIRADatabase.kt +++ b/app/src/main/java/sushi/hardcore/aira/AIRADatabase.kt @@ -19,6 +19,8 @@ object AIRADatabase { external fun clearTemporaryFiles(): Int external fun getIdentityPublicKey(): ByteArray external fun getIdentityFingerprint(): String + external fun getUsePadding(): Boolean + external fun setUsePadding(usePadding: Boolean): Boolean external fun changeName(newName: String): Boolean external fun changePassword(databaseFolder: String, oldPassword: ByteArray?, newPassword: ByteArray?): Boolean } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/aira/ChatActivity.kt b/app/src/main/java/sushi/hardcore/aira/ChatActivity.kt index 166c1ac..70f9a85 100644 --- a/app/src/main/java/sushi/hardcore/aira/ChatActivity.kt +++ b/app/src/main/java/sushi/hardcore/aira/ChatActivity.kt @@ -126,8 +126,6 @@ class ChatActivity : ServiceBoundActivity() { if (this@ChatActivity.sessionId == sessionId) { runOnUiThread { findViewById(R.id.bottom_panel).visibility = View.GONE - binding.buttonSend.setOnClickListener(null) - binding.buttonAttach.setOnClickListener(null) } } } diff --git a/app/src/main/java/sushi/hardcore/aira/SettingsActivity.kt b/app/src/main/java/sushi/hardcore/aira/SettingsActivity.kt index 0e5340b..06d7d57 100644 --- a/app/src/main/java/sushi/hardcore/aira/SettingsActivity.kt +++ b/app/src/main/java/sushi/hardcore/aira/SettingsActivity.kt @@ -12,8 +12,8 @@ import androidx.appcompat.app.AppCompatActivity import androidx.preference.EditTextPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreferenceCompat import sushi.hardcore.aira.background_service.AIRAService -import sushi.hardcore.aira.databinding.ActivityMainBinding import sushi.hardcore.aira.databinding.ActivitySettingsBinding import sushi.hardcore.aira.utils.StringUtils @@ -22,20 +22,24 @@ class SettingsActivity: AppCompatActivity() { private lateinit var airaService: AIRAService override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.preferences, rootKey) - val identityName = findPreference("identityName") + val identityNamePreference = findPreference("identityName") + val paddingPreference = findPreference("psecPadding") + identityNamePreference?.isPersistent = false + paddingPreference?.isPersistent = false Intent(activity, AIRAService::class.java).also { serviceIntent -> activity?.bindService(serviceIntent, object : ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder) { val binder = service as AIRAService.AIRABinder airaService = binder.getService() - identityName?.text = airaService.identityName + identityNamePreference?.text = airaService.identityName + paddingPreference?.isChecked = airaService.usePadding } override fun onServiceDisconnected(name: ComponentName?) {} }, Context.BIND_AUTO_CREATE) } - identityName?.setOnPreferenceChangeListener { _, newValue -> + identityNamePreference?.setOnPreferenceChangeListener { _, newValue -> if (airaService.changeName(newValue as String)) { - identityName.text = newValue + identityNamePreference.text = newValue } false } @@ -107,6 +111,11 @@ class SettingsActivity: AppCompatActivity() { false } } + paddingPreference?.setOnPreferenceChangeListener { _, checked -> + airaService.usePadding = checked as Boolean + AIRADatabase.setUsePadding(checked) + true + } } private fun changePassword(context: Context, isIdentityProtected: Boolean, oldPasswordEditText: EditText, newPassword: ByteArray?) { diff --git a/app/src/main/java/sushi/hardcore/aira/background_service/AIRAService.kt b/app/src/main/java/sushi/hardcore/aira/background_service/AIRAService.kt index 2cad914..6ac325c 100644 --- a/app/src/main/java/sushi/hardcore/aira/background_service/AIRAService.kt +++ b/app/src/main/java/sushi/hardcore/aira/background_service/AIRAService.kt @@ -48,6 +48,7 @@ class AIRAService : Service() { private val sendFileTransfers = mutableMapOf() val receiveFileTransfers = mutableMapOf() lateinit var contacts: HashMap + var usePadding = true private lateinit var serviceHandler: Handler private lateinit var notificationManager: NotificationManagerCompat private lateinit var nsdManager: NsdManager @@ -308,7 +309,7 @@ class AIRAService : Service() { sessionIdByKey[key] = sessionId uiCallbacks?.onNewSession(sessionId, session.ip) if (!isContact(sessionId)) { - session.encryptAndSend(Protocol.askName()) + session.encryptAndSend(Protocol.askName(), usePadding) } } else { session.close() @@ -412,7 +413,7 @@ class AIRAService : Service() { } private fun sendAndSave(sessionId: Int, msg: ByteArray) { - sessions[sessionId]?.encryptAndSend(msg) + sessions[sessionId]?.encryptAndSend(msg, usePadding) if (msg[0] == Protocol.MESSAGE) { saveMsg(sessionId, msg) } @@ -460,7 +461,7 @@ class AIRAService : Service() { identityName?.let { for (session in sessions.values) { try { - session.encryptAndSend(Protocol.tellName(it)) + session.encryptAndSend(Protocol.tellName(it), usePadding) } catch (e: SocketException) { e.printStackTrace() } @@ -498,6 +499,7 @@ class AIRAService : Service() { sessionCounter++ } } + usePadding = AIRADatabase.getUsePadding() } private fun encryptNextChunk(session: Session, filesSender: FilesSender) { @@ -510,7 +512,7 @@ class AIRAService : Service() { } filesSender.nextChunk = if (read > 0) { filesSender.lastChunkSizes.add(nextChunk.size) - session.encrypt(nextChunk) + session.encrypt(nextChunk, usePadding) } else { null } @@ -540,7 +542,7 @@ class AIRAService : Service() { receiveFileTransfers.remove(sessionId)!!.fileTransferNotification.onAborted() } if (outgoing) { - session.encryptAndSend(Protocol.abortFilesTransfer()) + session.encryptAndSend(Protocol.abortFilesTransfer(), usePadding) } } @@ -574,7 +576,7 @@ class AIRAService : Service() { when (buffer[0]) { Protocol.ASK_NAME -> { identityName?.let { name -> - session.encryptAndSend(Protocol.tellName(name)) + session.encryptAndSend(Protocol.tellName(name), usePadding) } } Protocol.TELL_NAME -> { @@ -603,7 +605,7 @@ class AIRAService : Service() { val chunk = buffer.sliceArray(1 until buffer.size) try { outputStream.write(chunk) - session.encryptAndSend(Protocol.ackChunk()) + session.encryptAndSend(Protocol.ackChunk(), usePadding) file.transferred += chunk.size if (file.transferred >= file.fileSize) { outputStream.close() diff --git a/app/src/main/java/sushi/hardcore/aira/background_service/Session.kt b/app/src/main/java/sushi/hardcore/aira/background_service/Session.kt index bd21452..073dc70 100644 --- a/app/src/main/java/sushi/hardcore/aira/background_service/Session.kt +++ b/app/src/main/java/sushi/hardcore/aira/background_service/Session.kt @@ -166,16 +166,20 @@ class Session(private val socket: SocketChannel, val outgoing: Boolean): Selecta return false } - private fun randomPad(input: ByteArray): ByteArray { + private fun pad(input: ByteArray, usePadding: Boolean): ByteArray { val encodedLen = ByteBuffer.allocate(MESSAGE_LEN_LEN).putInt(input.size).array() - val msgLen = input.size + MESSAGE_LEN_LEN - var len = 1000 - while (len < msgLen) { - len *= 2 + return if (usePadding) { + val msgLen = input.size + MESSAGE_LEN_LEN + var len = 1000 + while (len < msgLen) { + len *= 2 + } + val padding = ByteArray(len-msgLen) + prng.nextBytes(padding) + encodedLen + input + padding + } else { + encodedLen + input } - val padding = ByteArray(len-msgLen) - prng.nextBytes(padding) - return encodedLen + input + padding } private fun unpad(input: ByteArray): ByteArray { @@ -190,8 +194,8 @@ class Session(private val socket: SocketChannel, val outgoing: Boolean): Selecta } } - fun encrypt(plainText: ByteArray): ByteArray { - val padded = randomPad(plainText) + fun encrypt(plainText: ByteArray, usePadding: Boolean): ByteArray { + val padded = pad(plainText, usePadding) val rawMsgLen = ByteBuffer.allocate(MESSAGE_LEN_LEN).putInt(padded.size).array() val nonce = ivToNonce(applicationKeys.localIv, localCounter) localCounter++ @@ -200,8 +204,8 @@ class Session(private val socket: SocketChannel, val outgoing: Boolean): Selecta return rawMsgLen+localCipher.doFinal(padded) } - fun encryptAndSend(plainText: ByteArray) { - writeAll(encrypt(plainText)) + fun encryptAndSend(plainText: ByteArray, usePadding: Boolean) { + writeAll(encrypt(plainText, usePadding)) } fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } diff --git a/app/src/main/native/src/identity.rs b/app/src/main/native/src/identity.rs index da86aca..3e74e16 100644 --- a/app/src/main/native/src/identity.rs +++ b/app/src/main/native/src/identity.rs @@ -20,6 +20,7 @@ impl<'a> DBKeys { pub const KEYPAIR: &'a str = "keypair"; pub const SALT: &'a str = "salt"; pub const MASTER_KEY: &'a str = "master_key"; + pub const USE_PADDING: &'a str = "use_padding"; } fn bool_to_byte(b: bool) -> u8 { @@ -44,7 +45,8 @@ struct EncryptedIdentity { name: String, encrypted_keypair: Vec, salt: Vec, - encrypted_master_key: Vec + encrypted_master_key: Vec, + encrypted_use_padding: Vec, } pub struct Contact { @@ -59,6 +61,7 @@ pub struct Identity { pub name: String, keypair: Keypair, pub master_key: [u8; crypto::MASTER_KEY_LEN], + pub use_padding: bool, database_folder: String, } @@ -334,6 +337,13 @@ impl Identity { result } + pub fn set_use_padding(&mut self, use_padding: bool) -> Result { + self.use_padding = use_padding; + let db = KeyValueTable::new(&self.get_database_path(), MAIN_TABLE)?; + let encrypted_use_padding = crypto::encrypt_data(&[bool_to_byte(use_padding)], &self.master_key).unwrap(); + db.update(DBKeys::USE_PADDING, &encrypted_use_padding) + } + pub fn zeroize(&mut self){ self.master_key.zeroize(); self.keypair.secret.zeroize(); @@ -345,11 +355,13 @@ impl Identity { let encrypted_keypair = db.get(DBKeys::KEYPAIR)?; let salt = db.get(DBKeys::SALT)?; let encrypted_master_key = db.get(DBKeys::MASTER_KEY)?; + let encrypted_use_padding = db.get(DBKeys::USE_PADDING)?; Ok(EncryptedIdentity { name: std::str::from_utf8(&name).unwrap().to_owned(), encrypted_keypair, salt, encrypted_master_key, + encrypted_use_padding, }) } @@ -375,12 +387,21 @@ impl Identity { }; match crypto::decrypt_data(&encrypted_identity.encrypted_keypair, &master_key) { Ok(keypair) => { - Ok(Identity{ - name: encrypted_identity.name, - keypair: Keypair::from_bytes(&keypair[..]).unwrap(), - master_key: master_key, - database_folder: database_folder, - }) + match crypto::decrypt_data(&encrypted_identity.encrypted_use_padding, &master_key) { + Ok(use_padding) => { + Ok(Identity{ + name: encrypted_identity.name, + keypair: Keypair::from_bytes(&keypair[..]).unwrap(), + master_key, + use_padding: byte_to_bool(use_padding[0]).unwrap(), + database_folder: database_folder, + }) + } + Err(e) => { + print_error!(e); + Err(String::from(DATABASE_CORRUPED_ERROR)) + } + } } Err(e) => { print_error!(e); @@ -418,11 +439,13 @@ impl Identity { salt }; db.set(DBKeys::SALT, &salt)?; - + let encrypted_use_padding = crypto::encrypt_data(&[bool_to_byte(true)], &master_key).unwrap(); + db.set(DBKeys::USE_PADDING, &encrypted_use_padding)?; Ok(Identity { name: name.to_owned(), keypair, master_key, + use_padding: true, database_folder }) } diff --git a/app/src/main/native/src/key_value_table.rs b/app/src/main/native/src/key_value_table.rs index 510d1da..b0556bd 100644 --- a/app/src/main/native/src/key_value_table.rs +++ b/app/src/main/native/src/key_value_table.rs @@ -28,4 +28,7 @@ impl<'a> KeyValueTable<'a> { pub fn update(&self, key: &str, value: &[u8]) -> Result { self.db.execute(&format!("UPDATE {} SET value=? WHERE key=\"{}\"", self.table_name, key), params![value]) } -} \ No newline at end of file + /*pub fn upsert(&self, key: &str, value: &[u8]) -> Result { + self.db.execute(&format!("INSERT INTO {} (key, value) VALUES(?1, ?2) ON CONFLICT(key) DO UPDATE SET value=?3", self.table_name), params![key, value, value]) + }*/ +} diff --git a/app/src/main/native/src/lib.rs b/app/src/main/native/src/lib.rs index 518e10e..e319373 100644 --- a/app/src/main/native/src/lib.rs +++ b/app/src/main/native/src/lib.rs @@ -344,6 +344,24 @@ pub fn Java_sushi_hardcore_aira_AIRADatabase_changeName(env: JNIEnv, _: JClass, } } +#[allow(non_snake_case)] +#[no_mangle] +pub fn Java_sushi_hardcore_aira_AIRADatabase_getUsePadding(_: JNIEnv, _: JClass) -> jboolean { + bool_to_jboolean(loaded_identity.lock().unwrap().as_mut().unwrap().use_padding) +} + +#[allow(non_snake_case)] +#[no_mangle] +pub fn Java_sushi_hardcore_aira_AIRADatabase_setUsePadding(_: JNIEnv, _: JClass, use_padding: jboolean) -> jboolean { + match loaded_identity.lock().unwrap().as_mut().unwrap().set_use_padding(jboolean_to_bool(use_padding)) { + Ok(_) => 1, + Err(e) => { + log_error(e); + 0 + } + } +} + #[allow(non_snake_case)] #[no_mangle] pub fn Java_sushi_hardcore_aira_AIRADatabase_getIdentityFingerprint(env: JNIEnv, _: JClass) -> jobject { diff --git a/app/src/main/res/drawable/ic_blur.xml b/app/src/main/res/drawable/ic_blur.xml new file mode 100644 index 0000000..64429b3 --- /dev/null +++ b/app/src/main/res/drawable/ic_blur.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 85b3cb8..e880af8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -81,4 +81,7 @@ About AIRA version Refresh name + Security + Use PSEC padding + PSEC padding obfuscates the length of your messages but uses more network bandwidth. diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index c4131bc..1f45f48 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -28,6 +28,16 @@ + + + + + +