Compare commits

...

11 Commits

Author SHA1 Message Date
f8e143db12 Fix password layout for smaller screens. 2020-07-15 15:26:36 +02:00
83328f42ec Fix: Password was not printable if not compromised. 2020-07-13 19:19:54 +02:00
3ffd0e063f Password generator implemented
- Passwords also get's checked if it's compromised
2020-07-13 19:17:43 +02:00
2efdce87a8 Password list get's loaded in the background
- User has to repeat his passphrase
- Passwords can now be toggled
- Error message on wrong passphrase on decryption
2020-07-12 19:51:30 +02:00
1bb1b55452 Passphrase & hint can now be set.
- Built-in password list
2020-07-12 00:30:57 +02:00
8865e9a394 QR-Codes can now be displayed after scanning.
- 2FA also supported
2020-07-09 16:18:49 +02:00
a6a9d95042 2FA codes can now be scanned. 2020-07-07 15:10:36 +02:00
576e16a2fc Fix decryption function 2020-07-05 18:16:37 +02:00
b1b4ad3030 Add compression support 2020-07-03 16:28:26 +02:00
a8e6c9e99b Basic schema parser implemented 2020-06-29 19:50:10 +02:00
1d020c5110 Basic create activity implemented 2020-06-26 18:35:14 +02:00
90 changed files with 185379 additions and 44 deletions

View File

