From c339901c4763aee5dfd3f09657e60a625520d1e4 Mon Sep 17 00:00:00 2001 From: Sneezry Date: Fri, 24 Apr 2015 02:54:40 +0800 Subject: [PATCH] 4.10 Add HOTP support --- _locales/en/messages.json | 18 ++++++++----- _locales/zh_CN/messages.json | 18 ++++++++----- css/popup.css | 44 ++++++++++++++++++++++++++----- javascript/background.js | 15 +++++++++-- javascript/popup.js | 51 ++++++++++++++++++++++++++++++------ manifest.json | 2 +- popup.html | 8 ++++++ totp/totp.js | 14 ++++++---- 8 files changed, 133 insertions(+), 37 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 5a39a5d..24a05a8 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -32,8 +32,8 @@ "description": "Scan QR Code." }, "add_secret": { - "message": "Input Secret", - "description": "Input Secret." + "message": "Manual Entry", + "description": "Manual Entry." }, "close": { "message": "Close", @@ -55,10 +55,6 @@ "message": "Secret", "description": "Secret." }, - "exportWarning": { - "message": "Warning! You may lose your data if you do not know what you are doing!", - "description": "Export Warning." - }, "updateSuccess": { "message": "Success.", "description": "Update Success." @@ -156,7 +152,15 @@ "description": "Capture Failed" }, "unencrypted_secret_warning": { - "message": "This secret is not encrypted! Click here to open security setting to fix this issue.", + "message": "This secret is not encrypted! Click here to set a passphrase to fix this issue.", "description": "Unencrypted Secret Warning" + }, + "based_on_time": { + "message": "Time Based", + "description": "Time Based" + }, + "based_on_counter": { + "message": "Counter Based", + "description": "Counter Based" } } \ No newline at end of file diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index fbbfa86..0d54268 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -32,8 +32,8 @@ "description": "Scan QR Code." }, "add_secret": { - "message": "输入密钥", - "description": "Input Secret." + "message": "手动输入", + "description": "Manual Entry." }, "close": { "message": "关闭", @@ -55,10 +55,6 @@ "message": "密钥", "description": "Secret." }, - "exportWarning": { - "message": "警告!如果你不知道正在做什么可能会丢失你的数据!", - "description": "Export Warning." - }, "updateSuccess": { "message": "成功。", "description": "Update Success." @@ -156,7 +152,15 @@ "description": "Capture Failed" }, "unencrypted_secret_warning": { - "message": "此密钥未被加密!点击此处打开安全设置以解决此问题。", + "message": "此密钥未被加密!点击此处来设置一个密码以解决此问题。", "description": "Unencrypted Secret Warning" + }, + "based_on_time": { + "message": "基于时间", + "description": "Time Based" + }, + "based_on_counter": { + "message": "基于计数器", + "description": "Counter Based" } } \ No newline at end of file diff --git a/css/popup.css b/css/popup.css index 7582a7c..04b408e 100644 --- a/css/popup.css +++ b/css/popup.css @@ -190,7 +190,7 @@ body { font-size: 12px; padding: 0 10px; margin: 0 4px; - width: 290px; + width: 250px; bottom: 4px; left: 0; background: #EC6959; @@ -227,7 +227,7 @@ body { } #codes.edit .code { - color: gray; + color: #CCC!important; -webkit-user-select: none; cursor: default; } @@ -258,10 +258,20 @@ body { display: block; } -#codes.timeout .code { +#codes.timeout .code:not(.hotp) { -webkit-animation: twinkling 1s infinite ease-in-out; } +.hotp { + color: #555; + cursor: default; +} + +.hotp[hasCode="true"] { + color: #08C; + cursor: pointer; +} + .movehandle { height: 98px; line-height: 98px; @@ -390,7 +400,24 @@ body { cursor: pointer; } -.sector { +.counter { + color: #888; + font-size: 18px; + text-align: center; + cursor: pointer; +} + +.counter:not([disabled="true"]):hover { + color: #000; +} + +.counter[disabled="true"] { + color: #CCC; + cursor: default; +} + +.sector, +.counter { width: 20px; height: 20px; position: absolute; @@ -398,7 +425,8 @@ body { bottom: 10px; } -#codes.edit .sector { +#codes.edit .sector, +#codes.edit .counter { display: none; } @@ -616,12 +644,14 @@ body { outline: none; } -.checkbox_group input[type="checkbox"] { +.checkbox_group input[type="checkbox"], +.radio_group input[type="radio"] { display: inline-block !important; width: auto !important; } -.checkbox_group label { +.checkbox_group label, +.radio_group label { display: inline-block !important; margin-left: 0 !important; } diff --git a/javascript/background.js b/javascript/background.js index c565f7a..761684f 100644 --- a/javascript/background.js +++ b/javascript/background.js @@ -49,7 +49,7 @@ function getQr(tab, left, top, width, height, windowWidth){ function getTotp(text){ var id = this.tab.id; - if(text.indexOf('otpauth://totp/') != 0){ + if(text.indexOf('otpauth://') != 0){ if(text == 'error decoding QR Code'){ chrome.tabs.sendMessage(id, {action: 'errorqr'}); } @@ -58,7 +58,9 @@ function getTotp(text){ } } else{ - var uri = text.split('otpauth://totp/')[1]; + var uri = text.split('otpauth://')[1]; + var type = uri.substr(0,4).toLowerCase(); + uri = uri.substr(5); var label = uri.split('?')[0]; var parameters = uri.split('?')[1]; if(!label || !parameters){ @@ -83,6 +85,10 @@ function getTotp(text){ else if(parameter[0].toLowerCase() == 'issuer'){ issuer = parameter[1]; } + else if(parameter[0].toLowerCase() == 'counter'){ + counter = Number(parameter[1]); + counter = (isNaN(counter)||counter<0)?0:counter; + } } if(!secret){ chrome.tabs.sendMessage(id, {action: 'errorqr'}); @@ -98,6 +104,7 @@ function getTotp(text){ addSecret[CryptoJS.MD5(secret)] = { account: account||'', issuer: issuer||'', + type: type, secret: CryptoJS.AES.encrypt(secret, decodedPhrase).toString(), index: index, encrypted: true @@ -107,10 +114,14 @@ function getTotp(text){ addSecret[CryptoJS.MD5(secret)] = { account: account||'', issuer: issuer||'', + type: type, secret: secret, index: index } } + if('hotp' === type && counter !== undefined){ + addSecret[CryptoJS.MD5(secret)].counter = counter; + } chrome.storage.sync.set(addSecret, function(){ chrome.tabs.sendMessage(id, {action: 'added', account: account}); }); diff --git a/javascript/popup.js b/javascript/popup.js index c243192..61f6f36 100644 --- a/javascript/popup.js +++ b/javascript/popup.js @@ -1,5 +1,5 @@ -var totp = new KeyUtilities(); -var getCode = totp.generate; +var otp = new KeyUtilities(); +var getCode = otp.generate; var _secret = []; var deleteIdList = []; var deleteKeyList = []; @@ -32,12 +32,13 @@ else if(document.cookie){ document.getElementById('extName').innerText = chrome.i18n.getMessage('extShortName'); document.getElementById('add_qr').innerText = chrome.i18n.getMessage('add_qr'); document.getElementById('add_secret').innerText = chrome.i18n.getMessage('add_secret'); +document.getElementById('totp_label').innerText = chrome.i18n.getMessage('based_on_time'); +document.getElementById('hotp_label').innerText = chrome.i18n.getMessage('based_on_counter'); document.getElementById('add_button').innerText = chrome.i18n.getMessage('ok'); document.getElementById('message_close').innerText = chrome.i18n.getMessage('ok'); document.getElementById('account_label').innerText = chrome.i18n.getMessage('account'); document.getElementById('secret_label').innerText = chrome.i18n.getMessage('secret'); document.getElementById('menuName').innerText = chrome.i18n.getMessage('settings'); - document.getElementById('security_new_phrase_label').innerText = chrome.i18n.getMessage('phrase'); document.getElementById('security_confirm_phrase_label').innerText = chrome.i18n.getMessage('confirm_phrase'); document.getElementById('security_warning').innerText = chrome.i18n.getMessage('security_warning'); @@ -325,6 +326,7 @@ function updateSecret(callback){ function saveSecret(){ var account = document.getElementById('account_input').value; var secret = document.getElementById('secret_input').value; + var type = document.getElementById('totp').checked?'totp':'hotp'; if(!account || !secret){ showMessage(chrome.i18n.getMessage('err_acc_sec')); return; @@ -337,6 +339,7 @@ function saveSecret(){ addSecret[CryptoJS.MD5(secret)] = { account: account, issuer: '', + type: type, secret: CryptoJS.AES.encrypt(secret, decodedPhrase).toString(), index: index, encrypted: true @@ -346,10 +349,14 @@ function saveSecret(){ addSecret[CryptoJS.MD5(secret)] = { account: account, issuer: '', + type: type, secret: secret, index: index } } + if('hotp' === type){ + addSecret[CryptoJS.MD5(secret)].counter = 0; + } chrome.storage.sync.set(addSecret); document.getElementById('infoAction').className = ''; document.getElementById('addAccount').className = ''; @@ -498,7 +505,7 @@ function updateCode(){ }, 200); } } - else{ + else if(_secret[i].type !== 'hotp'){ document.getElementById('code-'+i).innerText = getCode(_secret[i].secret); } } @@ -589,10 +596,10 @@ function showCodes(result){ el.id = 'codeBox-'+i; el.className = 'codeBox'; el.innerHTML = '
'+ - '
'+ + ('hotp'===_secret[i].type?'
':'
')+ (_secret[i].issuer?('
'+_secret[i].issuer+'
'):'')+ '
'+ - '
••••••
'+ + '
••••••
'+ '
'+_secret[i].account+'
'+ '
'+ '
'+ @@ -625,6 +632,10 @@ function showCodes(result){ for(var i=0; i +
+ + +
+
+ + +
diff --git a/totp/totp.js b/totp/totp.js index 5c2a9ac..75da67f 100644 --- a/totp/totp.js +++ b/totp/totp.js @@ -35,7 +35,7 @@ var KeyUtilities = function() { return str; }; - var generate = function(secret) { + var generate = function(secret, counter) { secret = secret.replace(/\s/g, ''); var len = 6; if(/^[a-z2-7]+=*$/.test(secret.toLowerCase())) { @@ -52,11 +52,15 @@ var KeyUtilities = function() { var key = base32tohex(secret.substr(4)); len = 8; } - var epoch = Math.round(new Date().getTime() / 1000.0); - if(localStorage.offset){ - epoch = epoch+Number(localStorage.offset); + if(isNaN(counter)){ + var epoch = Math.round(new Date().getTime() / 1000.0); + if(localStorage.offset){ + epoch = epoch+Number(localStorage.offset); + } + var counter = Math.floor(epoch / 30) } - var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0'); + + var time = leftpad(dec2hex(counter), 16, '0'); // external library for SHA functionality var hmacObj = new jsSHA(time, "HEX");