From 4e830055250cf567d8a3b071ac9db760c70473ae Mon Sep 17 00:00:00 2001 From: Stefan Brand Date: Thu, 14 Jul 2016 13:41:00 +0200 Subject: [PATCH] Added Token Length and Time Derivation Options --- qml/cover/CoverPage.qml | 10 +++++---- qml/harbour-sailotp.qml | 8 ++++++-- qml/lib/crypto.js | 14 +++++++++---- qml/lib/storage.js | 41 +++++++++++++++++++++++++------------ qml/pages/AddOTP.qml | 41 +++++++++++++++++++++++++++++++------ qml/pages/MainView.qml | 26 ++++++++--------------- qml/pages/QRPage.qml | 3 ++- qml/pages/ScanOTP.qml | 6 +++++- rpm/harbour-sailotp.changes | 4 ++++ rpm/harbour-sailotp.yaml | 2 +- 10 files changed, 105 insertions(+), 50 deletions(-) diff --git a/qml/cover/CoverPage.qml b/qml/cover/CoverPage.qml index 9937bc8..f0012ac 100644 --- a/qml/cover/CoverPage.qml +++ b/qml/cover/CoverPage.qml @@ -42,12 +42,14 @@ CoverBackground { // get seconds from current Date var curDate = new Date(); - if (lOTP.text == "------" || curDate.getSeconds() == 30 || curDate.getSeconds() == 0 || (curDate.getTime() - lastUpdated > 2000)) { - appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, appWin.coverType, 0); + var seconds = (curDate.getSeconds() + appWin.coverDiff) % 30 + + if (lOTP.text == "------" || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) { + appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, appWin.coverType, appWin.coverLen, appWin.coverDiff, 0); } // Change color of the OTP to red if less than 5 seconds left - if (29 - (curDate.getSeconds() % 30) < 5) { + if (29 - seconds < 5) { lOTP.color = "red" } else { lOTP.color = Theme.highlightColor @@ -101,7 +103,7 @@ CoverBackground { iconSource: appWin.coverType == "HOTP" ? "image://theme/icon-cover-refresh" : "image://theme/icon-cover-previous" onTriggered: { if (appWin.coverType == "HOTP") { - appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, "HOTP", DB.getCounter(appWin.coverTitle, appWin.coverSecret, true)); + appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, "HOTP", appWin.CoverLen, 0, DB.getCounter(appWin.coverTitle, appWin.coverSecret, true)); } else { var index = appWin.coverIndex - 1 if (index < 0) index = appWin.listModel.count - 1 diff --git a/qml/harbour-sailotp.qml b/qml/harbour-sailotp.qml index b7c76b5..1d08574 100644 --- a/qml/harbour-sailotp.qml +++ b/qml/harbour-sailotp.qml @@ -42,6 +42,8 @@ ApplicationWindow property string coverSecret: "" property string coverType: "" property string coverOTP: "------" + property int coverLen: 6 + property int coverDiff: 0 property int coverIndex: 0 // Global Listmodel for Tokens @@ -51,8 +53,8 @@ ApplicationWindow NotifyBanner { id: notify } // Add an entry to the list - function appendOTP(title, secret, type, counter, fav) { - listModel.append({"secret": secret, "title": title, "fav": fav, "type": type, "counter": counter, "otp": "------"}); + function appendOTP(title, secret, type, counter, fav, len, diff) { + listModel.append({"secret": secret, "title": title, "fav": fav, "type": type, "counter": counter, "len": len, "diff": diff, "otp": "------"}); } // Set the OTP shown on the Cover @@ -61,6 +63,8 @@ ApplicationWindow coverTitle = listModel.get(index).title; coverSecret = listModel.get(index).secret; coverType = listModel.get(index).type; + coverLen = listModel.get(index).len; + coverDiff = listModel.get(index).diff; coverIndex = index; if (coverType == "TOTP") { coverOTP = "------"; } else { coverOTP = listModel.get(index).otp; } for (var i=0; i 0) { - return(JSON.stringify({"app": "sailotp", "version": 2, "otplist": otpList})); + return(JSON.stringify({"app": "sailotp", "version": 3, "otplist": otpList})); } else { return("") } @@ -109,7 +122,7 @@ function json2db(jsonString, error) { var json = JSON.parse(jsonString); error = ""; - if ((json.version != "1" || json.version != "2") && json.app != "sailotp" ) { + if ((json.version != "1" || json.version != "2" || json.version != "3") && json.app != "sailotp" ) { error = "Unrecognized format, file is not a SailOTP export"; return(false); } else { @@ -120,9 +133,11 @@ function json2db(jsonString, error) { var otpItem = otpList.shift(); if (otpItem.title != "" & otpItem.secret.length >= 16) { if (json.version == "1") { - addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter, 0); + addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter, 0, 6, 0); + } else if (json.version == "2") { + addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter, otpItem.sort, 6, 0); } else { - addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter, otpItem.sort); + addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter, otpItem.sort, otpItem.len, otpItem.diff); } } } @@ -136,7 +151,7 @@ function json2db(jsonString, error) { } // Add a new OTP -function addOTP(title, secret, type, counter, sort) { +function addOTP(title, secret, type, counter, sort, len, diff) { var db = getDB(); db.transaction( @@ -144,7 +159,7 @@ function addOTP(title, secret, type, counter, sort) { if (checkOTP(title, secret)) { console.log("Token " + title + " is already in DB"); } else { - tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?, ?, ?, ?, ?);", [title, secret, type, counter, 0, sort]); + tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?, ?, ?, ?, ?, ?, ?);", [title, secret, type, counter, 0, sort, len, diff]); console.log("Token " + title + " added."); } }); @@ -195,12 +210,12 @@ function resetFav(title, secret) { } // Change an existing OTP -function changeOTP(title, secret, type, counter, oldtitle, oldsecret) { +function changeOTP(title, secret, type, counter, len, diff, oldtitle, oldsecret) { var db = getDB(); db.transaction( function(tx) { - tx.executeSql("UPDATE OTPStorage SET title=?, secret=?, type=?, counter=? WHERE title=? and secret=?;", [title, secret, type, counter, oldtitle, oldsecret]); + tx.executeSql("UPDATE OTPStorage SET title=?, secret=?, type=?, counter=?, len=?, diff=? WHERE title=? and secret=?;", [title, secret, type, counter, len, diff, oldtitle, oldsecret]); console.log("Token " + title + " modified."); } ); diff --git a/qml/pages/AddOTP.qml b/qml/pages/AddOTP.qml index fe79017..30aceb9 100644 --- a/qml/pages/AddOTP.qml +++ b/qml/pages/AddOTP.qml @@ -45,6 +45,8 @@ Dialog { property string paramType: "TOTP" property string paramLabel: "" property string paramKey: "" + property int paramLen: 6 + property int paramDiff: 0 property int paramCounter: 1 // New Counters start at 1 property bool paramNew: false @@ -70,7 +72,7 @@ Dialog { if (((paramType == "TOTP" || paramType == "TOTP_STEAM") && (otpLabel.text == "" || otpSecret.text == "")) || (paramType == "HOTP" && (otpLabel.text == "" || otpSecret.text == "" || otpCounter.text <= 0))) { notify.show(qsTr("Can't create QR-Code from incomplete settings!"), 4000); } else { - pageStack.push(Qt.resolvedUrl("QRPage.qml"), {paramLabel: otpLabel.text, paramKey: otpSecret.text, paramType: paramType, paramCounter: otpCounter.text}); + pageStack.push(Qt.resolvedUrl("QRPage.qml"), {paramLabel: otpLabel.text, paramKey: otpSecret.text, paramType: paramType, paramCounter: otpCounter.text, paramLen: otpLen.text}); } } } @@ -116,8 +118,35 @@ Dialog { horizontalAlignment: TextInput.AlignLeft EnterKey.enabled: text.length > 15 - EnterKey.iconSource: paramType == "HOTP" ? "image://theme/icon-m-enter-next" : "image://theme/icon-m-enter-accept" - EnterKey.onClicked: paramType == "HOTP" ? otpCounter.focus = true : addOTP.accept() + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.onClicked: otpLen.focus = true + } + TextField { + id: otpLen + width: parent.width + label: qsTr("Length") + text: paramLen + placeholderText: qsTr("Length of the Token") + focus: true + horizontalAlignment: TextInput.AlignLeft + validator: IntValidator { bottom: 1 } + + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.onClicked: paramType == "HOTP" ? otpCounter.focus = true : otpDiff.focus = true + } + TextField { + id: otpDiff + width: parent.width + visible: paramType == "TOTP" ? true : false + label: qsTr("Time Derivation (Seconds)") + text: paramDiff + placeholderText: qsTr("Time Derivation (Seconds)") + focus: true + horizontalAlignment: TextInput.AlignLeft + validator: IntValidator {} + + EnterKey.iconSource: "image://theme/icon-m-enter-accept" + EnterKey.onClicked: addOTP.accept() } TextField { id: otpCounter @@ -138,7 +167,7 @@ Dialog { } // Check if we can Save - canAccept: otpLabel.text.length > 0 && otpSecret.text.length >= 16 && (paramType == "TOTP" || paramType == "TOTP_STEAM" || otpCounter.text.length > 0) ? true : false + canAccept: otpLabel.text.length > 0 && otpSecret.text.length >= 16 && otpLen.text >= 1 && ((paramType == "TOTP" && otpDiff.text != "") || paramType == "TOTP_STEAM" || otpCounter.text.length > 0) ? true : false // Save if page is Left with Add onDone: { @@ -146,10 +175,10 @@ Dialog { // Save the entry to the Config DB if (paramLabel != "" && paramKey != "" && !paramNew) { // Parameters where filled -> Change existing entry - DB.changeOTP(otpLabel.text, otpSecret.text, paramType, otpCounter.text, paramLabel, paramKey) + DB.changeOTP(otpLabel.text, otpSecret.text, paramType, otpCounter.text, otpLen.text, otpDiff.text, paramLabel, paramKey) } else { // There were no parameters -> Add new entry - DB.addOTP(otpLabel.text, otpSecret.text, paramType, otpCounter.text, appWin.listModel.count); + DB.addOTP(otpLabel.text, otpSecret.text, paramType, otpCounter.text, appWin.listModel.count, otpLen.text, otpDiff.text); } // Refresh the main Page diff --git a/qml/pages/MainView.qml b/qml/pages/MainView.qml index eb6b865..948b5b9 100644 --- a/qml/pages/MainView.qml +++ b/qml/pages/MainView.qml @@ -41,18 +41,6 @@ Page { // This holds the time of the last update of the page as Unix Timestamp (in Milliseconds) property double lastUpdated: 0 - // Hand favorite over to the cover - function setCoverOTP(title, secret, type) { - appWin.coverTitle = title - appWin.coverSecret = secret - appWin.coverType = type - if (secret == "") { - appWin.coverOTP = ""; - } else if (type == "HOTP") { - appWin.coverOTP = "------"; - } - } - // Reload the List of OTPs from storage function refreshOTPList() { otpList.visible = false; @@ -68,14 +56,16 @@ Page { function refreshOTPValues() { // get seconds from current Date var curDate = new Date(); - var seconds = curDate.getSeconds(); + var seconds_global = curDate.getSeconds() % 30 // Iterate over all List entries for (var i=0; i 2000)) { - var curOTP = OTP.calcOTP(appWin.listModel.get(i).secret, appWin.listModel.get(i).type) + if (appWin.listModel.get(i).otp == "------" || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) { + var curOTP = OTP.calcOTP(appWin.listModel.get(i).secret, appWin.listModel.get(i).type, appWin.listModel.get(i).len, appWin.listModel.get(i).diff, 0) appWin.listModel.setProperty(i, "otp", curOTP); } } else if (appWin.coverType == "HOTP" && (curDate.getTime() - lastUpdated > 2000) && appWin.listModel.get(i).fav == 1) { @@ -85,7 +75,7 @@ Page { } // Update the Progressbar - updateProgress.value = 29 - (seconds % 30) + updateProgress.value = 29 - seconds_global // Set lastUpdate property lastUpdated = curDate.getTime(); } @@ -228,7 +218,7 @@ Page { visible: type == "HOTP" ? true : false onClicked: { appWin.listModel.setProperty(index, "counter", DB.getCounter(title, secret, true)); - appWin.listModel.setProperty(index, "otp", OTP.calcOTP(secret, "HOTP", counter)); + appWin.listModel.setProperty(index, "otp", OTP.calcOTP(secret, "HOTP", len, 0, counter)); if (fav == 1) appWin.coverOTP = otp; } } @@ -249,7 +239,7 @@ Page { MenuItem { text: qsTr("Edit") onClicked: { - pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage, paramLabel: title, paramKey: secret, paramType: type, paramCounter: DB.getCounter(title, secret, false)}) + pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage, paramLabel: title, paramKey: secret, paramType: type, paramLen: len, paramDiff: diff, paramCounter: DB.getCounter(title, secret, false)}) } } MenuItem { diff --git a/qml/pages/QRPage.qml b/qml/pages/QRPage.qml index 719eb82..6552e23 100644 --- a/qml/pages/QRPage.qml +++ b/qml/pages/QRPage.qml @@ -38,6 +38,7 @@ Page { property string paramType: "" property string paramLabel: "" property string paramKey: "" + property int paramLen: 6 property int paramCounter: 0 Label { @@ -65,7 +66,7 @@ Page { } else if (paramType == "HOTP") { if (paramLabel != "" && paramKey != "" && paramCounter > 0) - otpurl = "otpauth://hotp/"+paramLabel+"?secret="+paramKey+"&counter="+paramCounter; + otpurl = "otpauth://hotp/"+paramLabel+"?secret="+paramKey+"&counter="+paramCounter+"&digits="+paramLen; } if (otpurl != "") { qrImage.source = "image://qqrencoder/"+otpurl; diff --git a/qml/pages/ScanOTP.qml b/qml/pages/ScanOTP.qml index 66b5577..81b5cc2 100644 --- a/qml/pages/ScanOTP.qml +++ b/qml/pages/ScanOTP.qml @@ -90,9 +90,13 @@ Page { onTagFound: { var ret = URL.decode(tag); + var len = 6 scanning = false if (ret && ret.type != "" && ret.title != "" && ret.secret != "" && (ret.counter != "" || ret.type == "TOTP")) { - pageStack.replace(Qt.resolvedUrl("AddOTP.qml"), {parentPage: parentPage, paramLabel: ret.title, paramKey: ret.secret, paramType: ret.type, paramCounter: ret.counter, paramNew: true}) + if (ret.digits != "") { + len = ret.digits + } + pageStack.replace(Qt.resolvedUrl("AddOTP.qml"), {parentPage: parentPage, paramLabel: ret.title, paramKey: ret.secret, paramType: ret.type, paramCounter: ret.counter, paramLen: len, paramNew: true}) } else { notify.show(qsTr("No valid Token data found."), 3000); } diff --git a/rpm/harbour-sailotp.changes b/rpm/harbour-sailotp.changes index 5aba949..740bead 100644 --- a/rpm/harbour-sailotp.changes +++ b/rpm/harbour-sailotp.changes @@ -1,3 +1,7 @@ +* Thu Jul 14 2016 Stefan Brand 1.4-1 +- Added Setting for Time Derivation +- Added Setting for Token Length + * Sun Dec 06 2015 Stefan Brand 1.3-1 - Added SteamGuard OTP Type (Thanks to Robin Appelman) diff --git a/rpm/harbour-sailotp.yaml b/rpm/harbour-sailotp.yaml index 91145bc..20201e9 100644 --- a/rpm/harbour-sailotp.yaml +++ b/rpm/harbour-sailotp.yaml @@ -1,6 +1,6 @@ Name: harbour-sailotp Summary: SailOTP -Version: 1.3 +Version: 1.4 Release: 1 Group: Security URL: https://github.com/seiichiro0185/sailotp/