Skip to content

Commit

Permalink
release v1.2
Browse files Browse the repository at this point in the history
  • Loading branch information
Owais Shaikh committed May 19, 2021
1 parent e136951 commit 4baa091
Show file tree
Hide file tree
Showing 6 changed files with 629 additions and 2 deletions.
4 changes: 2 additions & 2 deletions app/src/main/java/com/wristkey/AddActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ class AddActivity : WearableActivity() {
@RequiresApi(Build.VERSION_CODES.N)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add)
setContentView(R.layout.activity_manual_entry)
val boxinsetlayout = findViewById<BoxInsetLayout>(R.id.BoxInsetLayout)
val addAccountLabel = findViewById<TextView>(R.id.AddAccountLabel)
val addAccountLabel = findViewById<TextView>(R.id.ManualEntryLabel)
val confirmButton = findViewById<ImageButton>(R.id.AuthenticatorConfirmButton)
val cancelButton = findViewById<ImageButton>(R.id.CancelButton)
val account = findViewById<EditText>(R.id.AccountField)
Expand Down
222 changes: 222 additions & 0 deletions app/src/main/java/com/wristkey/ManualEntryActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package com.wristkey
import android.content.Context
import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.support.wearable.activity.WearableActivity
import android.text.method.PasswordTransformationMethod
import android.view.View
import android.widget.*
import androidx.annotation.RequiresApi
import androidx.wear.widget.BoxInsetLayout
import com.google.gson.Gson
import java.util.*


class AddActivity : WearableActivity() {
@RequiresApi(Build.VERSION_CODES.N)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add)
val boxinsetlayout = findViewById<BoxInsetLayout>(R.id.BoxInsetLayout)
val addAccountLabel = findViewById<TextView>(R.id.AddAccountLabel)
val confirmButton = findViewById<ImageButton>(R.id.AuthenticatorConfirmButton)
val cancelButton = findViewById<ImageButton>(R.id.CancelButton)
val account = findViewById<EditText>(R.id.AccountField)
val sharedSecret = findViewById<EditText>(R.id.SharedSecretField)
sharedSecret.transformationMethod = PasswordTransformationMethod.getInstance()
val modeGroup = findViewById<RadioGroup>(R.id.GeneratorMode)
val timeMode = findViewById<RadioButton>(R.id.TimeMode)
val counterMode = findViewById<RadioButton>(R.id.CounterMode)
modeGroup.check(R.id.TimeMode)
var mode = "Time"
val digitLength = findViewById<SeekBar>(R.id.DigitLengthSeekbar)
digitLength.progress = 1
var selectedDigitLength = "6"
val digitLengthLabel = findViewById<TextView>(R.id.DigitLength)
val algorithm = findViewById<SeekBar>(R.id.AlgorithmKeylengthSeekbar)
algorithm.progress = 0
var selectedAlgorithm = "HmacAlgorithm.SHA1"
val algorithmLabel = findViewById<TextView>(R.id.AlgorithmLength)
val appData: SharedPreferences = applicationContext.getSharedPreferences(
appDataFile,
Context.MODE_PRIVATE
)
var currentAccent = appData.getString("accent", "4285F4")
var currentTheme = appData.getString("theme", "000000")
boxinsetlayout.setBackgroundColor(Color.parseColor("#" + currentTheme))
account.backgroundTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
account.foregroundTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
account.compoundDrawableTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
sharedSecret.backgroundTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
sharedSecret.foregroundTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
sharedSecret.compoundDrawableTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
timeMode.buttonTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
counterMode.buttonTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
digitLength.backgroundTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
digitLength.foregroundTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
digitLength.progressTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
digitLength.progressBackgroundTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
digitLength.secondaryProgressTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
digitLength.indeterminateTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
digitLength.thumbTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
digitLength.tickMarkTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
algorithm.backgroundTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
algorithm.foregroundTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
algorithm.progressTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
algorithm.progressBackgroundTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
algorithm.secondaryProgressTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
algorithm.indeterminateTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
algorithm.thumbTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
algorithm.tickMarkTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
confirmButton.backgroundTintList = ColorStateList.valueOf(Color.parseColor("#" + currentAccent))
if (currentTheme == "F7F7F7") {
addAccountLabel.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
account.setHintTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
sharedSecret.setHintTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
timeMode.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
counterMode.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
digitLengthLabel.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
algorithmLabel.setTextColor(ColorStateList.valueOf(Color.parseColor("#000000")))
} else {
addAccountLabel.setTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
account.setHintTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
sharedSecret.setHintTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
timeMode.setTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
counterMode.setTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
digitLengthLabel.setTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
algorithmLabel.setTextColor(ColorStateList.valueOf(Color.parseColor("#FFFFFF")))
}
confirmButton.setOnClickListener {
val errorToast: Toast?
val tokenData = ArrayList<String>()
if (account.text.toString() == ""){
errorToast = Toast.makeText(this, "Enter account name", Toast.LENGTH_SHORT)
errorToast.show()
}else if (sharedSecret.text.toString() == ""){
errorToast = Toast.makeText(this, "Enter shared secret", Toast.LENGTH_SHORT)
errorToast.show()
}else if((sharedSecret.text.toString()).length < 8 && selectedDigitLength == "6" && selectedAlgorithm == "HmacAlgorithm.SHA1" && mode == "Time"){
errorToast = Toast.makeText(this, "Invalid shared secret", Toast.LENGTH_SHORT)
errorToast.show()
}else{
tokenData.add(account.text.toString())
tokenData.add(sharedSecret.text.toString())
tokenData.add(mode)
tokenData.add(selectedDigitLength)
tokenData.add(selectedAlgorithm)
tokenData.add("0") // If counter mode is selected, initial value must be 0.
val json = Gson().toJson(tokenData)

val id = UUID.randomUUID().toString()

logins.edit().putString(id, json).apply()
val addedToast = Toast.makeText(this, "Added account", Toast.LENGTH_SHORT)
addedToast.show()
finish()
}
}
cancelButton.setOnClickListener {
finish()
}
modeGroup.setOnCheckedChangeListener { _, checkedId ->
mode = if (checkedId != -1) {
(findViewById<View>(checkedId) as RadioButton).text.toString()
} else {
""
}
}
digitLength.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) {
if (digitLength.progress == 0) {
digitLengthLabel.text = "4 digits"
selectedDigitLength = "4"
} else if (digitLength.progress == 1) {
selectedDigitLength = "6"
digitLengthLabel.text = "6 digits"
} else if (digitLength.progress == 2) {
selectedDigitLength = "7"
digitLengthLabel.text = "7 digits"
} else if (digitLength.progress == 3) {
selectedDigitLength = "8"
digitLengthLabel.text = "8 digits"
}
}

