diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 858d190..54bf586 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,11 +12,13 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> + + + android:windowSoftInputMode="adjustPan" /> diff --git a/app/src/main/res/drawable/dummy_qr_code.png b/app/src/main/assets/dummy_qr_code.png similarity index 100% rename from app/src/main/res/drawable/dummy_qr_code.png rename to app/src/main/assets/dummy_qr_code.png diff --git a/app/src/main/assets/print_background.png b/app/src/main/assets/print_background.png new file mode 100644 index 0000000..53625c2 Binary files /dev/null and b/app/src/main/assets/print_background.png differ diff --git a/app/src/main/res/font/robotobold.ttf b/app/src/main/assets/robotobold.ttf similarity index 100% rename from app/src/main/res/font/robotobold.ttf rename to app/src/main/assets/robotobold.ttf diff --git a/app/src/main/res/font/robotoregular.ttf b/app/src/main/assets/robotoregular.ttf similarity index 100% rename from app/src/main/res/font/robotoregular.ttf rename to app/src/main/assets/robotoregular.ttf diff --git a/app/src/main/res/font/robotothin.ttf b/app/src/main/assets/robotothin.ttf similarity index 100% rename from app/src/main/res/font/robotothin.ttf rename to app/src/main/assets/robotothin.ttf diff --git a/app/src/main/java/com/github/mondei1/offpass/CreateActivity.kt b/app/src/main/java/com/github/mondei1/offpass/CreateActivity.kt index bbd4c8d..7064c9f 100644 --- a/app/src/main/java/com/github/mondei1/offpass/CreateActivity.kt +++ b/app/src/main/java/com/github/mondei1/offpass/CreateActivity.kt @@ -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 diff --git a/app/src/main/java/com/github/mondei1/offpass/CryptoOperations.kt b/app/src/main/java/com/github/mondei1/offpass/CryptoOperations.kt index 628e85f..d22bad6 100644 --- a/app/src/main/java/com/github/mondei1/offpass/CryptoOperations.kt +++ b/app/src/main/java/com/github/mondei1/offpass/CryptoOperations.kt @@ -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) diff --git a/app/src/main/java/com/github/mondei1/offpass/MainActivity.kt b/app/src/main/java/com/github/mondei1/offpass/MainActivity.kt index 0b615ef..ddb733c 100644 --- a/app/src/main/java/com/github/mondei1/offpass/MainActivity.kt +++ b/app/src/main/java/com/github/mondei1/offpass/MainActivity.kt @@ -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() } } diff --git a/app/src/main/java/com/github/mondei1/offpass/PassphraseActivity.kt b/app/src/main/java/com/github/mondei1/offpass/PassphraseActivity.kt new file mode 100644 index 0000000..4c8e093 --- /dev/null +++ b/app/src/main/java/com/github/mondei1/offpass/PassphraseActivity.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/mondei1/offpass/QRSchema.kt b/app/src/main/java/com/github/mondei1/offpass/QRSchema.kt index 5391a06..3ed8ef4 100644 --- a/app/src/main/java/com/github/mondei1/offpass/QRSchema.kt +++ b/app/src/main/java/com/github/mondei1/offpass/QRSchema.kt @@ -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 { diff --git a/app/src/main/java/com/github/mondei1/offpass/ViewActivity.kt b/app/src/main/java/com/github/mondei1/offpass/ViewActivity.kt new file mode 100644 index 0000000..ec3c1f6 --- /dev/null +++ b/app/src/main/java/com/github/mondei1/offpass/ViewActivity.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_create.xml b/app/src/main/res/layout/activity_create.xml index 1183075..be6d949 100644 --- a/app/src/main/res/layout/activity_create.xml +++ b/app/src/main/res/layout/activity_create.xml @@ -14,19 +14,19 @@ android:layout_height="wrap_content" android:background="@color/colorPrimaryDark" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:title="Create"> + app:layout_constraintTop_toTopOf="parent"> + + + + +