working test

This commit is contained in:
AJ ONeal 2015-10-23 00:11:29 -07:00
parent c0c0dac081
commit c38768772a
3 changed files with 223 additions and 0 deletions

86
authenticator.js Normal file
View File

@ -0,0 +1,86 @@
(function (exports) {
'use strict';
var Authenticator = exports.Authenticator || exports;
var Unibabel = window.Unibabel || require('unibabel');
console.log('window.totp', window.totp);
var totp = window.totp || require('notp').totp;
if (!window.crypto) {
document.addEventListener('mousemove', function (event) {
var ev = event || window.event;
window.forge.random.collectInt(ev.pageX, 16);
window.forge.random.collectInt(ev.pageY, 16);
});
}
// Generate a key
function generateOtpKey() {
// 20 cryptographically random binary bytes (160-bit key)
if (false && window.crypto) {
var key = window.crypto.getRandomValues(new Uint8Array(20));
return Promise.resolve(key);
} else {
return new Promise(function (resolve, reject) {
window.forge.random.getBytes(20, function (err, bytes) {
if (err) {
reject(err);
return;
}
resolve(Unibabel.binaryStringToBuffer(bytes));
});
});
}
}
// Text-encode the key as base32 (in the style of Google Authenticator - same as Facebook, Microsoft, etc)
function encodeGoogleAuthKey(bin) {
// 32 ascii characters without trailing '='s
var base32 = Unibabel.bufferToBase32(bin).replace(/=/g, '');
// lowercase with a space every 4 characters
var key = base32.toLowerCase().replace(/(\w{4})/g, "$1 ").trim();
return key;
}
function generateGoogleAuthKey() {
return generateOtpKey().then(encodeGoogleAuthKey);
}
// Binary-decode the key from base32 (Google Authenticator, FB, M$, etc)
function decodeGoogleAuthKey(key) {
// decode base32 google auth key to binary
var unformatted = key.replace(/\W+/g, '').toUpperCase();
var bin = Unibabel.base32ToBuffer(unformatted);
return bin;
}
// Generate a Google Auth Token
function generateGoogleAuthToken(key) {
var bin = decodeGoogleAuthKey(key);
return totp.gen(bin);
}
// Verify a Google Auth Token
function verifyGoogleAuthToken(key, token) {
var bin = decodeGoogleAuthKey(key);
token = token.replace(/\W+/g, '');
// window is +/- 1 period of 30 seconds
return totp.verify(token, bin, { window: 1, time: 30 });
}
Authenticator.generateKey = generateGoogleAuthKey;
Authenticator.generateToken = generateGoogleAuthToken;
Authenticator.verifyToken = verifyGoogleAuthToken;
}(
'undefined' !== typeof window ? (window.Authenticator = {}) : module.exports
));

66
test.html Normal file
View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
<head>
<title>BOTP Test</title>
<meta charset="UTF-8">
</head>
<body>
<h1>Authenticator Test</h1>
<div>
Test with the <a href="https://www.authy.com/personal/mobile/" target="_blank">Authy App</a>.
</div>
<br />
<div>
<img alt="qrcode" class="js-qrcode" width="166" height="166" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" />
<p>20-byte (160-bit) Base32 Key:</p>
<h3 class="js-key">xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx</h3>
</div>
<br />
<div>
<label>Company Name:</label>
<input class="js-company-name" type="text" placeholder="Company Name" />
<label>User Account:</label>
<input class="js-user-account" type="text" placeholder="User Account" />
<button class="js-generate">Regenerate</button>
</div>
<br />
<div>
<label>Verification Token:</label>
<input class="js-token" type="text" placeholder="ex: 000 000" />
<button class="js-verify">Verify</button>
</div>
<!-- base32 conversion (and binary string for forge) -->
<script src="bower_components/unibabel/index.js"></script>
<script src="bower_components/unibabel/unibabel.hex.js"></script>
<script src="bower_components/unibabel/unibabel.base32.js"></script>
<!-- forge.hmac -->
<script src="bower_components/forge/js/util.js"></script>
<script src="bower_components/forge/js/sha1.js"></script>
<script src="bower_components/forge/js/hmac.js"></script>
<!-- forge.random.getBytes -->
<script src="bower_components/forge/js/sha256.js"></script>
<script src="bower_components/forge/js/cipher.js"></script>
<script src="bower_components/forge/js/cipherModes.js"></script>
<script src="bower_components/forge/js/aes.js"></script>
<script src="bower_components/forge/js/prng.js"></script>
<script src="bower_components/forge/js/random.js"></script>
<!-- botp.totp -->
<script src="bower_components/botp/sha1-hmac.js"></script>
<script src="bower_components/botp/index.js"></script>
<!-- Authenticator -->
<script src="authenticator.js"></script>
<script src="test.js"></script>
</body>
</html>

71
test.js Normal file
View File

@ -0,0 +1,71 @@
// forgive the suckiness, but whatever
(function (exports) {
'use strict';
var key;
var Authenticator = exports.Authenticator;
var $ = function (x) {
return document.querySelector(x);
};
function generate(ke) {
Authenticator.generateKey().then(function (k) {
key = ke || k;
var companyName = $('.js-company-name').value;
var userAccount = $('.js-user-account').value;
// obviously don't use this in production, but it's not so bad for the demo
var src = 'https://www.google.com/chart?chs=166x166&chld=L|0&cht=qr&chl='
+ encodeURIComponent(
'otpauth://totp/'
+ encodeURIComponent(companyName)
+ ':'
+ encodeURIComponent(userAccount)
+ '?secret='
+ key.replace(/\s+/g, '').toUpperCase()
);
$('.js-key').innerHTML = key; // safe to inject because I created it
$('img.js-qrcode').src = src;
Authenticator.generateToken(key).then(function (token) {
console.log('token', token);
Authenticator.verifyToken(key, token).then(function (result) {
console.log('verify', result);
Authenticator.verifyToken(key, '000 000').then(function (result) {
console.log('reject', result);
});
});
});
});
}
$('.js-verify').addEventListener('click', function () {
var token = $('.js-token').value;
Authenticator.verifyToken(key, token).then(function (result) {
var msg;
if (result) {
msg = 'Correct!';
} else {
msg = 'FAIL!';
}
window.alert(msg);
});
});
$('.js-generate').addEventListener('click', function () {
generate(null);
});
$('.js-company-name').value = 'ACME Co';
$('.js-user-account').value = 'john@example.com';
generate('hxdm vjec jjws rb3h wizr 4ifu gftm xboz');
}(
'undefined' !== typeof window ? window : module.exports
));