override fun onStartTrackingTouch(seekBar: SeekBar?) {
if (digitLength.progress == 0) {
digitLengthLabel.text = "4 digits"
selectedDigitLength = "4"
} else if (digitLength.progress == 1) {
selectedDigitLength = "6"
digitLengthLabel.text = "6 digits"
} else if (digitLength.progress == 2) {
selectedDigitLength = "7"
digitLengthLabel.text = "7 digits"
} else if (digitLength.progress == 3) {
selectedDigitLength = "8"
digitLengthLabel.text = "8 digits"
}
}

override fun onStopTrackingTouch(seekBar: SeekBar?) {
if (digitLength.progress == 0) {
digitLengthLabel.text = "4 digits"
selectedDigitLength = "4"
} else if (digitLength.progress == 1) {
selectedDigitLength = "6"
digitLengthLabel.text = "6 digits"
} else if (digitLength.progress == 2) {
selectedDigitLength = "7"
digitLengthLabel.text = "7 digits"
} else if (digitLength.progress == 3) {
selectedDigitLength = "8"
digitLengthLabel.text = "8 digits"
}
}
})

algorithm.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) {
if (algorithm.progress == 0) {
algorithmLabel.text = "SHA-1"
selectedAlgorithm = "HmacAlgorithm.SHA1"
} else if (algorithm.progress == 1) {
algorithmLabel.text = "SHA-256"
selectedAlgorithm = "HmacAlgorithm.SHA256"
} else if (algorithm.progress == 2) {
algorithmLabel.text = "SHA-512"
selectedAlgorithm = "HmacAlgorithm.SHA512"
}
}

override fun onStartTrackingTouch(seekBar: SeekBar?) {
if (algorithm.progress == 0) {
algorithmLabel.text = "SHA-1"
selectedAlgorithm = "HmacAlgorithm.SHA1"
} else if (algorithm.progress == 1) {
algorithmLabel.text = "SHA-256"
selectedAlgorithm = "HmacAlgorithm.SHA256"
} else if (algorithm.progress == 2) {
algorithmLabel.text = "SHA-512"
selectedAlgorithm = "HmacAlgorithm.SHA512"
}
}

