Optional PSEC padding

This commit is contained in:
Matéo Duparc 2021-05-14 11:43:51 +02:00
parent d6a7d1466d
commit e77887a51c
Signed by: hardcoresushi
GPG Key ID: 007F84120107191E
11 changed files with 112 additions and 35 deletions

View File

@ -19,6 +19,8 @@ object AIRADatabase {
external fun clearTemporaryFiles(): Int external fun clearTemporaryFiles(): Int
external fun getIdentityPublicKey(): ByteArray external fun getIdentityPublicKey(): ByteArray
external fun getIdentityFingerprint(): String external fun getIdentityFingerprint(): String
external fun getUsePadding(): Boolean
external fun setUsePadding(usePadding: Boolean): Boolean
external fun changeName(newName: String): Boolean external fun changeName(newName: String): Boolean
external fun changePassword(databaseFolder: String, oldPassword: ByteArray?, newPassword: ByteArray?): Boolean external fun changePassword(databaseFolder: String, oldPassword: ByteArray?, newPassword: ByteArray?): Boolean
} }

View File

@ -126,8 +126,6 @@ class ChatActivity : ServiceBoundActivity() {
if (this@ChatActivity.sessionId == sessionId) { if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread { runOnUiThread {
findViewById<ConstraintLayout>(R.id.bottom_panel).visibility = View.GONE findViewById<ConstraintLayout>(R.id.bottom_panel).visibility = View.GONE
binding.buttonSend.setOnClickListener(null)
binding.buttonAttach.setOnClickListener(null)
} }
} }
} }

View File

