Skip to content

Commit

Permalink
released version 1.2.1
Browse files Browse the repository at this point in the history
  • Loading branch information
Owais Shaikh committed May 20, 2021
1 parent 61357d4 commit 56b536c
Show file tree
Hide file tree
Showing 14 changed files with 100 additions and 34 deletions.
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,15 @@ If your watch is paired to an Android phone, you can use a third-party Wear OS f
2. Select the accounts you\'d like to export and tap the export button 'Export'.
3. Take a screenshot of the QR code that is displayed.
3. Take a picture or screenshot of the QR code that is displayed. **Make sure it is a PNG or JPG file** and that it is clear with no blurring, glare or pixelation.
4. place this screenshot on the main folder of your watch (/sdcard/) via ADB.
5. Open a terminal on your computer and place this screenshot PNG file on the main directory of your watch (/sdcard/) via the following command
4. Open a terminal on your computer and place this PNG or JPG file on the main directory of your watch (/sdcard/) via the following command
```adb push <screenshot filename>.png /sdcard/```
6. On your watch, open Wristkey, tap the settings icon '⚙️', then scroll down and tap *Import from Authenticator*.
4. After your accounts are imported, delete the PNG file from your watch via the following commands
4. After your accounts are imported, delete the PNG or JPG file from your watch via the following commands
```
adb shell
Expand Down Expand Up @@ -139,13 +137,13 @@ If the wrong codes are being shown, your watch may have the time set incorrectly
### File import not working
Make sure Wristkey has storage permissions in your watch's Settings app. Make sure the file you download from your Bitwarden account is an **Unencrypted** file in **JSON** format (Encrypted JSON and Encrypted / Unencrypted CSV files don't work). If importing from Authenticator, make sure the screenshot is in **PNG** format.
Make sure Wristkey has storage permissions in your watch's Settings app. If importing from JSON, make sure the file you download from your Bitwarden account is an **Unencrypted** file in **JSON** format (Encrypted JSON and Encrypted / Unencrypted CSV files don't work). If importing from Authenticator, make sure the screenshot or picture is in **PNG or JPG** format and is clear.
## Security
### Importing files
To prevent data extraction, snooping and theft, make sure you delete the JSON and PNG files from your watch's storage once you're done importing them. You can confirm this by connecting your watch via ADB and running the ```adb shell ls /sdcard/``` command.
To prevent data extraction, snooping and theft, make sure you delete the JSON, PNG or JPG files from your watch's storage once you're done importing them. You can confirm this by connecting your watch via ADB and running the ```adb shell ls /sdcard/``` command.
### In-app storage
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ android {
minSdkVersion 23
targetSdkVersion 26 // Play Store requires API29+
versionCode 1
versionName "1.2"
versionName "1.2.1"

ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
Expand Down Expand Up @@ -58,7 +58,7 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'com.google.android.support:wearable:2.8.1'
implementation 'com.google.android.gms:play-services-wearable:17.1.0'
implementation 'androidx.percentlayout:percentlayout:1.0.0'
Expand Down
Binary file modified app/build/outputs/apk/debug/app-debug.apk
Binary file not shown.
2 changes: 1 addition & 1 deletion app/build/outputs/apk/debug/output-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"type": "SINGLE",
"filters": [],
"versionCode": 1,
"versionName": "1.2",
"versionName": "1.2.1",
"outputFile": "app-debug.apk"
}
]
Expand Down
Binary file modified app/release/app-release.apk
Binary file not shown.
2 changes: 1 addition & 1 deletion app/release/output-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"type": "SINGLE",
"filters": [],
"versionCode": 1,
"versionName": "1.2",
"versionName": "1.2.1",
"outputFile": "app-release.apk"
}
]
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/wristkey/AddActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class AddActivity : WearableActivity() {
backButton.setOnClickListener {
val vibratorService = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
vibratorService.vibrate(50)
val intent = Intent(applicationContext, MainActivity::class.java)
startActivity(intent)
finish()
}

Expand Down
31 changes: 20 additions & 11 deletions app/src/main/java/com/wristkey/AuthenticatorQRImport.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.wristkey.databinding.ActivityAuthenticatorQrimportBinding
import org.json.JSONArray
import org.json.JSONObject
import java.io.*
import java.text.SimpleDateFormat
import java.util.*

class AuthenticatorQRImport : Activity() {
Expand Down Expand Up @@ -78,13 +79,11 @@ class AuthenticatorQRImport : Activity() {
try {
val files: Array<File> = getExternalStorageDirectory().listFiles()
for (file in files) {
if (file.name.endsWith(".png", ignoreCase = true)) {
if (file.name.endsWith(".png", ignoreCase = true) || file.name.endsWith(".jpg", ignoreCase = true) || file.name.endsWith(".jpeg", ignoreCase = true)) {
val reader: InputStream = BufferedInputStream(FileInputStream(file.path))
val imageBitmap = BitmapFactory.decodeStream(reader)
val decodedQRCodeData: String = scanQRImage(imageBitmap)

Log.d("data>>", decodedQRCodeData)

if (decodedQRCodeData.contains("otpauth-migration://")) {
setContentView(R.layout.import_loading_screen)
val loadingLayout = findViewById<BoxInsetLayout>(R.id.LoadingLayout)
Expand Down Expand Up @@ -138,8 +137,8 @@ class AuthenticatorQRImport : Activity() {
Python.start(AndroidPlatform(this))
}

Python.getInstance().getModule("extract_otp_secret_keys")
.callAttr("decode", decodedQRCodeData)
Python.getInstance().getModule("extract_otp_secret_keys").callAttr("decode", decodedQRCodeData)
val timeStamp: String = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date())

var logcat: Process
val log = StringBuilder()
Expand All @@ -159,21 +158,25 @@ class AuthenticatorQRImport : Activity() {
}

val logExtractedString = log.toString()
.substringAfter(timeStamp) // get most recent occurence of data
.substringAfter("python.stdout")
.substringAfter("<\$beginwristkeygoogleauthenticatorimport\$>")
.substringBefore("<\$endwristkeygoogleauthenticatorimport\$>")

// convert json data and store in sharedprefs

val items = JSONObject(logExtractedString)
for (key in items.keys()) {
val tokenData = ArrayList<String>()
tokenData.add(key)
if (!importUsernames.isChecked) {
tokenData.add(key.toString().replaceAfter("(", "").replace("(", "")) // name without username
} else {
tokenData.add(key) // name with username
}
val itemData = JSONObject(items[key].toString())
tokenData.add(itemData["secret"].toString())
if (itemData["type"] == "2") tokenData.add("Time") else tokenData.add("Counter")
tokenData.add("6")
tokenData.add("HmacAlgorithm.SHA1")
tokenData.add(itemData["secret"].toString()) //secret
if (itemData["type"] == "2") tokenData.add("Time") else tokenData.add("Counter") // mode
tokenData.add("6") // length
tokenData.add("HmacAlgorithm.SHA1") // algorithm
tokenData.add("0") // If counter mode is selected, initial value must be 0.

val id = UUID.randomUUID().toString()
Expand All @@ -191,6 +194,9 @@ class AuthenticatorQRImport : Activity() {
val vibratorService =
getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
vibratorService.vibrate(50)

val intent = Intent(applicationContext, MainActivity::class.java)
startActivity(intent)
finish()
} else {
Toast.makeText(
Expand All @@ -202,6 +208,9 @@ class AuthenticatorQRImport : Activity() {
val vibratorService =
getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
vibratorService.vibrate(50)

val intent = Intent(applicationContext, MainActivity::class.java)
startActivity(intent)
finish()
}
}
Expand Down
17 changes: 17 additions & 0 deletions app/src/main/java/com/wristkey/DeleteActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.wristkey

import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.graphics.Color
Expand Down Expand Up @@ -38,6 +40,14 @@ class DeleteActivity : WearableActivity() {
confirmationText.setTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
}

if (appData.getBoolean("screen_lock", true)) {
val lockscreen = getSystemService(KEYGUARD_SERVICE) as KeyguardManager
if (lockscreen.isKeyguardSecure) {
val i = lockscreen.createConfirmDeviceCredentialIntent("Wristkey", "App locked")
startActivityForResult(i, CODE_AUTHENTICATION_VERIFICATION)
}
}

confirmButton.setOnClickListener {
logins.edit().remove(idForDeleteActivity).apply()
finish()
Expand All @@ -50,4 +60,11 @@ class DeleteActivity : WearableActivity() {
finish()
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (!(resultCode == RESULT_OK && requestCode == CODE_AUTHENTICATION_VERIFICATION)) {
finish()
}
}
}
23 changes: 13 additions & 10 deletions app/src/main/java/com/wristkey/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,6 @@ class MainActivity : WearableActivity() {
}
}

masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
logins = EncryptedSharedPreferences.create(
loginsFile,
masterKeyAlias,
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

val appData: SharedPreferences = applicationContext.getSharedPreferences(
appDataFile,
Context.MODE_PRIVATE
Expand All @@ -73,6 +64,15 @@ class MainActivity : WearableActivity() {
}
}

masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
logins = EncryptedSharedPreferences.create(
loginsFile,
masterKeyAlias,
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

Expand Down Expand Up @@ -318,13 +318,16 @@ class MainActivity : WearableActivity() {
startActivity(intent)
val vibratorService = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
vibratorService.vibrate(100)
finish()
}

aboutButton.setOnClickListener {
val intent = Intent(applicationContext, AboutActivity::class.java)
startActivity(intent)
val vibratorService = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
vibratorService.vibrate(50)
}

settingsButton.setOnClickListener {
val intent = Intent(applicationContext, SettingsActivity::class.java)
startActivity(intent)
Expand All @@ -343,7 +346,7 @@ class MainActivity : WearableActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (!(resultCode == RESULT_OK && requestCode == CODE_AUTHENTICATION_VERIFICATION)) {
Toast.makeText(this, "Access denied", Toast.LENGTH_SHORT).show()
finish()
}
}

Expand Down
24 changes: 24 additions & 0 deletions app/src/main/java/com/wristkey/QRCodeActivity.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.wristkey

import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.Point
Expand All @@ -20,6 +24,19 @@ class QRCodeActivity : WearableActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_qrcode)

val appData: SharedPreferences = applicationContext.getSharedPreferences(
appDataFile,
Context.MODE_PRIVATE
)

if (appData.getBoolean("screen_lock", true)) {
val lockscreen = getSystemService(KEYGUARD_SERVICE) as KeyguardManager
if (lockscreen.isKeyguardSecure) {
val i = lockscreen.createConfirmDeviceCredentialIntent("Wristkey", "App locked")
startActivityForResult(i, CODE_AUTHENTICATION_VERIFICATION)
}
}

val manager = getSystemService(WINDOW_SERVICE) as WindowManager
val display = manager.defaultDisplay
val point = Point()
Expand All @@ -44,4 +61,11 @@ class QRCodeActivity : WearableActivity() {
}
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (!(resultCode == RESULT_OK && requestCode == CODE_AUTHENTICATION_VERIFICATION)) {
finish()
}
}
}
13 changes: 13 additions & 0 deletions app/src/main/java/com/wristkey/SettingsActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,32 @@ class SettingsActivity : WearableActivity() {
boxinsetlayout.setBackgroundColor(Color.parseColor("#"+currentTheme))
backButton.backgroundTintList = ColorStateList.valueOf(Color.parseColor("#"+currentAccent))
deleteButton.backgroundTintList = ColorStateList.valueOf(Color.parseColor("#"+currentAccent))
ambientMode.buttonTintList = ColorStateList.valueOf(Color.parseColor("#"+currentAccent))
screenLock.buttonTintList = ColorStateList.valueOf(Color.parseColor("#"+currentAccent))
beep.buttonTintList = ColorStateList.valueOf(Color.parseColor("#"+currentAccent))
vibrate.buttonTintList = ColorStateList.valueOf(Color.parseColor("#"+currentAccent))

beep.isChecked = appData.getBoolean("beep", false)
vibrate.isChecked = appData.getBoolean("vibrate", false)
ambientMode.isChecked = appData.getBoolean("ambient_mode", false)
screenLock.isChecked = appData.getBoolean("screen_lock", true)

if (currentTheme == "F7F7F7") {
settingsLabelText.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
notifyLabelText.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
beep.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
vibrate.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
ambientMode.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
screenLock.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
themeLabelText.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
accentLabelText.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
deleteButtonText.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
} else {
settingsLabelText.setTextColor(ColorStateList.valueOf(Color.parseColor("#BDBDBD")))
beep.setTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
vibrate.setTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
ambientMode.setTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
screenLock.setTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
themeLabelText.setTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
accentLabelText.setTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
deleteButtonText.setTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
Expand Down Expand Up @@ -136,6 +147,8 @@ class SettingsActivity : WearableActivity() {
val lockscreen = getSystemService(KEYGUARD_SERVICE) as KeyguardManager
if (!lockscreen.isKeyguardSecure) {
screenLock.visibility = View.GONE
} else {
screenLock.visibility = View.VISIBLE
}

ambientMode.setOnCheckedChangeListener { _, b ->
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/layout/activity_add.xml
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@
android:layout_marginRight="5dp"
android:foregroundGravity="center"
android:gravity="center|left"
android:text="Import from\nGoogle\nAuthenticator\n(Beta)"
android:text="Import from\nGoogle\nAuthenticator"
android:textColor="#FFFFFF"
android:textSize="15sp" />
</LinearLayout>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
-->
<string name="about_app">Wristkey is an open-source standalone two-factor authentication client for Wear OS.</string>
<string name="bitwarden_import_blurb">To import secrets from Bitwarden, make sure you have an unencrypted Bitwarden JSON file placed in the main directory of your watch (/sdcard/).\n\nAfter your accounts are imported, delete the JSON file.</string>
<string name="authenticator_import_blurb">To import secrets from Google Authenticator, make sure you have a PNG screenshot of the QR code placed on the main folder of your watch (/sdcard/).\n\nAfter your accounts are imported, delete the PNG file.</string>
<string name="authenticator_import_blurb">To import secrets from Google Authenticator, make sure you have a PNG or JPG image of the QR code placed on the main folder of your watch (/sdcard/).\n\nAfter your accounts are imported, delete the PNG file.</string>
<string name="about_url">https://gitlab.com/thomascat/wristkey</string>
<string name="hello_world">Hello Square World!</string>
<string name="title_activity_add">AddActivity</string>
Expand Down

0 comments on commit 56b536c

Please sign in to comment.