diff --git a/README.md b/README.md index 32b8457..e4fc753 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ if you need the information outside of SailOTP. Importing the file is also possible from the pulley menu. If a file contains tokens that are already in the database (title and secret of the token match an existing one), these tokens will not be added again. +It is also possible to generate tokens for SteamGuard (Steams TOTP-Variant for 2-Factor-Auth). To use this feature, one will have to activate it using the original Steam Android app. After activating it one can get the secret code from '/opt/alien/data/data/com.valvesoftware.android.steam.community/files/Steamguard-$STEAMID'. This file contains json data, including the OTP-URL in the form 'otpauth://totp/Steam:$STEAM_USERNAME?secret=$SECRET&issuer=Steam'. The code from this URL can be added manually using the 'Steam Guard'-OTP-Type in SailOTP. + ## Contact and Issues If you find any bugs or want to suggest a feature, feel free to use Githubs diff --git a/i18n/de.ts b/i18n/de.ts index 7a30a33..c74d5be 100644 --- a/i18n/de.ts +++ b/i18n/de.ts @@ -12,26 +12,36 @@ Copyright: Stefan Brand<br/>Lizenz: BSD (3-Klausel) - + A Simple Sailfish OTP Generator (RFC 6238/4226 compatible) Ein einfacher Sailfish OTP-Generator (RFC 6238/4226-kompatibel) - + Copyright: Stefan Brand License: BSD (3-clause) Copyright: Stefan Brand Lizenz: BSD (3-Klausel) - + SailOTP uses the following third party libs: SailOTP verwendet folgende externe Bibliotheken: - + + Contributors: + Mitwirkende: + + + + Support + Unterstützung + + + Translators: Übersetzer: @@ -39,67 +49,72 @@ 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 - + Next Counter Value Nächster Zählerwert - + Next Value of the Counter Nächster Wert für den Zähler @@ -107,114 +122,114 @@ Lizenz: BSD (3-Klausel) ExportPage - + File already exists, choose "Overwrite existing" to overwrite it. Datei existiert, aktiviere "Existierende überschreiben" um sie zu ersetzen. - + Given file does not exist! Gewählte Datei existiert nicht! - - + + Export Export - - + + Import Import - + Filename Dateiname - + File to import Aus Datei importieren - + File to export In Datei exportieren - + Overwrite existing Existierende überschreiben - + Password Passwort - + Password for the file Passwort für die Datei - + Passwords don't match! Passwörter nicht identisch! - + Passwords match! Passwörter identisch! - + Repeated Password for the file Passwort wiederholen - + Here you can Import Tokens from a file. Put in the file location and the password you used on export. Pull left to start the import. Hier können Tokens aus einer Datei importiert werden. Gib die Datei und das beim Export gewählte Passwort ein. Nach links ziehen um zu starten. - + Here you can export Tokens to a file. The exported file will be encrypted with AES-256-CBC and Base64 encoded. Choose a strong password, the file will contain the secrets used to generate the Tokens for your accounts. Pull left to start the export. Hier können Tokens in eine Datei exportiert werden. Die Datei wird mit AES-256-CBC verschlüsselt und Base64-kodiert. Wähle ein starkes Passwort, die Datei enthält die geheimen Schlüssel zur Erzeugung der Tokens für deine Accounts. Nach links ziehen um zu starten. - + Error writing to file Fehler beim Schreiben der Datei - + Token Database exported to Datenbank exportiert nach - + Could not encrypt tokens. Error: Fehler beim Verschlüsseln. Fehler: - + Could not read tokens from Database Datenbank konnte nicht gelesen werden - + Tokens imported from Tokens importiert aus - + Unable to decrypt file, did you use the right password? Fehler beim entschlüsseln, falsches Passwort? - + Could not read from file Datei konnte nicht gelesen werden @@ -222,7 +237,7 @@ Lizenz: BSD (3-Klausel) MainView - + About Über @@ -235,57 +250,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 @@ -293,7 +308,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! @@ -301,32 +316,32 @@ Lizenz: BSD (3-Klausel) ScanOTP - + Can't access temporary directory Kein Zugriff auf temporäres Verzeichnis. - + Add manually Manuell hinzufügen - + Scan Code Code scannen - + Scanning... 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 75939ea..eb435ab 100644 --- a/i18n/en.ts +++ b/i18n/en.ts @@ -4,24 +4,34 @@ About - + A Simple Sailfish OTP Generator (RFC 6238/4226 compatible) - + Copyright: Stefan Brand License: BSD (3-clause) - + SailOTP uses the following third party libs: - + + Contributors: + Contributor: + + + + Support + support + + + Translators: Translators: @@ -29,67 +39,72 @@ 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 - + Next Counter Value - + Next Value of the Counter @@ -97,114 +112,114 @@ License: BSD (3-clause) ExportPage - + File already exists, choose "Overwrite existing" to overwrite it. - + Given file does not exist! - - + + Export - - + + Import - + Filename - + File to import - + File to export - + Overwrite existing - + Password - + Password for the file - + Passwords don't match! - + Passwords match! - + Repeated Password for the file - + Here you can Import Tokens from a file. Put in the file location and the password you used on export. Pull left to start the import. - + Here you can export Tokens to a file. The exported file will be encrypted with AES-256-CBC and Base64 encoded. Choose a strong password, the file will contain the secrets used to generate the Tokens for your accounts. Pull left to start the export. - + Error writing to file - + Token Database exported to - + Could not encrypt tokens. Error: - + Could not read tokens from Database - + Tokens imported from - + Unable to decrypt file, did you use the right password? - + Could not read from file @@ -212,62 +227,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 @@ -275,7 +290,7 @@ License: BSD (3-clause) QRPage - + Can't create QR-Code from incomplete settings! Can't create QR-Code from incomplete settings! @@ -283,32 +298,32 @@ License: BSD (3-clause) ScanOTP - + Can't access temporary directory - + Add manually - + Scan Code - + Scanning... - + 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 01cfa84..90b64e3 100644 --- a/i18n/sv.ts +++ b/i18n/sv.ts @@ -4,26 +4,36 @@ About - + A Simple Sailfish OTP Generator (RFC 6238/4226 compatible) En enkel Sailfish OTP-generator (RFC 6238/4226-kompatibel) - + Copyright: Stefan Brand License: BSD (3-clause) Copyright: Stefan Brand Licens: BSD (3-clause) - + SailOTP uses the following third party libs: SailOTP använder följande tredjeparts bibliotek: - + + Contributors: + Medarbetare: + + + + Support + försörjning + + + Translators: Översättare: @@ -31,67 +41,72 @@ 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 - + Next Counter Value Nästa räknarvärde - + Next Value of the Counter Nästa värde på räknaren @@ -99,114 +114,114 @@ Licens: BSD (3-clause) ExportPage - + File already exists, choose "Overwrite existing" to overwrite it. Filen finns redan. Välj "Skriv över befintlig" för att skriva över den. - + Given file does not exist! Angiven fil finns inte! - - + + Export Exportera - - + + Import Importera - + Filename Filnamn - + File to import Fil att importera - + File to export Fil att exportera - + Overwrite existing Skriv över befintlig - + Password Lösenord - + Password for the file Lösenord för filen - + Passwords don't match! Lösenorden stämmer inte! - + Passwords match! Lösenorden stämmer! - + Repeated Password for the file Upprepat lösenord för filen - + Here you can Import Tokens from a file. Put in the file location and the password you used on export. Pull left to start the import. Här kan du importera Token från en fil. Ange filens sökväg och lösenordet du angav vid exporten. Svep åt vänster för att starta importen. - + Here you can export Tokens to a file. The exported file will be encrypted with AES-256-CBC and Base64 encoded. Choose a strong password, the file will contain the secrets used to generate the Tokens for your accounts. Pull left to start the export. Här kan du exportera Token till en fil. Den exporterade filen kommer att krypteras med AES-256-CBC och Base64. Välj ett starkt lösenord, filen kommer att innehålla hemligheterna som användes för att generera Token för ditt konto. Svep åt vänster för att starta exporten. - + Error writing to file Fel vid skrivning till fil - + Token Database exported to Token-databas exporterad till - + Could not encrypt tokens. Error: Kunde inte kryptera token. Fel: - + Could not read tokens from Database Kunde inte läsa in token från databasen - + Tokens imported from Token importerad från - + Unable to decrypt file, did you use the right password? Kunde inte dekryptera filen. Angav du rätt lösenord? - + Could not read from file Kunde inte läsa från fil @@ -214,62 +229,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 @@ -277,7 +292,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! @@ -285,32 +300,32 @@ Licens: BSD (3-clause) ScanOTP - + Can't access temporary directory Kan inte komma åt temp-mapp - + Add manually Lägg till manuellt - + Scan Code Skanna kod - + Scanning... 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 6a31e98..9937bc8 100644 --- a/qml/cover/CoverPage.qml +++ b/qml/cover/CoverPage.qml @@ -43,7 +43,7 @@ CoverBackground { var curDate = new Date(); if (lOTP.text == "------" || curDate.getSeconds() == 30 || curDate.getSeconds() == 0 || (curDate.getTime() - lastUpdated > 2000)) { - appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, "TOTP", 0); + appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, appWin.coverType, 0); } // Change color of the OTP to red if less than 5 seconds left @@ -59,7 +59,7 @@ CoverBackground { Timer { interval: 1000 // Timer runs only when cover is visible and favourite is set - running: !Qt.application.active && appWin.coverSecret != "" && appWin.coverType == "TOTP" + running: !Qt.application.active && appWin.coverSecret != "" && (appWin.coverType == "TOTP" || appWin.coverType == "TOTP_STEAM") repeat: true onTriggered: updateOTP(); } @@ -107,7 +107,7 @@ CoverBackground { if (index < 0) index = appWin.listModel.count - 1 appWin.setCover(index); DB.setFav(appWin.coverTitle, appWin.coverSecret) - if (appWin.coverType == "TOTP") updateOTP(); + if (appWin.coverType == "TOTP" || appWin.coverType == "TOTP_STEAM") updateOTP(); } } } @@ -118,7 +118,7 @@ CoverBackground { if (index >= appWin.listModel.count) index = 0 appWin.setCover(index); DB.setFav(appWin.coverTitle, appWin.coverSecret) - if (appWin.coverType == "TOTP") updateOTP(); + if (appWin.coverType == "TOTP" || appWin.coverType == "TOTP_STEAM") updateOTP(); } } } diff --git a/qml/lib/crypto.js b/qml/lib/crypto.js index 9cba4f0..bff4a58 100644 --- a/qml/lib/crypto.js +++ b/qml/lib/crypto.js @@ -63,6 +63,11 @@ function leftpad(str, len, pad) { return str; } +// characters steam uses to generate the final code +var steamChars = ['2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C', + 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', + 'R', 'T', 'V', 'W', 'X', 'Y'] + // *** Main Function *** // // Calculate an OTP-Value from the given secret @@ -75,7 +80,7 @@ function calcOTP(secret, type, counter) { var key = base32tohex(secret); var factor = ""; - if (type == "TOTP") { + if (type.substr(0, 4) == "TOTP") { // Get current Time in UNIX Timestamp format (Seconds since 01.01.1970 00:00 UTC) var epoch = Math.round(new Date().getTime() / 1000.0); // Get last full 30 / 60 Seconds and convert to HEX @@ -92,8 +97,19 @@ function calcOTP(secret, type, counter) { // Finally convert the HMAC-Value to the corresponding 6-digit token var offset = hex2dec(hmac.substring(hmac.length - 1)); - var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + ''; - otp = (otp).substr(otp.length - 6, 6); + var code = hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff'); + var otp = ''; + + // Steam has it's own way of creating the code from the result + if (type == "TOTP_STEAM") { + for (var i = 0; i < 5; i++) { + otp += steamChars[code % steamChars.length]; + code = Math.floor(code/steamChars.length); + } + } else { + otp = code + ''; + otp = (otp).substr(otp.length - 6, 6); + } } catch (e) { otp = "Invalid Secret!" } diff --git a/qml/lib/storage.js b/qml/lib/storage.js index ec139ff..94fc281 100644 --- a/qml/lib/storage.js +++ b/qml/lib/storage.js @@ -200,13 +200,10 @@ function changeOTP(title, secret, type, counter, oldtitle, oldsecret) { db.transaction( function(tx) { - if (checkOTP(title, secret)) { - console.log("Token " + title + " is already in DB"); - } else { tx.executeSql("UPDATE OTPStorage SET title=?, secret=?, type=?, counter=? WHERE title=? and secret=?;", [title, secret, type, counter, oldtitle, oldsecret]); console.log("Token " + title + " modified."); } - }); + ); } function changeOTPSort(title, secret, sort) { diff --git a/qml/pages/About.qml b/qml/pages/About.qml index 6f3031c..bea3e23 100644 --- a/qml/pages/About.qml +++ b/qml/pages/About.qml @@ -33,6 +33,9 @@ import Sailfish.Silica 1.0 // Define the Layout of the About Page Page { id: aboutPage + + allowedOrientations: Orientation.All + SilicaFlickable { id: flickable anchors.fill: parent @@ -93,6 +96,17 @@ Page { color: "white" } + TextArea { + id: contributors + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width + font.pixelSize: Theme.fontSizeSmall + horizontalAlignment: TextEdit.Center + readOnly: true + text: qsTr("Contributors:")+"\n\nRobin Appelman: SteamGuard "+qsTr("Support") + color: "white" + } + TextArea { id: translators anchors.horizontalCenter: parent.horizontalCenter diff --git a/qml/pages/AddOTP.qml b/qml/pages/AddOTP.qml index 782f7a6..fe79017 100644 --- a/qml/pages/AddOTP.qml +++ b/qml/pages/AddOTP.qml @@ -36,6 +36,8 @@ import "../lib/storage.js" as DB // Import the storage library for Config-Access Dialog { id: addOTP + allowedOrientations: Orientation.All + // We get the Object of the parent page on call to refresh it after adding a new Entry property QtObject parentPage: null @@ -65,7 +67,7 @@ Dialog { MenuItem { text: qsTr("Show QR-Code") onClicked: { - if ((paramType == "TOTP" && (otpLabel.text == "" || otpSecret.text == "")) || (paramType == "HOTP" && (otpLabel.text == "" || otpSecret.text == "" || otpCounter.text <= 0))) { + 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}); @@ -88,6 +90,7 @@ Dialog { menu: ContextMenu { MenuItem { text: qsTr("Time-based (TOTP)"); onClicked: { paramType = "TOTP" } } MenuItem { text: qsTr("Counter-based (HOTP)"); onClicked: { paramType = "HOTP" } } + MenuItem { text: qsTr("Steam Guard"); onClicked: { paramType = "TOTP_STEAM" } } } } TextField { @@ -113,8 +116,8 @@ Dialog { horizontalAlignment: TextInput.AlignLeft EnterKey.enabled: text.length > 15 - EnterKey.iconSource: paramType == "TOTP" ? "image://theme/icon-m-enter-accept" : "image://theme/icon-m-enter-next" - EnterKey.onClicked: paramType == "TOTP" ? addOTP.accept() : otpCounter.focus = true + 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() } TextField { id: otpCounter @@ -135,7 +138,7 @@ Dialog { } // Check if we can Save - canAccept: otpLabel.text.length > 0 && otpSecret.text.length >= 16 && (paramType == "TOTP" || otpCounter.text.length > 0) ? true : false + canAccept: otpLabel.text.length > 0 && otpSecret.text.length >= 16 && (paramType == "TOTP" || paramType == "TOTP_STEAM" || otpCounter.text.length > 0) ? true : false // Save if page is Left with Add onDone: { diff --git a/qml/pages/ExportPage.qml b/qml/pages/ExportPage.qml index ea60aaa..290953f 100644 --- a/qml/pages/ExportPage.qml +++ b/qml/pages/ExportPage.qml @@ -38,6 +38,8 @@ import "../lib/gibberish-aes.js" as Gibberish //Import AES encryption library Dialog { id: exportPage + allowedOrientations: Orientation.All + // We get the Object of the parent page on call to refresh it after adding a new Entry property QtObject parentPage: null property string mode: "import" diff --git a/qml/pages/MainView.qml b/qml/pages/MainView.qml index 2a7e540..eb6b865 100644 --- a/qml/pages/MainView.qml +++ b/qml/pages/MainView.qml @@ -36,6 +36,8 @@ import "../lib/crypto.js" as OTP Page { id: mainPage + allowedOrientations: Orientation.All + // This holds the time of the last update of the page as Unix Timestamp (in Milliseconds) property double lastUpdated: 0 @@ -70,10 +72,10 @@ Page { // Iterate over all List entries for (var i=0; i 2000)) { - var curOTP = OTP.calcOTP(appWin.listModel.get(i).secret, "TOTP") + var curOTP = OTP.calcOTP(appWin.listModel.get(i).secret, appWin.listModel.get(i).type) appWin.listModel.setProperty(i, "otp", curOTP); } } else if (appWin.coverType == "HOTP" && (curDate.getTime() - lastUpdated > 2000) && appWin.listModel.get(i).fav == 1) { @@ -119,16 +121,20 @@ Page { width: parent.width maximumValue: 29 anchors.top: parent.top - anchors.topMargin: 48 + anchors.topMargin: 36 * Theme.pixelRatio // Only show when there are enries visible: appWin.listModel.count } + + SilicaListView { id: otpList + header: PageHeader { title: "SailOTP" } + anchors.fill: parent model: appWin.listModel width: parent.width diff --git a/qml/pages/QRPage.qml b/qml/pages/QRPage.qml index f5339bd..719eb82 100644 --- a/qml/pages/QRPage.qml +++ b/qml/pages/QRPage.qml @@ -33,6 +33,8 @@ import Sailfish.Silica 1.0 Page { id: qrpage + allowedOrientations: Orientation.All + property string paramType: "" property string paramLabel: "" property string paramKey: "" diff --git a/qml/pages/ScanOTP.qml b/qml/pages/ScanOTP.qml index 2821d23..66b5577 100644 --- a/qml/pages/ScanOTP.qml +++ b/qml/pages/ScanOTP.qml @@ -37,6 +37,8 @@ import "../lib/urldecoder.js" as URL Page { id: scanPage + allowedOrientations: Orientation.All + property QtObject parentPage: null property bool scanning: false diff --git a/rpm/harbour-sailotp.changes b/rpm/harbour-sailotp.changes index d0deb1b..5aba949 100644 --- a/rpm/harbour-sailotp.changes +++ b/rpm/harbour-sailotp.changes @@ -1,3 +1,6 @@ +* Sun Dec 06 2015 Stefan Brand 1.3-1 +- Added SteamGuard OTP Type (Thanks to Robin Appelman) + * Sat May 30 2015 Stefan Brand 1.2-1 - Added Swedish translation (Thanks to Åke Engelbrektson) diff --git a/rpm/harbour-sailotp.yaml b/rpm/harbour-sailotp.yaml index 1867833..91145bc 100644 --- a/rpm/harbour-sailotp.yaml +++ b/rpm/harbour-sailotp.yaml @@ -1,6 +1,6 @@ Name: harbour-sailotp Summary: SailOTP -Version: 1.2 +Version: 1.3 Release: 1 Group: Security URL: https://github.com/seiichiro0185/sailotp/