diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt
index 62926fe7..64722fde 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt
@@ -3,9 +3,14 @@ package com.tutpro.baresip
import android.app.Activity
import android.content.Intent
import android.os.Bundle
+import android.os.Environment
import android.support.v7.app.AppCompatActivity
+import android.view.Menu
import android.view.MenuItem
import android.widget.*
+import android.app.AlertDialog
+import android.view.ViewGroup
+import android.view.LayoutInflater
import java.io.File
import java.util.ArrayList
@@ -13,8 +18,8 @@ import java.util.ArrayList
class AccountsActivity : AppCompatActivity() {
internal lateinit var alAdapter: AccountListAdapter
-
internal var aor = ""
+ internal var password = ""
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -73,8 +78,25 @@ class AccountsActivity : AppCompatActivity() {
}
}
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.accounts_menu, menu)
+ return true
+ }
+
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
+ R.id.export_accounts -> {
+ if (Utils.requestPermission(this,
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE))
+ askPassword("Encrypt Password")
+ }
+ R.id.import_accounts -> {
+ if (Utils.requestPermission(this,
+ android.Manifest.permission.READ_EXTERNAL_STORAGE)) {
+ askPassword("Decrypt Password")
+
+ }
+ }
android.R.id.home -> {
Log.d("Baresip", "Back array was pressed at Accounts")
val i = Intent()
@@ -85,6 +107,45 @@ class AccountsActivity : AppCompatActivity() {
return true
}
+ private fun askPassword(title: String) {
+ val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+ val builder = AlertDialog.Builder(this)
+ builder.setTitle(title)
+ val viewInflated = LayoutInflater.from(this)
+ .inflate(R.layout.password_dialog, findViewById(android.R.id.content) as ViewGroup,
+ false)
+ val input = viewInflated.findViewById(R.id.password) as EditText
+ builder.setView(viewInflated)
+ builder.setPositiveButton(android.R.string.ok) { dialog, _ ->
+ dialog.dismiss()
+ password = input.text.toString()
+ if (password != "") {
+ if (title == "Encrypt Password") {
+ if (exportAccounts(dir, password))
+ Utils.alertView(this, "Info",
+ "Exported accounts to Download folder as 'accounts.bs'")
+ else
+ Utils.alertView(this, "Error",
+ "Failed to export accounts to Download folder.")
+ } else {
+ if (importAccounts(dir, password))
+ Utils.alertView(this, "Info",
+ "Imported accounts from Download folder " +
+ "'accounts.bs'. You need to restart baresip.")
+ else
+ Utils.alertView(this, "Error",
+ "Failed to import accounts from Download folder. " +
+ "Either file 'accounts.bs' doesn't exist or " +
+ "you gave wrong Decrypt Password.")
+ }
+ }
+ }
+ builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
+ dialog.cancel()
+ }
+ builder.show()
+ }
+
companion object {
internal var filesPath = ""
@@ -104,6 +165,22 @@ class AccountsActivity : AppCompatActivity() {
Utils.putFileContents(File(filesPath + "/accounts"), accounts)
// Log.d("Baresip", "Saved accounts '${accounts}' to '${filesPath}/accounts'")
}
+
+ fun exportAccounts(path: File, password: String): Boolean {
+ var accounts = ""
+ for (a in Account.accounts())
+ accounts = accounts + a.print() + "\n"
+ return Utils.putFileContents(File(path, "accounts.bs"),
+ Utils.encrypt(accounts, password))
+ }
+
+ fun importAccounts(path: File, password: String): Boolean {
+ val content = Utils.getFileContents(File(path, "accounts.bs"))
+ if (content == "Failed") return false
+ val accounts = Utils.decrypt(content, password)
+ if (accounts == "") return false
+ return Utils.putFileContents(File(filesPath + "/accounts"), accounts)
+ }
}
}
diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactsActivity.kt
index ff04e530..cff14b45 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/ContactsActivity.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/ContactsActivity.kt
@@ -2,7 +2,7 @@ package com.tutpro.baresip
import android.app.Activity
import android.content.*
-import android.os.Bundle
+import android.os.Bundle
import android.os.Environment
import android.support.v7.app.AppCompatActivity
import android.view.Menu
@@ -59,7 +59,7 @@ class ContactsActivity : AppCompatActivity() {
R.id.export_contacts -> {
if (saveContacts(dir))
Utils.alertView(this,"",
- "Exported contacts to Download folder.")
+ "Exported contacts to Download folder as 'contacts.bs'.")
else
Utils.alertView(this,"Error",
"Failed to export contacts to Download folder. " +
@@ -100,11 +100,11 @@ class ContactsActivity : AppCompatActivity() {
var contents = ""
for (c in Contact.contacts())
contents += "\"${c.name}\" ${c.uri}\n"
- return Utils.putFileContents(File(path, "contacts"), contents)
+ return Utils.putFileContents(File(path, "contacts.bs"), contents)
}
fun restoreContacts(path: File): Boolean {
- val content = Utils.getFileContents(File(path, "contacts"))
+ val content = Utils.getFileContents(File(path, "contacts.bs"))
if (content == "Failed") return false
Api.contacts_remove()
Contact.contacts().clear()
diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt
index 8b79ab44..4dc94703 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt
@@ -9,8 +9,6 @@ import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
-import android.view.Menu
-import android.view.MenuItem
import android.content.pm.PackageManager
import android.media.AudioManager
import android.os.Build
@@ -19,7 +17,6 @@ import android.support.v4.content.LocalBroadcastManager
import android.view.inputmethod.InputMethodManager
import android.text.InputType
import android.text.TextWatcher
-import android.widget.RelativeLayout
import android.widget.*
import android.view.*
@@ -130,7 +127,7 @@ class MainActivity : AppCompatActivity() {
Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
Log.w("Baresip", "Baresip does not have RECORD_AUDIO permission")
ActivityCompat.requestPermissions(this,
- arrayOf(Manifest.permission.RECORD_AUDIO), RECORD_AUDIO_PERMISSION)
+ arrayOf(Manifest.permission.RECORD_AUDIO), PERMISSION_REQUEST_CODE)
}
aorSpinner = findViewById(R.id.AoRList)
@@ -1142,7 +1139,8 @@ class MainActivity : AppCompatActivity() {
const val CONTACT_CODE = 7
const val MESSAGES_CODE = 8
const val MESSAGE_CODE = 9
- const val RECORD_AUDIO_PERMISSION = 1
+
+ const val PERMISSION_REQUEST_CODE = 1
}
diff --git a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt
index 2f55bf68..5eddd9a1 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt
@@ -1,16 +1,32 @@
package com.tutpro.baresip
+import android.app.Activity
import android.app.ActivityManager
import android.content.Context
import android.support.v7.app.AlertDialog
import android.os.PowerManager
import android.app.KeyguardManager
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.Bundle
+import android.support.v4.app.ActivityCompat
+import android.support.v4.content.ContextCompat
import android.text.Editable
import android.text.TextWatcher
+import android.util.Base64
+import android.util.Base64.encodeToString
+
+import kotlin.collections.ArrayList
+import kotlin.Exception
import java.io.*
+import java.security.SecureRandom
+import java.util.*
+import java.nio.ByteBuffer
+import javax.crypto.Cipher
+import javax.crypto.SecretKeyFactory
+import javax.crypto.spec.GCMParameterSpec
+import javax.crypto.spec.PBEKeySpec
object Utils {
@@ -285,6 +301,75 @@ object Utils {
}
}
+ fun requestPermission(ctx: Context, permission: String) : Boolean {
+ if (ContextCompat.checkSelfPermission(ctx, permission) != PackageManager.PERMISSION_GRANTED) {
+ Log.w("Baresip", "Baresip does not have $permission permission")
+ ActivityCompat.requestPermissions(ctx as Activity, arrayOf(permission),
+ MainActivity.PERMISSION_REQUEST_CODE)
+ return false
+ }
+ return true
+ }
+
+ fun encrypt(plainText: String, password: String): String {
+ val sr = SecureRandom.getInstance("SHA1PRNG")
+ val salt = ByteArray(16)
+ sr.nextBytes(salt)
+ val iterationCount = Random().nextInt(1024) + 512
+ val spec = PBEKeySpec(password.toCharArray(), salt, iterationCount, 256)
+ val secretKey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec)
+ val iv = ByteArray(12)
+ SecureRandom().nextBytes(iv)
+ val cipher = Cipher.getInstance("AES/GCM/NoPadding")
+ val parameterSpec = GCMParameterSpec(128, iv)
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec)
+ val cipherText = cipher.doFinal(plainText.toByteArray())
+ val byteBuffer = ByteBuffer.allocate(Int.SIZE_BYTES + salt.size + Int.SIZE_BYTES +
+ Int.SIZE_BYTES + iv.size + cipherText.size)
+ byteBuffer.putInt(salt.size)
+ byteBuffer.put(salt)
+ byteBuffer.putInt(iterationCount)
+ byteBuffer.putInt(iv.size)
+ byteBuffer.put(iv)
+ byteBuffer.put(cipherText)
+ return encodeToString(byteBuffer.array(), Base64.DEFAULT)
+ }
+
+ fun decrypt(cipherMessage: String, password: String): String {
+ val byteBuffer = ByteBuffer.wrap(Base64.decode(cipherMessage, Base64.DEFAULT))
+ val saltLength = byteBuffer.getInt()
+ if (saltLength != 16) {
+ Log.w("Baresip", "invalid salt length $saltLength")
+ return ""
+ }
+ val salt = ByteArray(saltLength)
+ byteBuffer.get(salt)
+ val iterationCount = byteBuffer.getInt()
+ if ((iterationCount < 512) || (iterationCount > 1023 + 512)) {
+ Log.w("Baresip", "invalid iteratorCount $iterationCount")
+ return ""
+ }
+ val spec = PBEKeySpec(password.toCharArray(), salt, iterationCount, 256)
+ val secretKey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec)
+ val ivLength = byteBuffer.getInt()
+ if (ivLength != 12) {
+ Log.d("Baresip", "invalid iv length $ivLength")
+ return ""
+ }
+ val iv = ByteArray(ivLength)
+ byteBuffer.get(iv)
+ val cipherText = ByteArray(byteBuffer.remaining())
+ byteBuffer.get(cipherText)
+ val cipher = Cipher.getInstance("AES/GCM/NoPadding")
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, iv))
+ try {
+ return String(cipher.doFinal(cipherText))
+ } catch (e: Exception) {
+ Log.w("Baresip", "Decryption failed ${e.printStackTrace()}")
+ return ""
+ }
+ }
+
fun dumpIntent(intent: Intent) {
val bundle: Bundle = intent.extras ?: return
diff --git a/app/src/main/res/layout/password_dialog.xml b/app/src/main/res/layout/password_dialog.xml
new file mode 100644
index 00000000..f971b727
--- /dev/null
+++ b/app/src/main/res/layout/password_dialog.xml
@@ -0,0 +1,20 @@
+
+