@@ -16,6 +16,7 @@ android {
targetSdkVersion 29 targetSdkVersion 29
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -34,12 +35,27 @@ repositories {
dependencies { dependencies {
def room_version = "2.2.5"
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.0' 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"
/ One-time password /
implementation "dev.turingcomplete:kotlin-onetimepassword:2.0.1"
/ QRCode reader /
implementation 'com.journeyapps:zxing-android-embedded:4.1.0' implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

View File

@@ -6,12 +6,20 @@
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" /> <uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
<application <application
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:hardwareAccelerated="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity android:name=".GeneratorActivity"></activity>
<activity android:name=".ViewActivity" />
<activity android:name=".PassphraseActivity" />
<activity
android:name=".CreateActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:windowSoftInputMode="adjustPan" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:screenOrientation="portrait"> android:screenOrientation="portrait">
@@ -21,6 +29,7 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<meta-data <meta-data
android:name="preloaded_fonts" android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" /> android:resource="@array/preloaded_fonts" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,458 @@
package com.github.mondei1.offpass
import android.annotation.SuppressLint
import android.content.*
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.print.PrintAttributes
import android.print.PrintJob
import android.print.PrintManager
import android.text.method.HideReturnsTransformationMethod
import android.text.method.PasswordTransformationMethod
import android.util.Base64
import android.util.Log
import android.view.ContextThemeWrapper
import android.view.View
import android.view.WindowManager
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
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
class CreateActivity : AppCompatActivity() {
private var schema: QRSchema? = null
private var qrCodeBitmap: Bitmap? = null
private var fa_coroutine: Job? = null
private var mWebView: WebView? = null
private var printJob: PrintJob? = null
private var fa_uri: Uri? = null
private var known_passwords = ArrayList<String>()
/**
* Gets triggered when the user clicks on printing button (top left) and renders the paper to
* print.
*/
fun doPrint(passphrase: String, hint: String) {
// Set up printing
this.schema = QRSchema(this)
this.schema!!.title = title_input.text.toString()
this.schema!!.username = username_input.text.toString()
this.schema!!.password = password_input.text.toString()
this.schema!!.email = email_input.text.toString()
this.schema!!.website_url = url_input.text.toString()
if (fa_uri != null) {
this.schema!!.custom.put("2fa", fa_uri.toString())
}
this.schema!!.build(arrayOf(), passphrase)
val barcodeEncoder: BarcodeEncoder = BarcodeEncoder()
this.qrCodeBitmap = barcodeEncoder.encodeBitmap(this.schema!!.raw, BarcodeFormat.QR_CODE, 400, 400)
val webView = WebView(this)
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, requesst: WebResourceRequest) = false
override fun onPageFinished(view: WebView?, url: String?) {
Log.i("Create Activity", "Template page finished loading")
createWebPrintJob(webView)
mWebView = null
}
}
var htmlDocument = String(this.resources.openRawResource(
this.resources.getIdentifier("print", "raw", this.packageName)
).readBytes()).replace("\n", "")
// Prepare html document
var byteArrayOutputStream = ByteArrayOutputStream()
this.qrCodeBitmap?.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
htmlDocument = htmlDocument.replace("\$DATE", Date().time.toString())
.replace("\$HINT", hint)
.replace("\$QRCODE", Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.NO_WRAP))
Log.i("Create Activity", htmlDocument)
webView.loadDataWithBaseURL("file:///android_asset/", htmlDocument, "text/HTML", "UTF-8", null)
mWebView = webView
}
/**
* Invoked after doPrint() and takes the rendered web view and creates a new print job.
*/
fun createWebPrintJob(webView: WebView) {
(this.getSystemService(Context.PRINT_SERVICE) as? PrintManager)?.let { printManager ->
val jobName = "Offpass Document"
val printAdapter = webView.createPrintDocumentAdapter(jobName)
printManager.print(
jobName,
printAdapter,
PrintAttributes.Builder().build()
).also { printJob ->
this.printJob = printJob
}
}
}
/**
* Invoked when user gave empty passphrase.
*/
fun showError(title: String, body: String) {
val builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.ErrorDialog))
with(builder) {
setTitle(title)
setMessage(body)
setPositiveButton("Got it", DialogInterface.OnClickListener { dialogInterface, i ->
dialogInterface.dismiss()
})
show()
}
}
fun passwordKnown(password: String): Boolean {
// TODO: Doesn't work on all devices somehow :shrug:
return this.known_passwords.contains(password.toLowerCase(Locale.ROOT))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Prevent other apps from making screenshots or to record Offpass.
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
this.schema = QRSchema(this)
/*this.schema!!.decrypted_raw = "%JtuB4O9M42%Gitea|Nicolas|542superGoOD_pW&|klier.nicolas@protonmail.com|\$ul|(\$vb)\$O4|()What's your favorite series%Rick and morty|(2fa)otpauth://totp/OffPass%20Test?secret=d34gfkki5dkd5knifysrpgndd5xb2c7eddwki7ya4pvoisfa5c3ko5pv&issuer=Nicolas%20Klier"
this.schema!!.parse(this)
this.schema!!.build(arrayOf("website_url", "2fa", "What's your favorite series"), "123")
this.schema!!.decrypt(this.schema!!.raw, "123")*/
setSupportActionBar(findViewById(R.id.toolbar))
// Split word list in the background (this process can take a few seconds to complete)
GlobalScope.async {
val known_passwords_ = String(resources.openRawResource(
resources.getIdentifier("passwordlist", "raw", packageName)
).readBytes())
known_passwords = known_passwords_.split("\n") as ArrayList<String>
}
setContentView(R.layout.activity_create)
back.setOnClickListener {
Log.i("CREATE", "Back got clicked!")
finish()
}
print_button.setOnClickListener {
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
}
// 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(),
editTextLayout.hint_input.text.toString())
})
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()
}
} else {
continueAfterCheck()
}
}
password_hide.setOnClickListener {
if (password_input.transformationMethod == HideReturnsTransformationMethod.getInstance()) {
password_input.transformationMethod = PasswordTransformationMethod.getInstance()
password_hide.setImageResource(R.drawable.ic_visibility_on)
} else {
password_input.transformationMethod = HideReturnsTransformationMethod.getInstance()
password_hide.setImageResource(R.drawable.ic_visibility_off)
}
}
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
fa_input.setOnClickListener {
if (fa_coroutine != null) {
if (fa_coroutine!!.isActive) {
// Copy code if already scanend
Toast.makeText(this, "Copied!", Toast.LENGTH_SHORT).show()
val clip: ClipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clip.setPrimaryClip(ClipData.newPlainText("2FA code", fa_input.text))
return@setOnClickListener
}
}
// Open QR-Code scanner
IntentIntegrator(this)
.setPrompt("Scan 2FA secret")
.setBeepEnabled(false)
.setBarcodeImageEnabled(false)
.initiateScan()
}
}
@SuppressLint("SimpleDateFormat", "SetTextI18n")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
var result: IntentResult =
IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
if (result.contents == null) {
Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show()
} else {
// We scanned a 2FA code
if (result.contents.startsWith("otpauth://totp/")) {
fa_progress.visibility = View.VISIBLE
fa_uri = Uri.parse(result.contents)
if (fa_uri?.getQueryParameter("secret").toString() == "" || fa_uri?.getQueryParameter("secret") == null) {
showError("Corrupt 2FA", "The scanned 2FA secret doesn't contain a secret!")
return
}
val fa_generator = GoogleAuthenticator(base32secret = fa_uri?.getQueryParameter("secret").toString())
this.schema!!.custom.put("2fa", fa_uri?.getQueryParameter("secret").toString())
/*val objAnim: ObjectAnimator = ObjectAnimator.ofInt(fa_progress, "progress")
objAnim.duration = 300
objAnim.interpolator = LinearInterpolator()
objAnim.start()*/
// TODO: There is a bug. When the user changes his current app, this coroutine stops.
fa_coroutine = GlobalScope.launch(Dispatchers.Main) {
while (true) {
try {
fa_input.setText(fa_generator.generate())
} catch (error: IllegalArgumentException) {
showError("Corrupt 2FA", "The scanned 2FA secret is corrupt and therefore no codes can be generated.")
return@launch
}
var seconds_remaining = CryptoOperations().getRemainingTOTPTime()
Log.i("2FA Generator", "Remaining: $seconds_remaining")
if (seconds_remaining == 0) seconds_remaining = 30
// Change color
if (seconds_remaining >= 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")})"
} else {
showError("No 2FA", "The scanned QR-Code has the wrong format.")
}
}
}
override fun onStop() {
super.onStop()
fa_coroutine?.cancel(CancellationException("Activity is stopping"))
}
}

View File

@@ -0,0 +1,154 @@
package com.github.mondei1.offpass
import android.os.HandlerThread
import android.os.Looper
import android.util.Base64
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
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
class EncryptionResult(
val result: String = "",
val salt: ByteArray = ByteArray(1),
val iv: ByteArray = ByteArray(1)
) {
}
class CryptoOperations {
private val random: SecureRandom = SecureRandom()
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
}
/**
* 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, 8000, 256)
val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
val hash = factory.generateSecret(spec).encoded
Log.i("Crypto", "Hashed ${password.toString()} to ${Base64.encodeToString(hash, Base64.DEFAULT)}")
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()
val key_bytes: ByteArray = hash(key.toCharArray(), salt).toByteArray(Charsets.UTF_8)
val aes_key: ByteArray = ByteArray(32)
// Now make the key bytes fit into 256 bits
if (key_bytes.size < aes_key.size) {
throw Error("Tried to cut down hash bytes to 32 but there are less then that.")
}
for (i in aes_key.indices) {
aes_key.set(i, key_bytes[i])
}
val key: SecretKey = SecretKeySpec(aes_key, "AES")
val cipher: Cipher = Cipher.getInstance("AES/CBC/ISO10126Padding")
// Performing actual crypto operation
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))
Log.d("Crypto", "Encrypted $plain => ${Base64.encodeToString(ciphered, Base64.NO_WRAP)}")
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)
// Now make the key bytes fit into 256
if (key_bytes.size < aes_key.size) {
throw Error("Tried to cut down hash bytes to 32 but there are less then that.")
}
for (i in 0..aes_key.size-1) {
aes_key.set(i, key_bytes[i])
}
val encrypted_raw = Base64.decode(encrypted, Base64.NO_WRAP)
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/CBC/ISO10126Padding")
// Performing actual crypto operation
cipher.init(Cipher.DECRYPT_MODE, keySpec, IvParameterSpec(iv))
val ciphered = cipher.doFinal(encrypted_raw) // TODO: Catch error if decryption goes wrong
// Concat everything into one byte array
val byteBuffer: ByteBuffer = ByteBuffer.allocate(ciphered.size)
byteBuffer.put(ciphered)
Log.d("Crypto", "Decrypted $encrypted => ${String(byteBuffer.array())}")
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()
random.nextBytes(iv)
return iv
}
/**
* Produces secure random string. <b>This function can be slow, use only if necessary!</b>
*/
fun nextString(length: Int): String {
chars = CharArray(length)
for (idx in chars.indices) chars[idx] = possibleChars[random.nextInt(possibleChars.length)]
Log.i("Random", "Produced random string: ${String(chars)}")
return String(chars)
}
}