@ -12,8 +12,8 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import sushi.hardcore.aira.background_service.AIRAService import sushi.hardcore.aira.background_service.AIRAService
import sushi.hardcore.aira.databinding.ActivityMainBinding
import sushi.hardcore.aira.databinding.ActivitySettingsBinding import sushi.hardcore.aira.databinding.ActivitySettingsBinding
import sushi.hardcore.aira.utils.StringUtils import sushi.hardcore.aira.utils.StringUtils
@ -22,20 +22,24 @@ class SettingsActivity: AppCompatActivity() {
private lateinit var airaService: AIRAService private lateinit var airaService: AIRAService
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey) setPreferencesFromResource(R.xml.preferences, rootKey)
val identityName = findPreference<EditTextPreference>("identityName") val identityNamePreference = findPreference<EditTextPreference>("identityName")
val paddingPreference = findPreference<SwitchPreferenceCompat>("psecPadding")
identityNamePreference?.isPersistent = false
paddingPreference?.isPersistent = false
Intent(activity, AIRAService::class.java).also { serviceIntent -> Intent(activity, AIRAService::class.java).also { serviceIntent ->
activity?.bindService(serviceIntent, object : ServiceConnection { activity?.bindService(serviceIntent, object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder) { override fun onServiceConnected(name: ComponentName?, service: IBinder) {
val binder = service as AIRAService.AIRABinder val binder = service as AIRAService.AIRABinder
airaService = binder.getService() airaService = binder.getService()
identityName?.text = airaService.identityName identityNamePreference?.text = airaService.identityName
paddingPreference?.isChecked = airaService.usePadding
} }
override fun onServiceDisconnected(name: ComponentName?) {} override fun onServiceDisconnected(name: ComponentName?) {}
}, Context.BIND_AUTO_CREATE) }, Context.BIND_AUTO_CREATE)
} }
identityName?.setOnPreferenceChangeListener { _, newValue -> identityNamePreference?.setOnPreferenceChangeListener { _, newValue ->
if (airaService.changeName(newValue as String)) { if (airaService.changeName(newValue as String)) {
identityName.text = newValue identityNamePreference.text = newValue
} }
false false
} }
@ -107,6 +111,11 @@ class SettingsActivity: AppCompatActivity() {
false false
} }
} }
paddingPreference?.setOnPreferenceChangeListener { _, checked ->
airaService.usePadding = checked as Boolean
AIRADatabase.setUsePadding(checked)
true
}
} }
private fun changePassword(context: Context, isIdentityProtected: Boolean, oldPasswordEditText: EditText, newPassword: ByteArray?) { private fun changePassword(context: Context, isIdentityProtected: Boolean, oldPasswordEditText: EditText, newPassword: ByteArray?) {

View File

@ -48,6 +48,7 @@ class AIRAService : Service() {
private val sendFileTransfers = mutableMapOf<Int, FilesSender>() private val sendFileTransfers = mutableMapOf<Int, FilesSender>()
val receiveFileTransfers = mutableMapOf<Int, FilesReceiver>() val receiveFileTransfers = mutableMapOf<Int, FilesReceiver>()
lateinit var contacts: HashMap<Int, Contact> lateinit var contacts: HashMap<Int, Contact>
var usePadding = true
private lateinit var serviceHandler: Handler private lateinit var serviceHandler: Handler
private lateinit var notificationManager: NotificationManagerCompat private lateinit var notificationManager: NotificationManagerCompat
private lateinit var nsdManager: NsdManager private lateinit var nsdManager: NsdManager
@ -308,7 +309,7 @@ class AIRAService : Service() {
sessionIdByKey[key] = sessionId sessionIdByKey[key] = sessionId
uiCallbacks?.onNewSession(sessionId, session.ip) uiCallbacks?.onNewSession(sessionId, session.ip)
if (!isContact(sessionId)) { if (!isContact(sessionId)) {
session.encryptAndSend(Protocol.askName()) session.encryptAndSend(Protocol.askName(), usePadding)
} }
} else { } else {
session.close() session.close()
@ -412,7 +413,7 @@ class AIRAService : Service() {
} }
private fun sendAndSave(sessionId: Int, msg: ByteArray) { private fun sendAndSave(sessionId: Int, msg: ByteArray) {
sessions[sessionId]?.encryptAndSend(msg) sessions[sessionId]?.encryptAndSend(msg, usePadding)
if (msg[0] == Protocol.MESSAGE) { if (msg[0] == Protocol.MESSAGE) {
saveMsg(sessionId, msg) saveMsg(sessionId, msg)
} }
@ -460,7 +461,7 @@ class AIRAService : Service() {
identityName?.let { identityName?.let {
for (session in sessions.values) { for (session in sessions.values) {
try { try {
session.encryptAndSend(Protocol.tellName(it)) session.encryptAndSend(Protocol.tellName(it), usePadding)
} catch (e: SocketException) { } catch (e: SocketException) {
e.printStackTrace() e.printStackTrace()
} }
@ -498,6 +499,7 @@ class AIRAService : Service() {
sessionCounter++ sessionCounter++
} }
} }
usePadding = AIRADatabase.getUsePadding()
} }
private fun encryptNextChunk(session: Session, filesSender: FilesSender) { private fun encryptNextChunk(session: Session, filesSender: FilesSender) {
@ -510,7 +512,7 @@ class AIRAService : Service() {
} }
filesSender.nextChunk = if (read > 0) { filesSender.nextChunk = if (read > 0) {
filesSender.lastChunkSizes.add(nextChunk.size) filesSender.lastChunkSizes.add(nextChunk.size)
session.encrypt(nextChunk) session.encrypt(nextChunk, usePadding)
} else { } else {
null null
} }
@ -540,7 +542,7 @@ class AIRAService : Service() {
receiveFileTransfers.remove(sessionId)!!.fileTransferNotification.onAborted() receiveFileTransfers.remove(sessionId)!!.fileTransferNotification.onAborted()
} }
if (outgoing) { if (outgoing) {
session.encryptAndSend(Protocol.abortFilesTransfer()) session.encryptAndSend(Protocol.abortFilesTransfer(), usePadding)
} }
} }
@ -574,7 +576,7 @@ class AIRAService : Service() {
when (buffer[0]) { when (buffer[0]) {
Protocol.ASK_NAME -> { Protocol.ASK_NAME -> {
identityName?.let { name -> identityName?.let { name ->
session.encryptAndSend(Protocol.tellName(name)) session.encryptAndSend(Protocol.tellName(name), usePadding)
} }
} }
Protocol.TELL_NAME -> { Protocol.TELL_NAME -> {
@ -603,7 +605,7 @@ class AIRAService : Service() {
val chunk = buffer.sliceArray(1 until buffer.size) val chunk = buffer.sliceArray(1 until buffer.size)
try { try {
outputStream.write(chunk) outputStream.write(chunk)
session.encryptAndSend(Protocol.ackChunk()) session.encryptAndSend(Protocol.ackChunk(), usePadding)
file.transferred += chunk.size file.transferred += chunk.size
if (file.transferred >= file.fileSize) { if (file.transferred >= file.fileSize) {
outputStream.close() outputStream.close()

View File

@ -166,16 +166,20 @@ class Session(private val socket: SocketChannel, val outgoing: Boolean): Selecta
return false 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 encodedLen = ByteBuffer.allocate(MESSAGE_LEN_LEN).putInt(input.size).array()
val msgLen = input.size + MESSAGE_LEN_LEN return if (usePadding) {
var len = 1000 val msgLen = input.size + MESSAGE_LEN_LEN
while (len < msgLen) { var len = 1000
len *= 2 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 { 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 { fun encrypt(plainText: ByteArray, usePadding: Boolean): ByteArray {
val padded = randomPad(plainText) val padded = pad(plainText, usePadding)
val rawMsgLen = ByteBuffer.allocate(MESSAGE_LEN_LEN).putInt(padded.size).array() val rawMsgLen = ByteBuffer.allocate(MESSAGE_LEN_LEN).putInt(padded.size).array()
val nonce = ivToNonce(applicationKeys.localIv, localCounter) val nonce = ivToNonce(applicationKeys.localIv, localCounter)
localCounter++ localCounter++
@ -200,8 +204,8 @@ class Session(private val socket: SocketChannel, val outgoing: Boolean): Selecta
return rawMsgLen+localCipher.doFinal(padded) return rawMsgLen+localCipher.doFinal(padded)
} }
fun encryptAndSend(plainText: ByteArray) { fun encryptAndSend(plainText: ByteArray, usePadding: Boolean) {
writeAll(encrypt(plainText)) writeAll(encrypt(plainText, usePadding))
} }
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }

View File

@ -20,6 +20,7 @@ impl<'a> DBKeys {
pub const KEYPAIR: &'a str = "keypair"; pub const KEYPAIR: &'a str = "keypair";
pub const SALT: &'a str = "salt"; pub const SALT: &'a str = "salt";
pub const MASTER_KEY: &'a str = "master_key"; pub const MASTER_KEY: &'a str = "master_key";
pub const USE_PADDING: &'a str = "use_padding";
} }
fn bool_to_byte(b: bool) -> u8 { fn bool_to_byte(b: bool) -> u8 {
@ -44,7 +45,8 @@ struct EncryptedIdentity {
name: String, name: String,
encrypted_keypair: Vec<u8>, encrypted_keypair: Vec<u8>,
salt: Vec<u8>, salt: Vec<u8>,
encrypted_master_key: Vec<u8> encrypted_master_key: Vec<u8>,
encrypted_use_padding: Vec<u8>,
} }
pub struct Contact { pub struct Contact {
@ -59,6 +61,7 @@ pub struct Identity {
pub name: String, pub name: String,
keypair: Keypair, keypair: Keypair,
pub master_key: [u8; crypto::MASTER_KEY_LEN], pub master_key: [u8; crypto::MASTER_KEY_LEN],
pub use_padding: bool,
database_folder: String, database_folder: String,
} }
@ -334,6 +337,13 @@ impl Identity {
result result
} }
pub fn set_use_padding(&mut self, use_padding: bool) -> Result<usize, rusqlite::Error> {
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){ pub fn zeroize(&mut self){
self.master_key.zeroize(); self.master_key.zeroize();
self.keypair.secret.zeroize(); self.keypair.secret.zeroize();
@ -345,11 +355,13 @@ impl Identity {
let encrypted_keypair = db.get(DBKeys::KEYPAIR)?; let encrypted_keypair = db.get(DBKeys::KEYPAIR)?;
let salt = db.get(DBKeys::SALT)?; let salt = db.get(DBKeys::SALT)?;
let encrypted_master_key = db.get(DBKeys::MASTER_KEY)?; let encrypted_master_key = db.get(DBKeys::MASTER_KEY)?;
let encrypted_use_padding = db.get(DBKeys::USE_PADDING)?;
Ok(EncryptedIdentity { Ok(EncryptedIdentity {
name: std::str::from_utf8(&name).unwrap().to_owned(), name: std::str::from_utf8(&name).unwrap().to_owned(),
encrypted_keypair, encrypted_keypair,
salt, salt,
encrypted_master_key, encrypted_master_key,
encrypted_use_padding,
}) })
} }
@ -375,12 +387,21 @@ impl Identity {
}; };
match crypto::decrypt_data(&encrypted_identity.encrypted_keypair, &master_key) { match crypto::decrypt_data(&encrypted_identity.encrypted_keypair, &master_key) {
Ok(keypair) => { Ok(keypair) => {
Ok(Identity{ match crypto::decrypt_data(&encrypted_identity.encrypted_use_padding, &master_key) {
name: encrypted_identity.name, Ok(use_padding) => {
keypair: Keypair::from_bytes(&keypair[..]).unwrap(), Ok(Identity{
master_key: master_key, name: encrypted_identity.name,
database_folder: database_folder, 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) => { Err(e) => {
print_error!(e); print_error!(e);
@ -418,11 +439,13 @@ impl Identity {
salt salt
}; };
db.set(DBKeys::SALT, &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 { Ok(Identity {
name: name.to_owned(), name: name.to_owned(),
keypair, keypair,
master_key, master_key,
use_padding: true,
database_folder database_folder
}) })
} }

View File

@ -28,4 +28,7 @@ impl<'a> KeyValueTable<'a> {
pub fn update(&self, key: &str, value: &[u8]) -> Result<usize, Error> { pub fn update(&self, key: &str, value: &[u8]) -> Result<usize, Error> {
self.db.execute(&format!("UPDATE {} SET value=? WHERE key=\"{}\"", self.table_name, key), params![value]) self.db.execute(&format!("UPDATE {} SET value=? WHERE key=\"{}\"", self.table_name, key), params![value])
} }
} /*pub fn upsert(&self, key: &str, value: &[u8]) -> Result<usize, Error> {
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])
}*/
}

View File

@ -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)] #[allow(non_snake_case)]
#[no_mangle] #[no_mangle]
pub fn Java_sushi_hardcore_aira_AIRADatabase_getIdentityFingerprint(env: JNIEnv, _: JClass) -> jobject { pub fn Java_sushi_hardcore_aira_AIRADatabase_getIdentityFingerprint(env: JNIEnv, _: JClass) -> jobject {

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M6,13c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM6,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM6,9c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM3,9.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM6,5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM21,10.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM14,7c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45,1 1,1zM14,3.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM3,13.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM10,20.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM10,3.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM10,7c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45,1 1,1zM10,12.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM18,13c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM18,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM18,9c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM18,5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM21,13.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM14,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM14,20.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM10,8.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM10,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM14,12.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM14,8.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5z"/>
</vector>

View File

@ -81,4 +81,7 @@
<string name="about">About</string> <string name="about">About</string>
<string name="version">AIRA version</string> <string name="version">AIRA version</string>
<string name="refresh_name">Refresh name</string> <string name="refresh_name">Refresh name</string>
<string name="security">Security</string>
<string name="use_psec_padding">Use PSEC padding</string>
<string name="psec_padding_summary">PSEC padding obfuscates the length of your messages but uses more network bandwidth.</string>
</resources> </resources>

View File

@ -28,6 +28,16 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/security">
<SwitchPreferenceCompat
android:key="psecPadding"
android:title="@string/use_psec_padding"
android:summary="@string/psec_padding_summary"
android:icon="@drawable/ic_blur"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/about"> <PreferenceCategory android:title="@string/about">
<Preference <Preference