override fun onStopTrackingTouch(seekBar: SeekBar?) {
if (algorithm.progress == 0) {
algorithmLabel.text = "SHA-1"
selectedAlgorithm = "HmacAlgorithm.SHA1"
} else if (algorithm.progress == 1) {
algorithmLabel.text = "SHA-256"
selectedAlgorithm = "HmacAlgorithm.SHA256"
} else if (algorithm.progress == 2) {
algorithmLabel.text = "SHA-512"
selectedAlgorithm = "HmacAlgorithm.SHA512"
}
}
})
}
}
122 changes: 122 additions & 0 deletions app/src/main/python/extract_otp_secret_keys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Extract two-factor authentication (2FA, TFA) secret keys from export QR codes of "Google Authenticator" app
#
# Usage:
# 1. Export the QR codes from "Google Authenticator" app
# 2. Read QR codes with QR code reader (e.g. with a second device)
# 3. Save the captured QR codes in a text file. Save each QR code on a new line. (The captured QR codes look like "otpauth-migration://offline?data=...")
# 4. Call this script with the file as input:
# python extract_otp_secret_keys.py -p example_export.txt
#
# Requirement:
# The protobuf package of Google for proto3 is required for running this script.
# pip install protobuf
#
# Optional:
# For printing QR codes, the qrcode module is required
# pip install qrcode
#
# Technical background:
# The export QR code of "Google Authenticator" contains the URL "otpauth-migration://offline?data=...".
# The data parameter is a base64 encoded proto3 message (Google Protocol Buffers).
#
# Command for regeneration of Python code from proto3 message definition file (only necessary in case of changes of the proto3 message definition):
# protoc --python_out=generated_python google_auth.proto
#
# References:
# Proto3 documentation: https://developers.google.com/protocol-buffers/docs/pythontutorial
# Template code: https://github.com/beemdevelopment/Aegis/pull/406

# Author: Scito (https://scito.ch)

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import argparse
import base64
import fileinput
import sys
from urllib.parse import parse_qs, urlencode, urlparse, quote
from os import path, mkdir
from re import sub, compile as rcompile
import generated_python.google_auth_pb2

arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('--verbose', '-v', help='verbose output', action='store_true')
arg_parser.add_argument('--saveqr', '-s', help='save QR code(s) as images to the "qr" subfolder', action='store_true')
arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the terminal', action='store_true')
arg_parser.add_argument('infile', help='file or - for stdin (default: -) with "otpauth-migration://..." URLs separated by newlines, lines starting with # are ignored')
args = arg_parser.parse_args()

if args.saveqr or args.printqr: from qrcode import QRCode
verbose = args.verbose

# https://stackoverflow.com/questions/40226049/find-enums-listed-in-python-descriptor-for-protobuf
def get_enum_name_by_number(parent, field_name):
field_value = getattr(parent, field_name)
return parent.DESCRIPTOR.fields_by_name[field_name].enum_type.values_by_number.get(field_value).name

def convert_secret_from_bytes_to_base32_str(bytes):
return str(base64.b32encode(otp.secret), 'utf-8').replace('=', '')

def save_qr(data, name):
qr = QRCode()
qr.add_data(data)
img = qr.make_image(fill_color='black', back_color='white')
if verbose: print('Saving to {}'.format(name))
img.save(name)

def print_qr(data):
qr = QRCode()
qr.add_data(data)
qr.print_tty()

i = j = 0
for line in (line.strip() for line in fileinput.input(args.infile)):
if verbose: print(line)
if line.startswith('#') or line == '': continue
if not line.startswith('otpauth-migration://'): print('\nWARN: line is not a otpauth-migration:// URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format(args.infile, line))
parsed_url = urlparse(line)
params = parse_qs(parsed_url.query)
if not 'data' in params:
print('\nERROR: no data query parameter in input URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format(args.infile, line))
sys.exit(1)
data_encoded = params['data'][0]
data = base64.b64decode(data_encoded)
payload = generated_python.google_auth_pb2.MigrationPayload()
payload.ParseFromString(data)
i += 1
if verbose: print('\n{}. Payload Line'.format(i), payload, sep='\n')

# pylint: disable=no-member
for otp in payload.otp_parameters:
j += 1
if verbose: print('\n{}. Secret Key'.format(j))
else: print()
print('Name: {}'.format(otp.name))
secret = convert_secret_from_bytes_to_base32_str(otp.secret)
print('Secret: {}'.format(secret))
if otp.issuer: print('Issuer: {}'.format(otp.issuer))
print('Type: {}'.format(get_enum_name_by_number(otp, 'type')))
url_params = { 'secret': secret }
if otp.type == 1: url_params['counter'] = otp.counter
if otp.issuer: url_params['issuer'] = otp.issuer
otp_url = 'otpauth://{}/{}?'.format('totp' if otp.type == 2 else 'hotp', quote(otp.name)) + urlencode(url_params)
if verbose: print(otp_url)
if args.printqr:
print_qr(otp_url)
if args.saveqr:
if not(path.exists('qr')): mkdir('qr')
pattern = rcompile(r'[\W_]+')
file_otp_name = pattern.sub('', otp.name)
file_otp_issuer = pattern.sub('', otp.issuer)
save_qr(otp_url, 'qr/{}-{}{}.png'.format(j, file_otp_name, '-' + file_otp_issuer if file_otp_issuer else ''))
File renamed without changes.
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/ic_baseline_edit_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>
Loading

0 comments on commit 4baa091

Please sign in to comment.