View File

@@ -0,0 +1,28 @@
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())
}
}
}

View File

@@ -5,9 +5,13 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.github.mondei1.offpass.entities.Compression
import com.github.mondei1.offpass.entities.CompressionHelper
import com.google.zxing.integration.android.IntentIntegrator import com.google.zxing.integration.android.IntentIntegrator
import com.google.zxing.integration.android.IntentResult import com.google.zxing.integration.android.IntentResult
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@@ -17,13 +21,22 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
var dbHandler = CompressionHelper(this, null)
dbHandler.writableDatabase.execSQL("DELETE FROM `${CompressionHelper.TABLE_NAME}`;")
dbHandler.writableDatabase.close()
main_screen.setOnClickListener { main_screen.setOnClickListener {
IntentIntegrator(this) IntentIntegrator(this)
.setPrompt("Scan OffPass created QR-Code") .setPrompt("Scan OffPass created QR-Code")
.setBeepEnabled(false) .setBeepEnabled(false)
.setBarcodeImageEnabled(false) .setBarcodeImageEnabled(false)
.initiateScan() .initiateScan()
Log.i("Main", "Clicked on text") }
add_button.setOnClickListener {
val intent: Intent = Intent(this, CreateActivity::class.java)
startActivity(intent)
} }
} }
@@ -31,15 +44,14 @@ class MainActivity : AppCompatActivity() {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
var result: IntentResult = var result: IntentResult =
IntentIntegrator.parseActivityResult(requestCode, resultCode, data) IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
if (result != null) {
if (result.contents == null) { if (result.contents == null) {
Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show() Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show()
} else { } else {
Log.i("SCANNER", "Scanned: " + result.contents) // TODO: Check if scanned QR-Code is an OffPass one.
Toast.makeText(this, "Scanned: " + result.contents, Toast.LENGTH_LONG).show() val intent: Intent = Intent(this, PassphraseActivity::class.java)
} intent.putExtra("raw", result.contents)
} else { startActivity(intent)
super.onActivityResult(requestCode, resultCode, data) //Toast.makeText(this, "Scanned: " + result.contents, Toast.LENGTH_LONG).show()
} }
} }

View File

@@ -0,0 +1,48 @@
package com.github.mondei1.offpass
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.view.View
import kotlinx.android.synthetic.main.activity_passphrase.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.runBlocking
import javax.crypto.BadPaddingException
import kotlin.reflect.typeOf
class PassphraseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_passphrase)
val raw: String = intent.getStringExtra("raw")!!
val qrSchema: QRSchema = QRSchema(this)
back.setOnClickListener {
finish()
}
decrypt_button.setOnClickListener {
decrypt_button.text = "Decrypting ..."
var _continue = true;
runBlocking {
try {
qrSchema.decrypt(raw, passphrase_input.text.toString())
} catch (err: BadPaddingException) {
failed.visibility = View.VISIBLE
decrypt_button.text = "Decrypt"
_continue = false
return@runBlocking
}
}
if(!_continue) return@setOnClickListener
val intent: Intent = Intent(this, ViewActivity::class.java)
intent.putExtra("decrypted_raw",qrSchema.decrypted_raw)
startActivity(intent)
}
}
}

View File

