Optional PSEC padding
This commit is contained in:
parent
d6a7d1466d
commit
e77887a51c
@ -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
|
||||||
}
|
}
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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?) {
|
||||||
|
@ -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()
|
||||||
|
@ -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) }
|
||||||
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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])
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
5
app/src/main/res/drawable/ic_blur.xml
Normal file
5
app/src/main/res/drawable/ic_blur.xml
Normal 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>
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user