QR-Codes can now be displayed after scanning.
- 2FA also supported
This commit is contained in:
@@ -1,48 +1,97 @@
|
||||
package com.github.mondei1.offpass
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.ColorFilter
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.print.PrintAttributes
|
||||
import android.print.PrintJob
|
||||
import android.print.PrintManager
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
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 dev.turingcomplete.kotlinonetimepassword.GoogleAuthenticator
|
||||
import kotlinx.android.synthetic.main.activity_create.*
|
||||
import kotlinx.coroutines.*
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.*
|
||||
|
||||
class CreateActivity : AppCompatActivity() {
|
||||
private var fragment_title: TextInput? = null
|
||||
private var fragment_username: TextInput? = null
|
||||
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
|
||||
|
||||
fun doPrint() {
|
||||
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", "Not configurable yet!")
|
||||
.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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
//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 = 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")
|
||||
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))
|
||||
|
||||
@@ -51,6 +100,20 @@ class CreateActivity : AppCompatActivity() {
|
||||
Log.i("CREATE", "Back got clicked!")
|
||||
finish()
|
||||
}
|
||||
print_button.setOnClickListener {
|
||||
// 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()
|
||||
this.schema!!.build(arrayOf(), "123")
|
||||
|
||||
val barcodeEncoder: BarcodeEncoder = BarcodeEncoder()
|
||||
this.qrCodeBitmap = barcodeEncoder.encodeBitmap(this.schema!!.raw, BarcodeFormat.QR_CODE, 400, 400)
|
||||
doPrint()
|
||||
}
|
||||
|
||||
fa_input.isFocusable = false
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ class CryptoOperations {
|
||||
|
||||
// Performing actual crypto operation
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, IvParameterSpec(iv))
|
||||
val ciphered = cipher.doFinal(encrypted_raw)
|
||||
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)
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.github.mondei1.offpass
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.HandlerThread
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@@ -61,8 +60,11 @@ class MainActivity : AppCompatActivity() {
|
||||
if (result.contents == null) {
|
||||
Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show()
|
||||
} else {
|
||||
Log.i("SCANNER", "Scanned: " + result.contents)
|
||||
Toast.makeText(this, "Scanned: " + result.contents, Toast.LENGTH_LONG).show()
|
||||
// TODO: Check if scanned QR-Code is an OffPass one.
|
||||
val intent: Intent = Intent(this, PassphraseActivity::class.java)
|
||||
intent.putExtra("raw", result.contents)
|
||||
startActivity(intent)
|
||||
//Toast.makeText(this, "Scanned: " + result.contents, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.github.mondei1.offpass
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import kotlinx.android.synthetic.main.activity_passphrase.*
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
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 ..."
|
||||
runBlocking {
|
||||
qrSchema.decrypt(raw, passphrase_input.text.toString())
|
||||
}
|
||||
|
||||
val intent: Intent = Intent(this, ViewActivity::class.java)
|
||||
intent.putExtra("decrypted_raw",qrSchema.decrypted_raw)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class QRSchema {
|
||||
var decrypted_raw: String = ""
|
||||
|
||||
// Parsed content
|
||||
lateinit var session_key: String
|
||||
var session_key: String = ""
|
||||
lateinit var title: String
|
||||
lateinit var username: String
|
||||
lateinit var password: String
|
||||
@@ -100,23 +100,23 @@ class QRSchema {
|
||||
var key = fields[i].substring(1, closingBracket)
|
||||
var value = fields[i].substring(closingBracket+1, fields[i].length)
|
||||
|
||||
// Check if key and or value are compressed
|
||||
if (key.startsWith("$")) {
|
||||
runBlocking {
|
||||
key = resolve(key, context)
|
||||
}
|
||||
}
|
||||
if (value.startsWith("$")) {
|
||||
runBlocking {
|
||||
value = resolve(value, context)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
77
app/src/main/java/com/github/mondei1/offpass/ViewActivity.kt
Normal file
77
app/src/main/java/com/github/mondei1/offpass/ViewActivity.kt
Normal file
@@ -0,0 +1,77 @@
|
||||
package com.github.mondei1.offpass
|
||||
|
||||
import android.R.attr.key
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import dev.turingcomplete.kotlinonetimepassword.GoogleAuthenticator
|
||||
import kotlinx.android.synthetic.main.activity_view.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class ViewActivity : AppCompatActivity() {
|
||||
@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)
|
||||
}
|
||||
|
||||
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(qrSchema.password, 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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user