@@ -0,0 +1,234 @@
package com.github.mondei1.offpass
import android.content.Context
import android.util.Base64
import android.util.Log
import com.github.mondei1.offpass.entities.Compression
import com.github.mondei1.offpass.entities.CompressionHelper
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import java.lang.Error
class QRSchema {
private var db: CompressionHelper? = null
var raw: String = ""
var decrypted_raw: String = ""
// Parsed content
var session_key: String = ""
lateinit var title: String
lateinit var username: String
lateinit var password: String
lateinit var email: String
lateinit var website_url: String
var custom: HashMap<String, String> = HashMap() // All defined custom/optional fields
var question_awnser: HashMap<String, String> = HashMap() // Used for security questions
constructor(context: Context) {
this.db = CompressionHelper(context, null)
}
/**
* 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("$", "")
Log.i("QR-Code schema", "Try to resolve $to_resolve")
val database = db
return GlobalScope.async<String> {
var cursor = database!!.get(session_key, to_resolve)
if (cursor != null) {
cursor.toString()
} else {
""
}
}.await()
}
/**
* 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 == "") {
throw Error("Tried to parse QR-Code schema but raw content must be first decrypted! " +
"Set raw content first and then use decrypt()")
}
// First will be to split the string into it's parts
val fields: MutableList<String> = 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.
if (i == 0) {
// Check if there is a session key
if (fields[0].startsWith("%")) {
fields[0] = fields[0].replace("%", "")
session_key = fields[0].substring(0, 10) // Get first 10 chars, which are the
// session key
this.title = fields[0].substring(10, fields[0].length)
} else {
this.title = fields[0]
}
this.username = fields[1]
this.password = fields[2]
this.email = fields[3]
this.website_url = fields[4]
// Look if website url is compressed
if (this.website_url.startsWith("$")) {
val that_class = this
runBlocking {
that_class.website_url = resolve(that_class.website_url, context)
}
}
}
// Here are optional/custom fields
if (i > 4) {
if (fields[i].startsWith("(") && fields[i] != "") {
var closingBracket = fields[i].indexOf(")")
var key = fields[i].substring(1, closingBracket)
var value = fields[i].substring(closingBracket+1, fields[i].length)
// We got a security question/awnser
if (key == "") {
val qa = value.split("%")
question_awnser.put(qa[0], qa[1]);
} else {
// Check if key and or value are compressed
if (key.startsWith("$")) {
runBlocking {
key = resolve(key, context)
}
}
if (value.startsWith("$")) {
runBlocking {
value = resolve(value, context)
}
}
custom.put(key, value)
}
} else {
throw Error("Custom/optional field should start with an open bracket: "
+ fields[i].toString())
}
}
}
Log.i("QR-Code schema", "Found: $session_key, $title, $username, $password, " +
"$email, $website_url. ${custom.toString()} and ${question_awnser.toString()}")
return true
}
/**
* This function will take an raw string input and will decrypt it.
*
* @return It returns the decrypted raw value.
*/
fun decrypt(raw: String, passphrase: String): String {
val parts = raw.split(":")
val iv = parts[1]
val salt = parts[2]
val encrypted_content = parts[3]
Log.i("Decrypt schema", "Give data: $encrypted_content")
this.decrypted_raw = CryptoOperations().decrypt(
encrypted_content, passphrase,
iv.toByteArray(), salt.toByteArray())
return this.decrypted_raw
}
/**
* This function will take all defined variables and converts them into the QR-Code schema.
*
* @param compress_values Takes an string array where a entry can be `website_url` in case of an URL or
* the key of an optional field. For a security question take the question as key.
*/
fun build(compress_values: Array<String>, passphrase: String): String {
/* First phase is to construct the encrypted data. */
val session_key = CryptoOperations().nextString(10) // used for
var used_compression: Boolean = false
var url_copy = website_url
if (compress_values.contains("website_url")) {
used_compression = true
val email_key = CryptoOperations().nextString(4)
this.db?.add(session_key, Compression(email_key, website_url, null, null))
url_copy = "\$$email_key"
}
var encrypted_content: String =
"${this.title}|${this.username}|${this.password}|${this.email}|${url_copy}|"
// Loop thought optional/custom fields
for (i in this.custom) {
if (compress_values.contains(i.key)) {
used_compression = true
var key_key = CryptoOperations().nextString(4)
var key_value = CryptoOperations().nextString(4)
this.db?.add(session_key,
Compression(key_key, i.key, null, null))
this.db?.add(session_key,
Compression(key_value, i.value, null, null))
encrypted_content += "(\$$key_key)\$$key_value|"
} else {
encrypted_content += "(${i.key})${i.value}|"
}
}
// Loop thought security questions
for (i in this.question_awnser) {
used_compression = true
if (compress_values.contains(i.key)) {
var key_key = CryptoOperations().nextString(4)
var key_value = CryptoOperations().nextString(4)
this.db?.add(session_key,
Compression(key_key, i.key, null, null))
this.db?.add(session_key,
Compression(key_value, i.value, null, null))
encrypted_content += "()\$$key_key%\$$key_value|"
} else {
encrypted_content += "()${i.key}%${i.value}|"
}
}
encrypted_content = encrypted_content.dropLast(1)
if (used_compression) encrypted_content = "%$session_key%$encrypted_content"
Log.i("QR-Code Builder", "Constructed encrypted content: $encrypted_content")
// Now, let's encrypt that
val enc = runBlocking {
CryptoOperations().encrypt(passphrase, encrypted_content)
}
// TODO: Make schema version dynamic (currently hardcoded)
var final = "op1:" +
"${String(enc.iv)}:" +
"${String(enc.salt)}:" +
enc.result
.replace("\n", "")
Log.i("QR-Code Builder", "Returning final result: $final")
this.raw = final
return final
}
}

View File

@@ -0,0 +1,66 @@
package com.github.mondei1.offpass
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_text_input.*
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_HEAD = "param1"
private const val ARG_TITLE = "param2"
private const val ARG_TITLE_SIZE = "param3"
/**
* A simple [Fragment] subclass.
* Use the [TextInput.newInstance] factory method to
* create an instance of this fragment.
*/
class TextInput : Fragment() {
// TODO: Rename and change types of parameters
private var arg_head: String? = null
private var arg_title: String? = null
private var arg_title_size: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
arg_title = it.getString(ARG_TITLE)
arg_head = it.getString(ARG_HEAD)
arg_title_size = it.getString(ARG_TITLE_SIZE)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_text_input, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
head.text = arg_head
title_.hint = arg_title
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(head: String, title: String, title_size: String) =
TextInput().apply {
arguments = Bundle().apply {
putString(ARG_HEAD, head)
putString(ARG_TITLE, title)
putString(ARG_TITLE_SIZE, title_size)
}
}
}
}

View File

@@ -0,0 +1,120 @@
package com.github.mondei1.offpass
import android.R.attr.key
import android.annotation.SuppressLint
import android.app.ActionBar
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.text.method.HideReturnsTransformationMethod
import android.text.method.PasswordTransformationMethod
import android.util.Log
import android.view.View
import android.view.WindowManager
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import dev.turingcomplete.kotlinonetimepassword.GoogleAuthenticator
import kotlinx.android.synthetic.main.activity_create.*
import kotlinx.android.synthetic.main.activity_view.*
import kotlinx.android.synthetic.main.activity_view.back
import kotlinx.android.synthetic.main.activity_view.email_input
import kotlinx.android.synthetic.main.activity_view.fa_input
import kotlinx.android.synthetic.main.activity_view.fa_label
import kotlinx.android.synthetic.main.activity_view.fa_progress
import kotlinx.android.synthetic.main.activity_view.password_hide
import kotlinx.android.synthetic.main.activity_view.password_input
import kotlinx.android.synthetic.main.activity_view.title_label
import kotlinx.android.synthetic.main.activity_view.url_input
import kotlinx.android.synthetic.main.activity_view.username_input
import kotlinx.coroutines.*
class ViewActivity : AppCompatActivity() {
var fa_coroutine: Job? = null
override fun onBackPressed() {
val i = Intent(this, MainActivity::class.java)
i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
startActivity(i)
}
override fun setContentView(view: View?) {
// Prevent other apps from making screenshots or to record Offpass.
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
}
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view)
val qrSchema: QRSchema = QRSchema(this)
back.setOnClickListener {
val i = Intent(this, MainActivity::class.java)
i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
startActivity(i)
}
password_hide.setOnClickListener {
if (password_input.transformationMethod == HideReturnsTransformationMethod.getInstance()) {
password_input.transformationMethod = PasswordTransformationMethod.getInstance()
password_input.text = "123456"
password_hide.setImageResource(R.drawable.ic_visibility_on)
} else {
password_input.transformationMethod = HideReturnsTransformationMethod.getInstance()
password_input.text = qrSchema.password
password_hide.setImageResource(R.drawable.ic_visibility_off)
}
}
qrSchema.decrypted_raw = intent.getStringExtra("decrypted_raw")!!
qrSchema.parse(this)
title_label.text = qrSchema.title
username_input.setText(qrSchema.username, TextView.BufferType.EDITABLE)
password_input.setText("123456", TextView.BufferType.EDITABLE)
email_input.setText(qrSchema.email, TextView.BufferType.EDITABLE)
url_input.setText(qrSchema.website_url, TextView.BufferType.EDITABLE)
if (qrSchema.custom.containsKey("2fa")) {
val fa_uri = Uri.parse(qrSchema.custom.get("2fa"))
val fa_generator = GoogleAuthenticator(base32secret = fa_uri.getQueryParameter("secret").toString())
fa_label.text = "${fa_uri.path?.replace("/", "")} (${fa_uri.getQueryParameter("issuer")})"
fa_progress.visibility = View.VISIBLE
fa_coroutine = GlobalScope.launch(Dispatchers.Main) {
while (true) {
fa_input.setText(fa_generator.generate())
var seconds_remaining = CryptoOperations().getRemainingTOTPTime()
Log.i("2FA Generator", "Remaining: $seconds_remaining")
if (seconds_remaining == 0) seconds_remaining = 30
// Change color
if (seconds_remaining >= 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)
}
}
} else {
fa_layout.visibility = View.GONE
}
}
override fun onStop() {
super.onStop()
fa_coroutine?.cancel(CancellationException("Activity is stopping"))
}
}

