- Added possibility to export and import accounts.

This commit is contained in:
Juha Heinanen
2019-04-16 15:30:43 +03:00
parent 4afe9ca23c
commit 4899f31c6a
6 changed files with 199 additions and 10 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp" >
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<AutoCompleteTextView
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:imeOptions="actionDone"
android:inputType="textPassword" />
</android.support.design.widget.TextInputLayout>
</FrameLayout>

View File

@ -0,0 +1,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/export_accounts"
android:title="Export" />
<item android:id="@+id/import_accounts"
android:title="Import" />
</menu>