diff --git a/app/build.gradle b/app/build.gradle
index 3415a72..c68f3e9 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -42,6 +42,10 @@ dependencies {
implementation 'androidx.core:core-ktx:1.3.0'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
+
+ / One-time password /
+ implementation "dev.turingcomplete:kotlin-onetimepassword:2.0.1"
/ QRCode reader /
implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cc4c495..858d190 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -15,6 +15,7 @@
= 15) {
+ fa_progress.progressTintList = ColorStateList.valueOf(Color.GREEN)
+ } else if (seconds_remaining < 15 && seconds_remaining >= 8) {
+ fa_progress.progressTintList = ColorStateList.valueOf(Color.YELLOW)
+ } else {
+ fa_progress.progressTintList = ColorStateList.valueOf(Color.RED)
+ }
+
+ fa_progress.progress = seconds_remaining
+ delay(1000)
+ }
+ }
+
+ fa_label.text = "${fa_uri.path?.replace("/", "")} (${fa_uri.getQueryParameter("issuer")})"
+ }
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ fa_coroutine?.cancel(CancellationException("Activity is stopping"))
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/mondei1/offpass/CryptoOperations.kt b/app/src/main/java/com/github/mondei1/offpass/CryptoOperations.kt
index aa682b1..628e85f 100644
--- a/app/src/main/java/com/github/mondei1/offpass/CryptoOperations.kt
+++ b/app/src/main/java/com/github/mondei1/offpass/CryptoOperations.kt
@@ -7,6 +7,9 @@ import android.util.Log
import java.nio.ByteBuffer
import java.security.SecureRandom
import java.security.spec.KeySpec
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+import java.util.*
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.SecretKeyFactory
@@ -27,13 +30,25 @@ class CryptoOperations {
private val possibleChars: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
private lateinit var chars: CharArray
+ fun getRemainingTOTPTime(): Int {
+ /*
+ * If the time (in seconds) is 13, then this function should return 17.
+ * If the time ("") is 46, then this function should return 14
+ */
+ val format: DateFormat = SimpleDateFormat("ss")
+ val time_seconds = format.format(Calendar.getInstance().time).toInt()
+
+ if (time_seconds <= 30) return 30 - time_seconds
+ else return 60 - time_seconds
+ }
+
/**
- * Hash a password for storage.
+ * Hashes a any input with
*
* @return a secure authentication token to be stored for later authentication
*/
fun hash(password: CharArray, salt: ByteArray): String {
- val spec: KeySpec = PBEKeySpec(password, salt, 85000, 8*31)
+ val spec: KeySpec = PBEKeySpec(password, salt, 8000, 256)
val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
val hash = factory.generateSecret(spec).encoded
@@ -41,6 +56,12 @@ class CryptoOperations {
return Base64.encodeToString(hash, Base64.DEFAULT)
}
+ /**
+ * This function encrypts a provided string `plain` with a provided key `key`.
+ * Encryption function is AES-256-CBC
+ *
+ * @return Encrypted plain in Base64 encoded (without wrapping)
+ */
fun encrypt(key: String, plain: String): EncryptionResult {
val salt = nextString(10).toByteArray()
val iv = nextString(16).toByteArray()
@@ -56,10 +77,10 @@ class CryptoOperations {
}
val key: SecretKey = SecretKeySpec(aes_key, "AES")
- val cipher: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
+ val cipher: Cipher = Cipher.getInstance("AES/CBC/ISO10126Padding")
// Performing actual crypto operation
- cipher.init(Cipher.ENCRYPT_MODE, key, GCMParameterSpec(128, iv))
+ cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))
Log.i("Crypto", "Encrypt using ${String(aes_key)} '$plain' ${String(iv)}")
val ciphered = cipher.doFinal(plain.toByteArray(Charsets.UTF_8))
@@ -67,6 +88,14 @@ class CryptoOperations {
return EncryptionResult(Base64.encodeToString(ciphered, Base64.NO_WRAP), salt, iv)
}
+ /**
+ * This function decrypts a provided string `encrypted` with it's iv `iv`, key `key` and
+ * salt `salt`.
+ *
+ * Required is that the cipher AES-256-CBC was used.
+ *
+ * @return Decrypted cipher text as plain String.
+ */
fun decrypt(encrypted: String, key: String, iv: ByteArray, salt: ByteArray): String {
val key_bytes: ByteArray = hash(key.toCharArray(), salt).toByteArray(Charsets.UTF_8)
val aes_key: ByteArray = ByteArray(32)
@@ -84,10 +113,10 @@ class CryptoOperations {
Log.i("Crypto", "Decrypt using ${String(aes_key)} '${Base64.encodeToString(encrypted_raw, Base64.NO_WRAP)} (${encrypted_raw.size})' ${String(iv)}")
val keySpec: SecretKey = SecretKeySpec(aes_key, "AES")
- val cipher: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
+ val cipher: Cipher = Cipher.getInstance("AES/CBC/ISO10126Padding")
// Performing actual crypto operation
- cipher.init(Cipher.DECRYPT_MODE, keySpec, GCMParameterSpec(128, iv))
+ cipher.init(Cipher.DECRYPT_MODE, keySpec, IvParameterSpec(iv))
val ciphered = cipher.doFinal(encrypted_raw)
// Concat everything into one byte array
@@ -98,6 +127,11 @@ class CryptoOperations {
return String(byteBuffer.array())
}
+ /**
+ * Generates a random IV used for encryption.
+ *
+ * @return Random IV of a length of 12 bytes
+ */
fun nextIV(): ByteArray {
val iv: ByteArray = ByteArray(12)
val random: SecureRandom = SecureRandom()
@@ -106,6 +140,9 @@ class CryptoOperations {
return iv
}
+ /**
+ * Produces secure random string. This function can be slow, use only if necessary!
+ */
fun nextString(length: Int): String {
chars = CharArray(length)
for (idx in chars.indices) chars[idx] = possibleChars[random.nextInt(possibleChars.length)]
@@ -114,17 +151,4 @@ class CryptoOperations {
return String(chars)
}
-}
-
-class CryptoEncryptionRunnable(
- private val key: String,
- private val data: String,
- private val iv: ByteArray
-) : HandlerThread("Enc_Crypto") {
-
- override fun run() {
- Looper.prepare()
- val crypto = CryptoOperations()
- crypto.hash(key.toCharArray(), iv)
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/mondei1/offpass/MainActivity.kt b/app/src/main/java/com/github/mondei1/offpass/MainActivity.kt
index 727fc98..0b615ef 100644
--- a/app/src/main/java/com/github/mondei1/offpass/MainActivity.kt
+++ b/app/src/main/java/com/github/mondei1/offpass/MainActivity.kt
@@ -46,7 +46,6 @@ class MainActivity : AppCompatActivity() {
.setBeepEnabled(false)
.setBarcodeImageEnabled(false)
.initiateScan()
- Log.i("Main", "Clicked on text")
}
add_button.setOnClickListener {
diff --git a/app/src/main/java/com/github/mondei1/offpass/QRSchema.kt b/app/src/main/java/com/github/mondei1/offpass/QRSchema.kt
index 7929c21..5391a06 100644
--- a/app/src/main/java/com/github/mondei1/offpass/QRSchema.kt
+++ b/app/src/main/java/com/github/mondei1/offpass/QRSchema.kt
@@ -32,7 +32,9 @@ class QRSchema {
}
/**
- * This function takes a key `db_key` and tries to resolve it.
+ * This function takes a key `key` and tries to resolve it.
+ *
+ * @return The decrypted value associated with the key.
*/
private suspend fun resolve(key: String, context: Context): String {
val to_resolve = key.replace("$", "")
@@ -53,6 +55,7 @@ class QRSchema {
* This function will take the already set raw content and tries to parse it.
*
* @return It will return `true` if the operation was successful and `false` if otherwise.
+ * Parsed objects can be read from this class instance.
*/
fun parse(context: Context): Boolean {
if (decrypted_raw == "") {
@@ -61,7 +64,7 @@ class QRSchema {
}
// First will be to split the string into it's parts
- var fields: MutableList = this.decrypted_raw.split("|").toMutableList()
+ val fields: MutableList = this.decrypted_raw.split("|").toMutableList()
for (i in 0 until fields.size) {
// First four items have to exist and are title, username, password, URL in this order.
@@ -83,7 +86,7 @@ class QRSchema {
// Look if website url is compressed
if (this.website_url.startsWith("$")) {
- var that_class = this
+ val that_class = this
runBlocking {
that_class.website_url = resolve(that_class.website_url, context)
}
@@ -111,7 +114,7 @@ class QRSchema {
// We got a security question/awnser
if (key == "") {
- var qa = value.split("%")
+ val qa = value.split("%")
question_awnser.put(qa[0], qa[1]);
} else {
custom.put(key, value)
@@ -131,7 +134,7 @@ class QRSchema {
/**
* This function will take an raw string input and will decrypt it.
*
- * @return It returns the raw decrypted raw value.
+ * @return It returns the decrypted raw value.
*/
fun decrypt(raw: String, passphrase: String): String {
val parts = raw.split(":")
diff --git a/app/src/main/java/com/github/mondei1/offpass/TextInput.kt b/app/src/main/java/com/github/mondei1/offpass/TextInput.kt
index dff8d15..e2dfbb5 100644
--- a/app/src/main/java/com/github/mondei1/offpass/TextInput.kt
+++ b/app/src/main/java/com/github/mondei1/offpass/TextInput.kt
@@ -44,6 +44,7 @@ class TextInput : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
head.text = arg_head
+ title_.hint = arg_title
}
companion object {
diff --git a/app/src/main/res/drawable/dummy_qr_code.png b/app/src/main/res/drawable/dummy_qr_code.png
new file mode 100644
index 0000000..6fca785
Binary files /dev/null and b/app/src/main/res/drawable/dummy_qr_code.png differ
diff --git a/app/src/main/res/font/robotobold.ttf b/app/src/main/res/font/robotobold.ttf
new file mode 100644
index 0000000..d998cf5
Binary files /dev/null and b/app/src/main/res/font/robotobold.ttf differ
diff --git a/app/src/main/res/font/robotoregular.ttf b/app/src/main/res/font/robotoregular.ttf
new file mode 100644
index 0000000..2b6392f
Binary files /dev/null and b/app/src/main/res/font/robotoregular.ttf differ
diff --git a/app/src/main/res/font/robotothin.ttf b/app/src/main/res/font/robotothin.ttf
new file mode 100644
index 0000000..4e797cf
Binary files /dev/null and b/app/src/main/res/font/robotothin.ttf differ
diff --git a/app/src/main/res/layout/activity_create.xml b/app/src/main/res/layout/activity_create.xml
index 4929739..1183075 100644
--- a/app/src/main/res/layout/activity_create.xml
+++ b/app/src/main/res/layout/activity_create.xml
@@ -5,6 +5,7 @@
style="@style/AppTheme"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:background="@color/colorPrimary"
tools:context=".CreateActivity">
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="1.0" />
-
+ app:layout_constraintTop_toBottomOf="@+id/toolbar">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_text_input.xml b/app/src/main/res/layout/fragment_text_input.xml
index af3e08d..3c68aa6 100644
--- a/app/src/main/res/layout/fragment_text_input.xml
+++ b/app/src/main/res/layout/fragment_text_input.xml
@@ -17,7 +17,7 @@
android:textSize="20sp" />
+
+
+
+
+
+
+
+
OffPass
+
Created on $DATE
+
+
Password hint
+
$HINT
+
+

+
+
+
+
+
\ No newline at end of file