View File

@@ -0,0 +1,15 @@
package com.github.mondei1.offpass.entities
class Compression {
var key: String = "DEFAULT"
var value: String? = null
var iv: ByteArray? = null
var salt: ByteArray? = null
constructor(key: String, value: String, iv: ByteArray?, salt: ByteArray?) {
this.key = key
this.value = value
this.iv = iv
this.salt = salt
}
}

View File

@@ -0,0 +1,91 @@
package com.github.mondei1.offpass.entities
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.util.Base64
import android.util.Log
import com.github.mondei1.offpass.CryptoOperations
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.nio.charset.Charset
import kotlin.system.measureTimeMillis
class CompressionHelper(context: Context, factory: SQLiteDatabase.CursorFactory?):
SQLiteOpenHelper(context, DATABASE_NAME, factory, DATABASE_VERSION) {
private fun init_db(db: SQLiteDatabase) {
//db.execSQL("DROP TABLE $TABLE_NAME")
db.execSQL(
"CREATE TABLE IF NOT EXISTS `$TABLE_NAME` " +
"(`$COLUMN_KEY` VARCHAR(4) PRIMARY KEY, `$COLUMN_VALUE` TEXT, `$COLUMN_IV` BLOB," +
" `$COLUMN_SALT` BLOB)"
)
}
override fun onCreate(db: SQLiteDatabase) {
this.init_db(db)
}
override fun onOpen(db: SQLiteDatabase) {
super.onOpen(db)
this.init_db(db)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
// Do nothing. We don't want to delete any sensitive values.
}
fun add(session_key: String, compression: Compression) {
val encrypted_result = GlobalScope.async {
CryptoOperations().encrypt(session_key, compression.value!!)
}
runBlocking {
val res = encrypted_result.await()
writableDatabase.execSQL("INSERT INTO $TABLE_NAME (`$COLUMN_KEY`, `$COLUMN_VALUE`, `$COLUMN_IV`, `$COLUMN_SALT`) " +
"VALUES (?, ?, ?, ?)",
arrayOf(compression.key, res.result, res.iv, res.salt))
Log.d("Compression Helper", "Add key ${compression.key} = $res to database.")
}
}
/**
* @return The encrypted content and the IV used for encryption of a specified key.
* The returned array looks like that: [key, encrypted_value, iv]
*/
suspend fun get(session_key: String, key: String): String? {
var cursor = this.readableDatabase.rawQuery("SELECT * FROM $TABLE_NAME WHERE `key`=?", arrayOf(key))
var list: ArrayList<String> = ArrayList<String>()
var salt: ByteArray = ByteArray(16)
var iv: ByteArray = ByteArray(16)
with(cursor) {
while (moveToNext()) {
list.add(getString(getColumnIndex(COLUMN_VALUE)))
iv = getBlob(getColumnIndex(COLUMN_IV))
salt = getBlob(getColumnIndex(COLUMN_SALT))
}
}
val decrypted_result = GlobalScope.async {
// TODO: Is a list even required?
CryptoOperations().decrypt(list[0], session_key, iv, salt)
}
return decrypted_result.await().toString()
}
companion object {
private val DATABASE_VERSION = 3
private val DATABASE_NAME = "compression.db"
val TABLE_NAME = "compression"
val COLUMN_KEY = "key"
val COLUMN_VALUE = "value"
val COLUMN_SALT = ""
val COLUMN_IV = "iv"
}
}

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#fff"
android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM12,8h-2L10,4h2v4zM15,8h-2L13,4h2v4zM18,8h-2L16,4h2v4z" />
</vector>

View File

@@ -0,0 +1,10 @@
<!-- drawable/zip_box_outline.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="48dp"
android:width="48dp"
android:viewportWidth="26"
android:viewportHeight="26">
<path
android:fillColor="#fff"
android:pathData="M12 17V15H14V17H12M14 13V11H12V13H14M14 9V7H12V9H14M10 11H12V9H10V11M10 15H12V13H10V15M21 5V19C21 20.1 20.1 21 19 21H5C3.9 21 3 20.1 3 19V5C3 3.9 3.9 3 5 3H19C20.1 3 21 3.9 21 5M19 5H12V7H10V5H5V19H19V5Z" />
</vector>

View File

@@ -0,0 +1,10 @@
<!-- drawable/printer.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M18,3H6V7H18M19,12A1,1 0 0,1 18,11A1,1 0 0,1 19,10A1,1 0 0,1 20,11A1,1 0 0,1 19,12M16,19H8V14H16M19,8H5A3,3 0 0,0 2,11V17H6V21H18V17H22V11A3,3 0 0,0 19,8Z" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TextInput">
<!-- TODO: Update blank fragment layout -->
<TextView
android:id="@+id/head"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textColor="@color/colorPrimaryDark"
android:text="@string/text_input_head_undefined" />
<EditText
android:id="@+id/editTextTextPersonName"
style="@style/Widget.AppCompat.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:ems="15"
android:inputType="textPersonName"
android:singleLine="true"
android:textFontWeight="600"
android:textSize="30sp"
android:background="@color/colorPrimary"
android:hint="@string/text_input_title_undefined" />
</FrameLayout>

