From 98afd2b574be888cb45ef37a5db5cb51d3a391c2 Mon Sep 17 00:00:00 2001 From: Stefan Brand Date: Sun, 17 Jul 2016 19:35:53 +0200 Subject: [PATCH] Version 1.4 Length of the Token can now be defined Time Derivation can be set --- i18n/de.ts | 74 ++++++++++++++++++++++--------------- i18n/en.ts | 74 ++++++++++++++++++++++--------------- i18n/sv.ts | 74 ++++++++++++++++++++++--------------- qml/cover/CoverPage.qml | 10 +++-- qml/harbour-sailotp.qml | 8 +++- qml/lib/crypto.js | 14 +++++-- qml/lib/storage.js | 41 +++++++++++++------- qml/lib/urldecoder.js | 1 + qml/pages/AddOTP.qml | 57 +++++++++++++++++++++++++--- qml/pages/MainView.qml | 26 ++++--------- qml/pages/QRPage.qml | 5 ++- qml/pages/ScanOTP.qml | 6 ++- rpm/harbour-sailotp.changes | 4 ++ rpm/harbour-sailotp.yaml | 2 +- 14 files changed, 258 insertions(+), 138 deletions(-) diff --git a/i18n/de.ts b/i18n/de.ts index c74d5be..2187c75 100644 --- a/i18n/de.ts +++ b/i18n/de.ts @@ -49,72 +49,88 @@ Lizenz: BSD (3-Klausel) AddOTP - + Show QR-Code QR-Code anzeigen - + Can't create QR-Code from incomplete settings! Ein QR-Code kann nur mit vollständigen Einstellungen erzeugt werden! - + Save Speichern - + Add Hinzufügen - + Type Typ - + Time-based (TOTP) Zeitbasiert (TOTP) - + Counter-based (HOTP) Zählerbasiert (HOTP) - + Steam Guard Steam Guard - + Title Titel - + Title for the OTP Titel für das Token - + Secret (at least 16 characters) Schlüssel (mindestens 16 Zeichen) - + Secret OTP Key Geheimer Schlüssel - + + Length + Länge + + + + Length of the Token + Länge des Tokens + + + + + Time Derivation (Seconds) + Zeitabweichung (Sekunden) + + + Next Counter Value Nächster Zählerwert - + Next Value of the Counter Nächster Wert für den Zähler @@ -237,7 +253,7 @@ Lizenz: BSD (3-Klausel) MainView - + About Über @@ -250,57 +266,57 @@ Lizenz: BSD (3-Klausel) Datenbank importieren - + Export / Import Export / Import - + Add Token Token hinzufügen - + Nothing here Hier ist nichts - + Pull down to add a OTP Nach unten ziehen zum hinzufügen - + Deleting Lösche - + Token for Token für - + copied to clipboard kopiert - + Move up Nach oben - + Move down Nach unten - + Edit Bearbeiten - + Delete Löschen @@ -308,7 +324,7 @@ Lizenz: BSD (3-Klausel) QRPage - + Can't create QR-Code from incomplete settings! Ein QR-Code kann nur mit vollständigen Einstellungen erzeugt werden! @@ -336,12 +352,12 @@ Lizenz: BSD (3-Klausel) scanne... - + No valid Token data found. Kein gültiges Token gefunden. - + Tap the picture to start / stop scanning. Pull down to add Token manually. Vorschau antippen um den Scan zu starten / zu stoppen. Nach unten ziehen um manuell hinzu zu fügen. diff --git a/i18n/en.ts b/i18n/en.ts index eb435ab..2197812 100644 --- a/i18n/en.ts +++ b/i18n/en.ts @@ -39,72 +39,88 @@ License: BSD (3-clause) AddOTP - + Show QR-Code Show QR-Code - + Can't create QR-Code from incomplete settings! Can't create QR-Code from incomplete settings! - + Save - + Add - + Type - + Time-based (TOTP) - + Counter-based (HOTP) - + Steam Guard Steam Guard - + Title - + Title for the OTP - + Secret (at least 16 characters) - + Secret OTP Key - + + Length + Length + + + + Length of the Token + Length of the Token + + + + + Time Derivation (Seconds) + Time Derivation (Seconds) + + + Next Counter Value - + Next Value of the Counter @@ -227,62 +243,62 @@ License: BSD (3-clause) MainView - + About - + Export / Import - + Add Token - + Nothing here - + Pull down to add a OTP - + Deleting - + Token for - + copied to clipboard - + Move up Move up - + Move down Move down - + Edit - + Delete @@ -290,7 +306,7 @@ License: BSD (3-clause) QRPage - + Can't create QR-Code from incomplete settings! Can't create QR-Code from incomplete settings! @@ -318,12 +334,12 @@ License: BSD (3-clause) - + No valid Token data found. - + Tap the picture to start / stop scanning. Pull down to add Token manually. diff --git a/i18n/sv.ts b/i18n/sv.ts index 90b64e3..bc48975 100644 --- a/i18n/sv.ts +++ b/i18n/sv.ts @@ -41,72 +41,88 @@ Licens: BSD (3-clause) AddOTP - + Show QR-Code Visa QR-kod - + Can't create QR-Code from incomplete settings! Kan inte skapa QR-kod från ofullständiga inställningar! - + Save Spara - + Add Lägg till - + Type Typ - + Time-based (TOTP) Tidsbaserad (TOTP) - + Counter-based (HOTP) Räknarbaserad (HOTP) - + Steam Guard Steam Guard - + Title Namn - + Title for the OTP Namn på OTP:n - + Secret (at least 16 characters) Hemlighet (Minst 16 tecken) - + Secret OTP Key Hemlig OTP-nyckel - + + Length + längd + + + + Length of the Token + Längden av token + + + + + Time Derivation (Seconds) + Tidsavvikelsen (sekunder) + + + Next Counter Value Nästa räknarvärde - + Next Value of the Counter Nästa värde på räknaren @@ -229,62 +245,62 @@ Licens: BSD (3-clause) MainView - + About Om - + Export / Import Export / Import - + Add Token Lägg till Token - + Nothing here Inget här - + Pull down to add a OTP Dra neråt för att lägga till en OTP - + Deleting Tar bort - + Token for Token för - + copied to clipboard kopierad till urklipp - + Move up Flytta upp - + Move down Flytta ner - + Edit Redigera - + Delete Ta bort @@ -292,7 +308,7 @@ Licens: BSD (3-clause) QRPage - + Can't create QR-Code from incomplete settings! Kan inte skapa QR-kod från ofullständiga inställningar! @@ -320,12 +336,12 @@ Licens: BSD (3-clause) Skannar... - + No valid Token data found. Ingen giltig Token-data hittades. - + Tap the picture to start / stop scanning. Pull down to add Token manually. Tryck på bilden för att starta / stoppa skanning. Dra neråt för att lägga till Token manuellt. 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/lib/urldecoder.js b/qml/lib/urldecoder.js index 552df7b..c3ab55e 100644 --- a/qml/lib/urldecoder.js +++ b/qml/lib/urldecoder.js @@ -15,6 +15,7 @@ function decode(url) { var tmp = pstr.split("="); if (tmp[0] == "secret") ret.secret = tmp[1]; if (tmp[0] == "counter") ret.counter = tmp[1]; + if (tmp[0] == "digits") ret.digits = tmp[1]; } return ret; diff --git a/qml/pages/AddOTP.qml b/qml/pages/AddOTP.qml index fe79017..33ed946 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 @@ -59,6 +61,21 @@ Dialog { SilicaFlickable { id: addOtpList anchors.fill: parent + contentHeight: dialog.height + + PullDownMenu { + visible: checkQR() + MenuItem { + text: qsTr("Show QR-Code") + onClicked: { + 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, paramLen: otpLen.text}); + } + } + } + } @@ -79,7 +96,8 @@ Dialog { VerticalScrollDecorator {} Column { - anchors.fill: parent + id: dialog + width: parent.width DialogHeader { acceptText: paramNew ? qsTr("Add") : qsTr("Save") } @@ -116,8 +134,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 +183,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 +191,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..fe7ac07 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 { @@ -61,11 +62,11 @@ Page { var otpurl = ""; if (paramType == "TOTP") { if (paramLabel != "" && paramKey != "") - otpurl = "otpauth://totp/"+paramLabel+"?secret="+paramKey; + otpurl = "otpauth://totp/"+paramLabel+"?secret="+paramKey+"&digits="+paramLen; } 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/