diff --git a/app/build.gradle b/app/build.gradle index c68f3e9..796d5e4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -41,6 +41,9 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.0' + /* Password strengh library */ + compile 'com.nulab-inc:zxcvbn:1.3.0' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7" diff --git a/app/src/main/java/com/github/mondei1/offpass/CreateActivity.kt b/app/src/main/java/com/github/mondei1/offpass/CreateActivity.kt index dacd5bc..4faead8 100644 --- a/app/src/main/java/com/github/mondei1/offpass/CreateActivity.kt +++ b/app/src/main/java/com/github/mondei1/offpass/CreateActivity.kt @@ -27,12 +27,16 @@ import com.google.zxing.BarcodeFormat import com.google.zxing.integration.android.IntentIntegrator import com.google.zxing.integration.android.IntentResult import com.journeyapps.barcodescanner.BarcodeEncoder +import com.nulabinc.zxcvbn.Zxcvbn import dev.turingcomplete.kotlinonetimepassword.GoogleAuthenticator import kotlinx.android.synthetic.main.activity_create.* +import kotlinx.android.synthetic.main.activity_generator.* +import kotlinx.android.synthetic.main.activity_generator.view.* import kotlinx.android.synthetic.main.dialogpassphrase.view.* import kotlinx.coroutines.* import java.io.ByteArrayOutputStream import java.lang.IllegalArgumentException +import java.lang.StringBuilder import java.util.* import kotlin.collections.ArrayList @@ -168,54 +172,70 @@ class CreateActivity : AppCompatActivity() { finish() } print_button.setOnClickListener { - // Ask user for passhprase and hint - val builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.PassphraseDialog)) + fun continueAfterCheck() { + // Ask user for passhprase and hint + val builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.PassphraseDialog)) - with(builder) { - val editTextLayout = layoutInflater.inflate(R.layout.dialogpassphrase, null) - setView(editTextLayout) - setTitle("Set Passphrase") - setMessage("Final step is it to set your passphrase. Minimum are 8 characters.") - setPositiveButton("Print", DialogInterface.OnClickListener { dialogInterface, i -> - val passphrase = editTextLayout.passphrase_input.text.toString(); - if (passphrase == "") { - // Make a new alert, telling the user that passphrase must not be null. - dialogInterface.cancel() - showError("Empty passphrase", "Passphrase must not be null.") - return@OnClickListener - } - if (passphrase.length < 8) { - // Make a new alert, telling the user that his passphrase doesn't meet the minimum. - dialogInterface.cancel() - showError("Weak passphrase", "Passphrase has to be at least 8 characters long.") - return@OnClickListener - } - if (passphrase != editTextLayout.passphrase2_input.text.toString()) { - // Make a new alert, telling the user that his passphrase doesn't match the repeat field. - dialogInterface.cancel() - showError("Passphrase mismatch", "Both passphrases do not match.") - return@OnClickListener - } + with(builder) { + val editTextLayout = layoutInflater.inflate(R.layout.dialogpassphrase, null) + setView(editTextLayout) + setTitle("Set Passphrase") + setMessage("Final step is it to set your passphrase. Minimum are 8 characters.") + setPositiveButton("Print", DialogInterface.OnClickListener { dialogInterface, i -> + val passphrase = editTextLayout.passphrase_input.text.toString(); + if (passphrase == "") { + // Make a new alert, telling the user that passphrase must not be null. + dialogInterface.cancel() + showError("Empty passphrase", "Passphrase must not be null.") + return@OnClickListener + } + if (passphrase.length < 8) { + // Make a new alert, telling the user that his passphrase doesn't meet the minimum. + dialogInterface.cancel() + showError("Weak passphrase", "Passphrase has to be at least 8 characters long.") + return@OnClickListener + } + if (passphrase != editTextLayout.passphrase2_input.text.toString()) { + // Make a new alert, telling the user that his passphrase doesn't match the repeat field. + dialogInterface.cancel() + showError("Passphrase mismatch", "Both passphrases do not match.") + return@OnClickListener + } - // Check if password is found in our offline password list - if(passwordKnown(passphrase)) { - // Make a new alert, telling the user that his passphrase was found in our local wordlist. - dialogInterface.cancel() - showError("Passphrase is compromised!", "Passphrase got found in a wordlist of bad passwords. " + - "If you already used it somewhere, change it immediately!") - return@OnClickListener - } + // Check if password is found in our offline password list + if(passwordKnown(passphrase)) { + // Make a new alert, telling the user that his passphrase was found in our local wordlist. + dialogInterface.cancel() + showError("Passphrase is compromised!", "Passphrase got found in a wordlist of bad passwords. " + + "If you already used it somewhere, change it immediately!") + return@OnClickListener + } - doPrint(editTextLayout.passphrase_input.text.toString(), + doPrint(editTextLayout.passphrase_input.text.toString(), editTextLayout.hint_input.text.toString()) - }) - setNegativeButton("Go back", DialogInterface.OnClickListener { dialogInterface, i -> - dialogInterface.dismiss() - }) - show() + }) + setNegativeButton("Go back", DialogInterface.OnClickListener { dialogInterface, i -> + dialogInterface.dismiss() + }) + show() + } } + // First check if entered password is compromised + if (passwordKnown(password_input.text.toString())) { + val builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.ErrorDialog)) + with(builder) { + setTitle("Password compromised!") + setMessage("Your password is weak and should be changed as it got found in a list of weak passwords.") + setPositiveButton("I'll change it", DialogInterface.OnClickListener { dialogInterface, i -> + }) + setNegativeButton("I don't care", DialogInterface.OnClickListener { dialogInterface, i -> + continueAfterCheck() + }) + show() + } + } } password_hide.setOnClickListener { if (password_input.transformationMethod == HideReturnsTransformationMethod.getInstance()) { @@ -227,6 +247,121 @@ class CreateActivity : AppCompatActivity() { } } + password_random.setOnClickListener { + val builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.PassphraseGenerator)) + + with (builder) { + val layout = layoutInflater.inflate(R.layout.activity_generator, null) + val zxcvbn = Zxcvbn() + var length: Int = 8 + setTitle("Generate password") + setView(layout) + + fun newPassword() { + var combined = "" + val upper_case = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + val lower_case = "abcdefghijklmnopqrstuvwxyz" + val speicals = "!\";#$%&'()*+,-./:;<=>?@[/]^_`" + val digits = "1234567890" + val emojis = arrayOf(0x1F600, 0x1F603, 0x1F601, 0x1F911, 0x1F910, 0x1F637, 0x1F47D, 0x1F480, 0x1F916) + val rand: Random = Random() + + if (layout.AZ.isChecked) combined += upper_case + if (layout.az.isChecked) combined += lower_case + if (layout.zero_nine.isChecked) combined += digits + if (layout.emojis.isChecked) { + emojis.forEach { + combined += String(Character.toChars(it)) + } + } + if (layout.special.isChecked) combined += speicals + + val sb: StringBuilder = StringBuilder(length) + for (x in 1..length) { + sb.append(combined[rand.nextInt(combined.length)]) + } + + // Adapt text size + if (sb.length > 10) { + layout.random_string.textSize = 24.0f + } else if (sb.length > 16) { + layout.random_string.textSize = 18.0f + } + + // Measure strengh + layout.progressBar.progress = zxcvbn.measure(sb.toString()) + .score + + if (layout.progressBar.progress == 1) { + layout.progressBar.progressTintList = ColorStateList.valueOf(Color.RED) + } else if (layout.progressBar.progress == 2) { + layout.progressBar.progressTintList = ColorStateList.valueOf(Color.YELLOW) + } else if (layout.progressBar.progress == 3) { + layout.progressBar.progressTintList = ColorStateList.valueOf(Color.GREEN) + } else { + layout.progressBar.progressTintList = ColorStateList.valueOf(Color.GREEN) + } + + layout.random_string.setText(sb.toString()) + } + + // Initial set + layout.length_label.setText(length.toString()) + newPassword() + + // Add length + layout.add_button.setOnClickListener { + length++ + layout.length_label.setText(length.toString()) + newPassword() + } + layout.add_button.setOnLongClickListener { + length += 10 + layout.length_label.setText(length.toString()) + newPassword() + + true + } + + // Subtract length + layout.subtract_button.setOnClickListener { + length-- + layout.length_label.setText(length.toString()) + newPassword() + } + layout.subtract_button.setOnLongClickListener { + length -= 10 + layout.length_label.setText(length.toString()) + newPassword() + + true + } + + // Refresh + layout.refresh_button.setOnClickListener { + newPassword() + } + + // Copy password + layout.copy_button.setOnClickListener { + // Copy code if already scanend + val clip: ClipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + clip.setPrimaryClip(ClipData.newPlainText("Random password", layout.random_string.text.toString())) + Toast.makeText(applicationContext, "Copied!", Toast.LENGTH_SHORT).show() + } + + setPositiveButton("Use this", + DialogInterface.OnClickListener { dialog, id -> + password_input.setText(layout.random_string.text) + }) + setNegativeButton("Go back", + DialogInterface.OnClickListener { dialog, id -> + dialog.dismiss() + }) + show() + } + + } fa_input.isFocusable = false diff --git a/app/src/main/java/com/github/mondei1/offpass/GeneratorActivity.kt b/app/src/main/java/com/github/mondei1/offpass/GeneratorActivity.kt index 777b106..f67fcfb 100644 --- a/app/src/main/java/com/github/mondei1/offpass/GeneratorActivity.kt +++ b/app/src/main/java/com/github/mondei1/offpass/GeneratorActivity.kt @@ -2,10 +2,27 @@ package com.github.mondei1.offpass import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.util.Log +import kotlinx.android.synthetic.main.activity_generator.* class GeneratorActivity : AppCompatActivity() { + var length: Int = 12 + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_generator) + + length_label.setText(length.toString()) + Log.i("Generator", "Length: $length") + add_button.setOnClickListener { + Log.i("Generator", "Length: $length") + length++ + length_label.setText(length.toString()) + } + subtract_button.setOnClickListener { + Log.i("Generator", "Length: $length") + length-- + length_label.setText(length.toString()) + } } } \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ic_add.png b/app/src/main/res/drawable-hdpi/ic_add.png new file mode 100644 index 0000000..4969fca Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_add.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_content_copy.png b/app/src/main/res/drawable-hdpi/ic_content_copy.png new file mode 100644 index 0000000..5692b30 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_content_copy.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_refresh.png b/app/src/main/res/drawable-hdpi/ic_refresh.png new file mode 100644 index 0000000..f27935e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_refresh.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_remove.png b/app/src/main/res/drawable-hdpi/ic_remove.png new file mode 100644 index 0000000..9862826 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_remove.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_add.png b/app/src/main/res/drawable-mdpi/ic_add.png new file mode 100644 index 0000000..2eab138 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_add.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_content_copy.png b/app/src/main/res/drawable-mdpi/ic_content_copy.png new file mode 100644 index 0000000..ce6d1ca Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_content_copy.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_refresh.png b/app/src/main/res/drawable-mdpi/ic_refresh.png new file mode 100644 index 0000000..63edb6a Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_refresh.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_remove.png b/app/src/main/res/drawable-mdpi/ic_remove.png new file mode 100644 index 0000000..2ecdd65 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_remove.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_add.png b/app/src/main/res/drawable-xhdpi/ic_add.png new file mode 100644 index 0000000..61c63eb Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_add.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_content_copy.png b/app/src/main/res/drawable-xhdpi/ic_content_copy.png new file mode 100644 index 0000000..22b0d7d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_content_copy.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_refresh.png b/app/src/main/res/drawable-xhdpi/ic_refresh.png new file mode 100644 index 0000000..d937505 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_refresh.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_remove.png b/app/src/main/res/drawable-xhdpi/ic_remove.png new file mode 100644 index 0000000..c2838b4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_remove.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_add.png b/app/src/main/res/drawable-xxhdpi/ic_add.png new file mode 100644 index 0000000..ec4c7be Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_add.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_content_copy.png b/app/src/main/res/drawable-xxhdpi/ic_content_copy.png new file mode 100644 index 0000000..0717571 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_content_copy.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_refresh.png b/app/src/main/res/drawable-xxhdpi/ic_refresh.png new file mode 100644 index 0000000..096b21a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_refresh.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_remove.png b/app/src/main/res/drawable-xxhdpi/ic_remove.png new file mode 100644 index 0000000..c574c92 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_remove.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_add.png b/app/src/main/res/drawable-xxxhdpi/ic_add.png new file mode 100644 index 0000000..d89882c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_add.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_content_copy.png b/app/src/main/res/drawable-xxxhdpi/ic_content_copy.png new file mode 100644 index 0000000..8fdc606 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_content_copy.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_refresh.png b/app/src/main/res/drawable-xxxhdpi/ic_refresh.png new file mode 100644 index 0000000..ee85a4d Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_refresh.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_remove.png b/app/src/main/res/drawable-xxxhdpi/ic_remove.png new file mode 100644 index 0000000..5794285 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_remove.png differ diff --git a/app/src/main/res/layout/activity_generator.xml b/app/src/main/res/layout/activity_generator.xml index 2fb6fd1..3b05d41 100644 --- a/app/src/main/res/layout/activity_generator.xml +++ b/app/src/main/res/layout/activity_generator.xml @@ -1,9 +1,171 @@ - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_view.xml b/app/src/main/res/layout/activity_view.xml index 33dce6d..7381b1f 100644 --- a/app/src/main/res/layout/activity_view.xml +++ b/app/src/main/res/layout/activity_view.xml @@ -22,7 +22,9 @@ + android:orientation="horizontal" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> bold @android:color/holo_red_light + \ No newline at end of file