View File

@@ -0,0 +1,318 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/AppTheme"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
tools:context=".CreateActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryDark"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageButton
android:id="@+id/print_button"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="88dp"
android:layout_height="55dp"
android:layout_margin="0dp"
android:layout_marginStart="64dp"
android:backgroundTint="#F8F8F8"
android:padding="0dp"
android:src="@drawable/printer"
android:textColor="@color/colorPrimary"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/back"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="56dp"
android:layout_height="50dp"
android:layout_marginEnd="6dp"
android:src="@drawable/baseline_arrow_forward_white_36"
android:text=""
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/settings"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="32dp"
android:layout_height="31dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="72dp"
android:layout_marginBottom="6dp"
android:scaleX="1"
android:scaleY="1"
android:src="@drawable/baseline_settings_white_36"
android:text=""
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/settings2"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="38dp"
android:layout_height="38dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="116dp"
android:layout_marginBottom="3dp"
android:src="@drawable/ic_compress"
android:text=""
android:textColor="#000000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.appcompat.widget.Toolbar>
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="56dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/url_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="Login URL"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/email_input" />
<EditText
android:id="@+id/email_input"
style="@style/Widget.AppCompat.AutoCompleteTextView"
android:layout_width="354dp"
android:layout_height="97dp"
android:layout_marginStart="16dp"
android:background="@color/colorPrimary"
android:ems="10"
android:fontFamily="sans-serif-black"
android:hint="ricklactica42@example.com"
android:inputType="none|textEmailAddress"
android:singleLine="true"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/email_label" />
<TextView
android:id="@+id/username_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="Username"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_input" />
<EditText
android:id="@+id/password_input"
android:layout_width="0dp"
android:layout_height="81dp"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:background="@color/colorPrimary"
android:ems="10"
android:fontFamily="sans-serif-black"
android:hint="Secure password"
android:inputType="textPassword"
android:singleLine="true"
android:textSize="28sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password_label"
app:layout_constraintWidth_percent="0.6" />
<EditText
android:id="@+id/username_input"
style="@style/Widget.AppCompat.AutoCompleteTextView"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_marginStart="16dp"
android:background="@color/colorPrimary"
android:ems="10"
android:fontFamily="sans-serif-black"
android:hint="Ricklactica42"
android:inputType="textPersonName"
android:singleLine="true"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username_label" />
<TextView
android:id="@+id/password_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="Password"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username_input" />
<EditText
android:id="@+id/url_input"
style="@style/Widget.AppCompat.AutoCompleteTextView"
android:layout_width="354dp"
android:layout_height="48dp"
android:layout_marginStart="15dp"
android:background="@color/colorPrimary"
android:ems="10"
android:fontFamily="sans-serif-black"
android:hint="https://example.com/login"
android:inputType="none|textEmailAddress"
android:singleLine="true"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/url_label" />
<EditText
android:id="@+id/title_input"
style="@style/Widget.AppCompat.AutoCompleteTextView"
android:layout_width="283dp"
android:layout_height="50dp"
android:layout_marginStart="16dp"
android:background="@color/colorPrimary"
android:ems="10"
android:fontFamily="sans-serif-black"
android:hint="Google Account"
android:inputType="textPersonName"
android:singleLine="true"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_label" />
<TextView
android:id="@+id/email_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="E-Mail"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password_input" />
<TextView
android:id="@+id/title_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="Title"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/password_hide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:background="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="@+id/password_input"
app:layout_constraintEnd_toStartOf="@+id/password_random"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/password_input"
app:layout_constraintTop_toTopOf="@+id/password_input"
app:layout_constraintVertical_bias="0.48000002"
app:srcCompat="@drawable/ic_visibility_on" />
<ImageButton
android:id="@+id/password_random"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="15dp"
android:background="@color/colorPrimary"
android:tint="#000000"
app:layout_constraintBottom_toBottomOf="@+id/password_input"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/password_hide"
app:layout_constraintTop_toTopOf="@+id/password_input"
app:srcCompat="@drawable/ic_autorenew" />
<ProgressBar
android:id="@+id/fa_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="379dp"
android:layout_height="15dp"
android:layout_marginStart="16dp"
android:max="30"
android:visibility="invisible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fa_input" />
<TextView
android:id="@+id/fa_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="Two factor authentication"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/url_input" />
<EditText
android:id="@+id/fa_input"
style="@style/Widget.AppCompat.AutoCompleteTextView"
android:layout_width="354dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:background="#00FFFFFF"
android:editable="false"
android:ems="10"
android:fontFamily="sans-serif-black"
android:hint="TAP TO SCAN"
android:inputType="none|textEmailAddress"
android:singleLine="true"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fa_label" />
<Button
android:id="@+id/button"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="256dp"
android:layout_height="42dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:background="@color/colorPrimaryDark"
android:text="Add more"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fa_progress" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:context=".GeneratorActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="350dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="256dp"
android:layout_height="10dp"
android:layout_marginStart="80dp"
android:layout_marginTop="100dp"
android:layout_marginEnd="80dp"
android:progress="2"
android:min="0"
android:max="4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/random_string"
android:layout_width="201dp"
android:layout_height="wrap_content"
android:layout_marginStart="80dp"
android:layout_marginEnd="80dp"
android:layout_marginBottom="10dp"
android:gravity="center_vertical"
android:text="D%g$MA0H8d"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="30sp"
app:layout_constraintBottom_toTopOf="@+id/progressBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="@+id/refresh_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="#00FFFFFF"
app:layout_constraintBottom_toTopOf="@+id/progressBar"
app:layout_constraintEnd_toStartOf="@+id/random_string"
app:srcCompat="@drawable/ic_refresh" />
<ImageButton
android:id="@+id/copy_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:background="#00FFFFFF"
app:layout_constraintBottom_toTopOf="@+id/progressBar"
app:layout_constraintStart_toEndOf="@+id/random_string"
app:srcCompat="@drawable/ic_content_copy" />
<LinearLayout
android:id="@+id/password_length_container"
android:layout_width="142dp"
android:layout_height="45dp"
android:layout_marginStart="1dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="1dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progressBar">
<ImageButton
android:id="@+id/subtract_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:layout_weight="1"
android:background="#00FFFFFF"
app:srcCompat="@drawable/ic_remove" />
<TextView
android:id="@+id/length_label"
android:layout_width="45dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:fontFamily="sans-serif-light"
android:gravity="center_horizontal|center_vertical"
android:text="0"
android:textColor="@color/colorPrimaryDark"
android:textSize="24sp" />
<ImageButton
android:id="@+id/add_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:layout_weight="1"
android:background="#00FFFFFF"
app:srcCompat="@drawable/ic_add" />
</LinearLayout>
<CheckBox
android:id="@+id/AZ"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:checked="true"
android:text="A-Z"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password_length_container" />
<CheckBox
android:id="@+id/az"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp"
android:checked="true"
android:text="a-z"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/AZ" />
<CheckBox
android:id="@+id/zero_nine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp"
android:checked="true"
android:text="0-9"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/az" />
<CheckBox
android:id="@+id/special"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp"
android:checked="true"
android:text="!&quot;#$%'()*+"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/zero_nine" />
<CheckBox
android:id="@+id/emojis"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp"
android:text="😃👽😺👋"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/special" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@@ -66,7 +66,7 @@
app:srcCompat="@android:drawable/ic_menu_set_as" /> app:srcCompat="@android:drawable/ic_menu_set_as" />
<Button <Button
android:id="@+id/imageButton2" android:id="@+id/add_button"
style="@style/Widget.AppCompat.Button.Borderless" style="@style/Widget.AppCompat.Button.Borderless"
android:layout_height="120dp" android:layout_height="120dp"
android:layout_weight="1" android:layout_weight="1"
@@ -90,12 +90,13 @@
android:id="@+id/textView" android:id="@+id/textView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginBottom="5dp" android:layout_marginBottom="5dp"
android:fontFamily="@font/righteous" android:fontFamily="@font/righteous"
android:text="ALPHA" android:text="ALPHA"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
android:textSize="36sp" android:textSize="36sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
tools:layout_editor_absoluteX="291dp" /> app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark"
tools:context=".PassphraseActivity">
<EditText
android:id="@+id/passphrase_input"
android:layout_width="286dp"
android:layout_height="61dp"
android:layout_marginStart="88dp"
android:layout_marginEnd="89dp"
android:backgroundTint="#757575"
android:ems="10"
android:hint="Passphrase"
android:includeFontPadding="false"
android:inputType="textPassword"
android:letterSpacing="0.2"
android:paddingBottom="15dp"
android:textAlignment="center"
android:textColor="@color/colorPrimary"
android:textColorHint="#757575"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/decrypt_button"
style="@style/Widget.AppCompat.Button"
android:layout_width="152dp"
android:layout_height="59dp"
android:layout_marginStart="161dp"
android:layout_marginTop="48dp"
android:layout_marginEnd="162dp"
android:background="#00FFFFFF"
android:backgroundTint="#001C1C1C"
android:hapticFeedbackEnabled="false"
android:text="Decrypt"
android:textColor="@color/colorPrimary"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.484"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/passphrase_input" />
<Button
android:id="@+id/back"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="163dp"
android:layout_marginEnd="160dp"
android:layout_marginBottom="30dp"
android:fontFamily="sans-serif-light"
android:text="Back"
android:textColor="#AAAAAA"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/failed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="180dp"
android:layout_marginEnd="180dp"
android:layout_marginBottom="40dp"
android:fontFamily="sans-serif-black"
android:text="Wrong Passphrase"
android:textColor="#E53935"
android:textSize="18sp"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@+id/passphrase_input"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,230 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/AppTheme"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
tools:context=".ViewActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryDark"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:titleMargin="0dp">
</androidx.appcompat.widget.Toolbar>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/back"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="56dp"
android:layout_height="50dp"
android:rotation="180"
android:src="@drawable/baseline_arrow_forward_white_36"
android:text="" />
<TextView
android:id="@+id/title_label"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:fontFamily="sans-serif-black"
android:paddingTop="8dp"
android:text="TITLE"
android:textAlignment="viewStart"
android:textColor="@color/colorPrimary"
android:textSize="24sp" />
</LinearLayout>
<ScrollView
android:id="@+id/scrollView2"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/fa_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/url_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="Login URL"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/email_input" />
<TextView
android:id="@+id/email_input"
style="@style/Widget.AppCompat.AutoCompleteTextView"
android:layout_width="354dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:background="@color/colorPrimary"
android:ems="10"
android:fontFamily="sans-serif-black"
android:singleLine="true"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/email_label" />
<TextView
android:id="@+id/username_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="Username"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/password_input"
android:layout_width="288dp"
android:layout_height="68dp"
android:layout_marginStart="16dp"
android:background="@color/colorPrimary"
android:fontFamily="sans-serif-black"
android:gravity="center_vertical"
android:inputType="textPassword"
android:singleLine="true"
android:textColor="@color/colorPrimaryDark"
android:textSize="42sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password_label" />
<TextView
android:id="@+id/username_input"
style="@style/Widget.AppCompat.AutoCompleteTextView"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_marginStart="16dp"
android:background="@color/colorPrimary"
android:ems="10"
android:fontFamily="sans-serif-black"
android:singleLine="true"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username_label" />
<TextView
android:id="@+id/password_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="Password"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username_input" />
<TextView
android:id="@+id/url_input"
style="@style/Widget.AppCompat.AutoCompleteTextView"
android:layout_width="354dp"
android:layout_height="48dp"
android:layout_marginStart="15dp"
android:background="@color/colorPrimary"
android:ems="10"
android:fontFamily="sans-serif-black"
android:singleLine="true"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/url_label" />
<TextView
android:id="@+id/email_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:text="E-Mail"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password_input" />
<ImageButton
android:id="@+id/password_hide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="28dp"
android:background="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="@+id/password_input"
app:layout_constraintStart_toEndOf="@+id/password_input"
app:layout_constraintTop_toTopOf="@+id/password_input"
app:layout_constraintVertical_bias="0.45"
app:srcCompat="@drawable/ic_visibility_on" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/fa_layout"
android:layout_width="match_parent"
android:layout_height="120dp"
android:background="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/scrollView2">
<TextView
android:id="@+id/fa_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="Two factor authentication"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/fa_input"
style="@style/Widget.AppCompat.AutoCompleteTextView"
android:layout_width="354dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:background="@color/colorPrimary"
android:editable="false"
android:ems="10"
android:fontFamily="sans-serif-black"
android:hint="TAP TO SCAN"
android:inputType="none|textEmailAddress"
android:singleLine="true"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fa_label" />
<ProgressBar
android:id="@+id/fa_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="379dp"
android:layout_height="15dp"
android:layout_marginStart="16dp"
android:layout_marginTop="-10dp"
android:layout_marginEnd="16dp"
android:max="30"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fa_input" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="@color/colorPrimaryDark"
android:paddingLeft="50dp"
android:paddingRight="50dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/passphrase_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:backgroundTint="@color/colorPrimary"
android:textColorHint="@color/colorPrimary"
android:textColor="@color/colorPrimary"
android:hint="Enter passphrase"/>
<EditText
android:id="@+id/passphrase2_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:backgroundTint="@color/colorPrimary"
android:textColorHint="@color/colorPrimary"
android:textColor="@color/colorPrimary"
android:hint="Repeat passphrase"/>
<EditText
android:id="@+id/hint_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorPrimary"
android:textColorHint="@color/colorPrimary"
android:textColor="@color/colorPrimary"
android:layout_marginTop="10dp"
android:layout_marginBottom="30dp"
android:maxLength="32"
android:hint="Enter hint (optional)" />
</LinearLayout>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TextInput">
<TextView
android:id="@+id/head"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/text_input_head_undefined"
android:textAllCaps="false"
android:textColor="@color/colorPrimaryDark"
android:textSize="20sp" />
<EditText
android:id="@+id/title_"
style="@style/Widget.AppCompat.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:ems="15"
android:inputType="textPersonName"
android:singleLine="true"
android:textStyle="bold"
android:textSize="30sp"
android:hint="@string/text_input_title_undefined" />
<ImageButton
android:id="@+id/imageButton2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="350dp"
android:layout_marginTop="20dp"
android:background="@color/colorPrimary"
android:src="@drawable/baseline_visibility_black_48" />
</FrameLayout>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html>
<head>
<!-- Head is not important since
we will only print the body-->
</head>
<body>
<style>
@media print {
body {
max-height: 100vh;
page-break-after: always;
}
}
@font-face {
font-family: Roboto;
src: url(../font/robotolight.ttf);
font-weight: lighter;
}
@font-face {
font-family: Roboto;
src: url(../font/robotoregular.ttf);
font-weight: normal;
}
@font-face {
font-family: Roboto;
src: url(../font/robotobold.ttf);
font-weight: bold;
}
* {
text-align: center;
font-family: Roboto;
}
#content {
overflow: hidden;
height: 100vh;
width: 100vw;
border: 3px rgba(0, 0, 0, 0) solid;
}
h1 {
margin-top: 8rem;
font-size: 36pt;
}
p {
font-size: 24pt;
}
#hint_title {
margin-top: 10rem;
font-weight: bold;
}
img {
margin-top: 5rem;
height: 300px;
}
/* Background stripes */
#bg img {
position: fixed;
bottom: 10px;
left: 0;
width: 100vw;
height: auto;
}
</style>
<div id="content">
<h1>OffPass</h1>
<p>Created on $DATE</p>
<p id="hint_title">Password hint</p>
<p>$HINT</p>
<img src="data:image/png;base64, $QRCODE">
<div id="bg">
<img src="print_background.png">
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
<resources></resources>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
Because the window insets on round devices are larger than 15dp, this padding only applies
to square screens.
-->
<!--
This padding applies to both square and round screens. The total padding between the buttons
and the window insets is box_inset_layout_padding (above variable) on square screens and
inner_frame_layout_padding (below variable) on round screens.
-->
</resources>

