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 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
}

View File

@ -126,8 +126,6 @@ class ChatActivity : ServiceBoundActivity() {
if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread {
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.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<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 ->
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?) {

View File

@ -48,6 +48,7 @@ class AIRAService : Service() {
private val sendFileTransfers = mutableMapOf<Int, FilesSender>()
val receiveFileTransfers = mutableMapOf<Int, FilesReceiver>()
lateinit var contacts: HashMap<Int, Contact>
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()

View File

@ -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) }

View File

@ -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<u8>,
salt: Vec<u8>,
encrypted_master_key: Vec<u8>
encrypted_master_key: Vec<u8>,
encrypted_use_padding: Vec<u8>,
}
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<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){
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
})
}

View File

@ -28,4 +28,7 @@ impl<'a> KeyValueTable<'a> {
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])
}
/*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)]
#[no_mangle]
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="version">AIRA version</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>

View File

@ -28,6 +28,16 @@
</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">
<Preference