mirror of
https://github.com/seiichiro0185/sailotp.git
synced 2024-11-22 15:49:43 +00:00
Merge branch 'feat-cleanup' into develop
This commit is contained in:
commit
12cf6106a5
10 changed files with 386 additions and 301 deletions
26
COPYING
Normal file
26
COPYING
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
Copyright (c) 2013, Stefan Brand <seiichiro@seiichiro0185.org>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
materials provided with the distribution.
|
||||||
|
|
||||||
|
3. The names of the contributors may not be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||||
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
36
README.md
Normal file
36
README.md
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# SailOTP
|
||||||
|
|
||||||
|
SailOTP is a Sailfish Implementation of the Google-Authenticator algorithm,
|
||||||
|
also known as Timebased One Time Pad (TOPT) as described in RFC 6238. A growing
|
||||||
|
number of sites uses this algorithm for two-factor-authentication, including
|
||||||
|
Github, Linode and several Google services.
|
||||||
|
|
||||||
|
At the moment the App is quite basic. One can add new OTP-entries using the
|
||||||
|
pulley-menu. The main view of the app will show a List off all entries and
|
||||||
|
their current One-Time-Tokens. The entries will be regenerated every 30 seconds, the remaining time for the current tokens is shown through a progress bar at the top of the app. An entry can be deleted by long-pressing on it.
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
At the moment the only way to insert new entries into the app is to insert the
|
||||||
|
title and secret key by hand. It's not possible to use the QR-Codes some sites
|
||||||
|
provide directly.
|
||||||
|
|
||||||
|
## Contact and Issues
|
||||||
|
|
||||||
|
If you find any bugs or want to suggest a feature, feel free to use Githubs
|
||||||
|
Issues feature.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
SailOTP is licensed under a 3-Clause BSD-License. See COPYING for details.
|
||||||
|
|
||||||
|
## Accnowledgements
|
||||||
|
|
||||||
|
SailOTP uses the SHA-1 and HMAC-Implementation from
|
||||||
|
|
||||||
|
<a href="https://github.com/Caligatio/jsSHA" target="_blank">https://github.com/Caligatio/jsSHA</a>
|
||||||
|
|
||||||
|
The implementation of the TOTP-algorithm was inspired by:
|
||||||
|
|
||||||
|
<a href="http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/" target="_blank">http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/</a>
|
||||||
|
|
|
@ -30,8 +30,10 @@
|
||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
|
// Define the Layout of the Active Cover
|
||||||
CoverBackground {
|
CoverBackground {
|
||||||
|
|
||||||
|
// Show the SailOTP Logo
|
||||||
Image {
|
Image {
|
||||||
id: logo
|
id: logo
|
||||||
source: "../sailotp.png"
|
source: "../sailotp.png"
|
||||||
|
@ -40,6 +42,7 @@ CoverBackground {
|
||||||
anchors.topMargin: 48
|
anchors.topMargin: 48
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show the Application Name
|
||||||
Label {
|
Label {
|
||||||
id: label
|
id: label
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
|
@ -29,12 +29,15 @@
|
||||||
|
|
||||||
.import "./sha.js" as SHA
|
.import "./sha.js" as SHA
|
||||||
|
|
||||||
// Helper Functions
|
// *** Helper Functions *** //
|
||||||
|
|
||||||
|
// Decimal to HEX
|
||||||
function dec2hex(s) { return (s < 15.5 ? '0' : '') + Math.round(s).toString(16); }
|
function dec2hex(s) { return (s < 15.5 ? '0' : '') + Math.round(s).toString(16); }
|
||||||
|
|
||||||
|
// HEX to Decimal
|
||||||
function hex2dec(s) { return parseInt(s, 16); }
|
function hex2dec(s) { return parseInt(s, 16); }
|
||||||
|
|
||||||
|
// Convert Base32-secret to HEX Value
|
||||||
function base32tohex(base32) {
|
function base32tohex(base32) {
|
||||||
var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||||
var bits = "";
|
var bits = "";
|
||||||
|
@ -50,9 +53,9 @@ function base32tohex(base32) {
|
||||||
hex = hex + parseInt(chunk, 2).toString(16) ;
|
hex = hex + parseInt(chunk, 2).toString(16) ;
|
||||||
}
|
}
|
||||||
return hex;
|
return hex;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pad Strings to given length
|
||||||
function leftpad(str, len, pad) {
|
function leftpad(str, len, pad) {
|
||||||
if (len + 1 >= str.length) {
|
if (len + 1 >= str.length) {
|
||||||
str = Array(len + 1 - str.length).join(pad) + str;
|
str = Array(len + 1 - str.length).join(pad) + str;
|
||||||
|
@ -60,18 +63,28 @@ function leftpad(str, len, pad) {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *** Main Function *** //
|
||||||
|
|
||||||
// Calculate an OTP-Value from the given secret
|
// Calculate an OTP-Value from the given secret
|
||||||
|
// Parameter is the secret key in Base32-notation
|
||||||
function calcOTP(secret) {
|
function calcOTP(secret) {
|
||||||
|
// Convert the key to HEX
|
||||||
var key = base32tohex(secret);
|
var key = base32tohex(secret);
|
||||||
|
// Get current Time in UNIX Timestamp format (Seconds since 01.01.1970 00:00 UTC)
|
||||||
var epoch = Math.round(new Date().getTime() / 1000.0);
|
var epoch = Math.round(new Date().getTime() / 1000.0);
|
||||||
|
// Get last full 30 / 60 Seconds and convert to HEX
|
||||||
var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0');
|
var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0');
|
||||||
|
|
||||||
|
// Calculate the SHA-1 HMAC Value from time and key
|
||||||
var hmacObj = new SHA.jsSHA(time, 'HEX');
|
var hmacObj = new SHA.jsSHA(time, 'HEX');
|
||||||
var hmac = hmacObj.getHMAC(key, 'HEX', 'SHA-1', "HEX");
|
var hmac = hmacObj.getHMAC(key, 'HEX', 'SHA-1', "HEX");
|
||||||
|
|
||||||
|
// Finally convert the HMAC-Value to the corresponding 6-digit token
|
||||||
var offset = hex2dec(hmac.substring(hmac.length - 1));
|
var offset = hex2dec(hmac.substring(hmac.length - 1));
|
||||||
|
|
||||||
var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + '';
|
var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + '';
|
||||||
otp = (otp).substr(otp.length - 6, 6);
|
otp = (otp).substr(otp.length - 6, 6);
|
||||||
|
|
||||||
|
// return the calculated token
|
||||||
return otp;
|
return otp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,16 +30,15 @@
|
||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
|
// Define the Layout of the About Page
|
||||||
Page {
|
Page {
|
||||||
id: aboutPage
|
id: aboutPage
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: logo
|
id: logo
|
||||||
source: "../sailotp.png"
|
source: "../sailotp.png"
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
y: 200
|
y: 200
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: name
|
id: name
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
@ -81,5 +80,4 @@ Page {
|
||||||
text: "SailOTP uses the SHA-1 Implementation<br />from http://caligatio.github.io/jsSHA/"
|
text: "SailOTP uses the SHA-1 Implementation<br />from http://caligatio.github.io/jsSHA/"
|
||||||
color: "white"
|
color: "white"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,13 @@
|
||||||
|
|
||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
import "../lib/storage.js" as DB
|
import "../lib/storage.js" as DB // Import the storage library for Config-Access
|
||||||
|
|
||||||
|
// Define Layout of the Add OTP Dialog
|
||||||
Dialog {
|
Dialog {
|
||||||
id: addOTP
|
id: addOTP
|
||||||
|
|
||||||
|
// We get the Object of the parent page on call to refresh it after adding a new Entry
|
||||||
property QtObject parentPage: null
|
property QtObject parentPage: null
|
||||||
|
|
||||||
SilicaFlickable {
|
SilicaFlickable {
|
||||||
|
@ -48,7 +50,6 @@ Dialog {
|
||||||
DialogHeader {
|
DialogHeader {
|
||||||
acceptText: "Add"
|
acceptText: "Add"
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
id: otpLabel
|
id: otpLabel
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
@ -57,7 +58,6 @@ Dialog {
|
||||||
focus: true
|
focus: true
|
||||||
horizontalAlignment: TextInput.AlignLeft
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
id: otpSecret
|
id: otpSecret
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
@ -69,13 +69,16 @@ Dialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save if page is Left with Add
|
||||||
onDone: {
|
onDone: {
|
||||||
|
// Some basic Input Check, we need both Values to work
|
||||||
if (otpLabel.text != "" && otpSecret.text != "") {
|
if (otpLabel.text != "" && otpSecret.text != "") {
|
||||||
|
// Save the entry to the Config DB
|
||||||
DB.addOTP(otpLabel.text, otpSecret.text);
|
DB.addOTP(otpLabel.text, otpSecret.text);
|
||||||
|
// Refresh the main Page
|
||||||
parentPage.refreshOTPList();
|
parentPage.refreshOTPList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ Page {
|
||||||
id: otpListModel
|
id: otpListModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This holds the time of the last update of the page as Unix Timestamp (in Milliseconds)
|
||||||
property double lastUpdated: null
|
property double lastUpdated: null
|
||||||
|
|
||||||
// Add an entry to the list
|
// Add an entry to the list
|
||||||
|
@ -56,10 +57,13 @@ Page {
|
||||||
|
|
||||||
// Calculate new OTPs for every entry
|
// Calculate new OTPs for every entry
|
||||||
function refreshOTPValues() {
|
function refreshOTPValues() {
|
||||||
|
// get seconds from current Date
|
||||||
var curDate = new Date();
|
var curDate = new Date();
|
||||||
var seconds = curDate.getSeconds();
|
var seconds = curDate.getSeconds();
|
||||||
|
|
||||||
|
// Iterate over all List entries
|
||||||
for (var i=0; i<otpListModel.count; i++) {
|
for (var i=0; i<otpListModel.count; i++) {
|
||||||
|
// Only update on full 30 / 60 Seconds or if last run of the Functions is more than 2s in the past (e.g. app was in background)
|
||||||
if (otpListModel.get(i).otp == "" || seconds == 30 || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) {
|
if (otpListModel.get(i).otp == "" || seconds == 30 || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) {
|
||||||
var curOTP = OTP.calcOTP(otpListModel.get(i).secret)
|
var curOTP = OTP.calcOTP(otpListModel.get(i).secret)
|
||||||
otpListModel.setProperty(i, "otp", curOTP);
|
otpListModel.setProperty(i, "otp", curOTP);
|
||||||
|
@ -67,15 +71,16 @@ Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the Progressbar
|
||||||
updateProgress.value = 29 - (seconds % 30)
|
updateProgress.value = 29 - (seconds % 30)
|
||||||
|
// Set lastUpdate property
|
||||||
lastUpdated = curDate.getTime();
|
lastUpdated = curDate.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 1000
|
interval: 1000
|
||||||
running: Qt.application.active
|
running: Qt.application.active // Timer only runs when App is active
|
||||||
repeat: true
|
repeat: true
|
||||||
|
|
||||||
onTriggered: refreshOTPValues();
|
onTriggered: refreshOTPValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +130,7 @@ Page {
|
||||||
contentHeight: Theme.itemSizeMedium
|
contentHeight: Theme.itemSizeMedium
|
||||||
|
|
||||||
function remove() {
|
function remove() {
|
||||||
|
// Show 5s countdown, then delete from DB and List
|
||||||
remorseAction("Deleting", function() { DB.removeOTP(title, secret); otpListModel.remove(index) })
|
remorseAction("Deleting", function() { DB.removeOTP(title, secret); otpListModel.remove(index) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +168,9 @@ Page {
|
||||||
VerticalScrollDecorator{}
|
VerticalScrollDecorator{}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
// Initialize DB (create tables etc..)
|
||||||
DB.initialize();
|
DB.initialize();
|
||||||
|
// Load list of OTP-Entries
|
||||||
refreshOTPList();
|
refreshOTPList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,30 @@
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2013 Jolla Ltd.
|
* Copyright (c) 2013, Stefan Brand <seiichiro@seiichiro0185.org>
|
||||||
Contact: Thomas Perl <thomas.perl@jollamobile.com>
|
* All rights reserved.
|
||||||
All rights reserved.
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
You may use this file under the terms of BSD license as follows:
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
Redistribution and use in source and binary forms, with or without
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
modification, are permitted provided that the following conditions are met:
|
* list of conditions and the following disclaimer.
|
||||||
* Redistributions of source code must retain the above copyright
|
*
|
||||||
notice, this list of conditions and the following disclaimer.
|
* 2. Redistributions in binary form must reproduce the above copyright notice, this
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
* list of conditions and the following disclaimer in the documentation and/or other
|
||||||
notice, this list of conditions and the following disclaimer in the
|
* materials provided with the distribution.
|
||||||
documentation and/or other materials provided with the distribution.
|
*
|
||||||
* Neither the name of the Jolla Ltd nor the
|
* 3. The names of the contributors may not be used to endorse or promote products
|
||||||
names of its contributors may be used to endorse or promote products
|
* derived from this software without specific prior written permission.
|
||||||
derived from this software without specific prior written permission.
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef QT_QML_DEBUG
|
#ifdef QT_QML_DEBUG
|
||||||
|
@ -34,7 +33,6 @@
|
||||||
|
|
||||||
#include <sailfishapp.h>
|
#include <sailfishapp.h>
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
return SailfishApp::main(argc, argv);
|
return SailfishApp::main(argc, argv);
|
||||||
|
|
Loading…
Reference in a new issue