View File

@@ -1,3 +1,10 @@
<resources> <resources>
<string name="app_name">OffPass</string> <string name="app_name">OffPass</string>
<!--
This string is used for square devices and overridden by hello_world in
values-round/strings.xml for round devices.
-->
<string name="text_input_head_undefined">HEAD UNDEFINED</string>
<string name="text_input_title_undefined">TITLE UNDEFINED</string>
</resources> </resources>

View File

@@ -1,10 +1,29 @@
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">#fff</item>
<item name="android:statusBarColor">@color/colorPrimaryDark</item>
</style> </style>
<style name="PassphraseDialog" parent="@android:style/Theme.Material.Dialog">
<item name="android:textColor">@color/colorPrimary</item>
<item name="android:textStyle">bold</item>
<item name="android:backgroundDimAmount">0.9</item>
<item name="android:background">@color/colorPrimaryDark</item>
</style>
<style name="ErrorDialog" parent="@android:style/Theme.Material.Dialog">
<item name="android:textColor">@color/colorPrimary</item>
<item name="android:textStyle">bold</item>
<item name="android:background">@android:color/holo_red_light</item>
</style>
<style name="PassphraseGenerator" parent="@android:style/Theme.Material.Dialog">
<item name="android:paddingTop">0dp</item>
<item name="android:layout_marginTop">0dp</item>
<item name="android:textColor">@color/colorPrimaryDark</item>
<item name="background">@color/colorPrimary</item>
<item name="android:textStyle">bold</item>
</style>
</resources> </resources>

View File

@@ -2,3 +2,4 @@ Credits
=================================== ===================================
Icons made by photo3idea_studio (https://www.flaticon.com/de/autoren/photo3idea-studio) from www.flaticon.com Icons made by photo3idea_studio (https://www.flaticon.com/de/autoren/photo3idea-studio) from www.flaticon.com
Icons made by xnimrodx (https://www.flaticon.com/de/autoren/xnimrodx) from www.flaticon.com Icons made by xnimrodx (https://www.flaticon.com/de/autoren/xnimrodx) from www.flaticon.com
Icons made by Michael Irigoyen (http://twitter.com/mririgo)