├── .gitignore ├── index.js ├── img ├── yubikeys.jpg ├── yubikey2Text.png ├── yubikey2Text_trans.png └── yubikey2.svg ├── .travis.yml ├── .eslintrc ├── .d.ts ├── package.json ├── example ├── test.js └── testOffline.js ├── lib ├── modhex.js └── yub.js ├── test └── spec.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = require('./lib/yub.js'); 3 | -------------------------------------------------------------------------------- /img/yubikeys.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/kibikey/master/img/yubikeys.jpg -------------------------------------------------------------------------------- /img/yubikey2Text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/kibikey/master/img/yubikey2Text.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "4" 6 | - "6" 7 | -------------------------------------------------------------------------------- /img/yubikey2Text_trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/kibikey/master/img/yubikey2Text_trans.png -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "strict": 1, 4 | "quotes": [1, "single"], 5 | "curly": [1, "multi-line"], 6 | "camelcase": 0, 7 | "comma-dangle": 0, 8 | "no-console": 2, 9 | "no-use-before-define": [1, "nofunc"], 10 | "no-underscore-dangle": 0, 11 | "no-unused-vars": [1, { 12 | "argsIgnorePattern": "^_.*$", 13 | }], 14 | "semi": 1 15 | }, 16 | env: { 17 | "node": true 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /.d.ts: -------------------------------------------------------------------------------- 1 | declare module "yub" { 2 | interface YubResponse { 3 | t?: string | Date, 4 | otp: string, 5 | nonce?: any, 6 | sl?: string, 7 | status?: any, 8 | signatureVerified: boolean, 9 | nonceVerified: boolean, 10 | identity?: string, 11 | encrypted: string, 12 | encryptedHex: string, 13 | serial?: number, 14 | valid: boolean 15 | } 16 | 17 | interface YubCallback { 18 | (err: Error, response: YubResponse) 19 | } 20 | 21 | export function init(client_id: string, secret_key: string) : void; 22 | export function verify(otp: string, callback: YubCallback) : void; 23 | export function verifyOffline(otp: string, callback: YubCallback) : void; 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kibikey", 3 | "version": "0.11.1", 4 | "description": "Yubico Yubikey API Client for Node.js", 5 | "main": "index.js", 6 | "repository": "https://github.com/kibibit/yub.git", 7 | "keywords": [ 8 | "Yubikey", 9 | "Yubico", 10 | "API", 11 | "authentication", 12 | "security" 13 | ], 14 | "author": "Glynn Bird", 15 | "license": "Apache-2.0", 16 | "scripts": { 17 | "test": "mocha", 18 | "lint": "eslint ." 19 | }, 20 | "dependencies": { 21 | }, 22 | "devDependencies": { 23 | "eslint": "^3.1.1", 24 | "mocha": "^3.0.0", 25 | "nock": "^8.0.0", 26 | "pre-commit": "^1.1.3" 27 | }, 28 | "pre-commit": [ 29 | "lint", 30 | "test" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /example/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable no-console */ 3 | var yub = require('../index.js'); 4 | 5 | var key = process.argv.length - 1; 6 | var otp = process.argv[key]; 7 | 8 | var clientId = process.env.CLIENT_ID || process.env.USER; 9 | var secretKey = process.env.SECRET_KEY || process.env.PASSWORD; 10 | 11 | // initialise the yub library 12 | yub.init(clientId, secretKey); 13 | 14 | // attempt to verify the key 15 | yub.verify(otp, function(err,data) { 16 | if(err) { 17 | console.log('Error'); 18 | process.exit(-1); 19 | } 20 | console.log(data); 21 | if (data.valid) { 22 | console.log(data); 23 | process.exit(0); 24 | } else { 25 | console.log('Invalid OTP'); 26 | process.exit(-2); 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /example/testOffline.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable no-console */ 3 | var yub = require('../index.js'); 4 | 5 | var key = process.argv.length - 1; 6 | var otp = process.argv[key]; 7 | 8 | var clientId = process.env.CLIENT_ID || process.env.USER; 9 | var secretKey = process.env.SECRET_KEY || process.env.PASSWORD; 10 | 11 | // initialise the yub library 12 | yub.init(clientId, secretKey); 13 | 14 | // attempt to verify the key 15 | yub.verifyOffline(otp, function(err,data) { 16 | if(err) { 17 | console.log('Error'); 18 | process.exit(-1); 19 | } 20 | if (data.identity && data.serial) { 21 | console.log(data); 22 | process.exit(0); 23 | } else { 24 | console.log('Invalid OTP'); 25 | process.exit(-2); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /lib/modhex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // lookup table for modhex codes 3 | var trans = 'cbdefghijklnrtuv'; 4 | 5 | // convert number to 2 digit hex = 255 --> ff 6 | function toHex (n) { 7 | return ('0' + n.toString(16)).substr(-2); 8 | } 9 | 10 | // decode a modhex string to a hexadecimal string 11 | // see http://static.yubico.com/var/uploads/pdfs/YubiKey_manual-2.2.pdf 12 | function decode (src) { 13 | var b = 0; 14 | var flag = false; 15 | var dst = null; 16 | var hex = ''; 17 | var p1 = null; 18 | 19 | // convert string to hexadecimal string 20 | for (var i=0; i < src.length; i++) { 21 | p1 = trans.indexOf(src[i]); 22 | if (p1 == -1) { 23 | // if a unsupported digit is detected, return null 24 | return null; 25 | } 26 | b = (p1 == -1) ? 0 : p1; 27 | if ((flag = !flag)) { 28 | dst = b; 29 | } else { 30 | hex += toHex(dst << 4 | b); 31 | } 32 | } 33 | 34 | // return as hexadecimal number 35 | return hex; 36 | } 37 | 38 | // decode a modhex string to an integer 39 | // see http://static.yubico.com/var/uploads/pdfs/YubiKey_manual-2.2.pdf 40 | function decodeInt (src) { 41 | var d = decode(src); 42 | return (d == null) ? null : parseInt(d, 16); 43 | }; 44 | 45 | module.exports = { 46 | decode: decode, 47 | decodeInt: decodeInt 48 | }; 49 | -------------------------------------------------------------------------------- /test/spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | process.env.NODE_ENV = 'test'; 3 | var assert = require('assert'); 4 | var yub = require('../index.js'); 5 | var qs = require('querystring'); 6 | var nock = require('nock'); 7 | var clientID = '12345'; 8 | var secretKey = 'dEaDBeEf=='; 9 | 10 | describe('yub', function() { 11 | before(function(done) { 12 | yub.init(clientID, secretKey); 13 | done(); 14 | }); 15 | 16 | it('should work offline', function(done) { 17 | var otp = 'cffcccdebcntjcbuelkbidnitrgidnkhgkehbrlbhtgk'; 18 | yub.verifyOffline(otp, function(err, data) { 19 | assert.equal(err, null); 20 | assert.equal(typeof data, 'object'); 21 | assert.equal(data.status, null); 22 | assert.equal(data.signatureVerified, false); 23 | assert.equal(data.identity, otp.slice(0, 12)); 24 | assert.equal(data.valid, false); 25 | done(); 26 | }); 27 | }); 28 | 29 | it('should verify otp online', function(done) { 30 | var otp = 'cffcccdebcntbilunkhgvehfuigcljjtudrfhgikcirl'; 31 | var pathMatcher = new RegExp('/wsapi/2\\\.0/verify' + 32 | '\\\?id=' + clientID + 33 | '&nonce=[0-9a-f]+?' + 34 | '&otp=' + otp + 35 | '&h=([a-zA-Z0-9]|%3D|%2B)+'); 36 | 37 | // Fake the request 38 | nock(/https:\/\/api[2-5]?\.yubico\.com/) 39 | .get(pathMatcher) 40 | .reply(200, function(uri, _requestBody) { 41 | var query = qs.parse(uri.split('?')[1]); 42 | var out = { 43 | t: new Date().toISOString(), 44 | status: 'OK', 45 | nonce: query.nonce 46 | }; 47 | out.h = yub._calculateHmac(out, secretKey); 48 | return yub._calculateStringToHash(out).replace(/&/g, '\r\n'); 49 | }); 50 | 51 | yub.verify(otp, function(err, data) { 52 | assert.equal(err, null); 53 | assert.equal(typeof data, 'object'); 54 | assert.equal(data.status, 'OK'); 55 | assert.equal(data.identity, otp.slice(0, 12)); 56 | assert.equal(data.valid, true); 57 | done(); 58 | }); 59 | }); 60 | 61 | it('should calculate correct HMAC', function() { 62 | var otp = 'cffcccdebcntbilunkhgvehfuigcljjtudrfhgikcirl'; 63 | var expected = 'qM8ROgn0GATFwGoqqI68Nu4CJA4='; 64 | var obj = { 65 | id: clientID, 66 | nonce: 'abcdefg', 67 | otp: otp 68 | }; 69 | var actual = yub._calculateHmac(obj, secretKey); 70 | assert.equal(actual, expected); 71 | }); 72 | 73 | it('should round-robin through servers', function(done) { 74 | var otp = 'cffcccdebcntbilunkhgvehfuigcljjtudrfhgikcirl'; 75 | 76 | var received = []; 77 | var servers = yub._servers; 78 | 79 | // Go around twice to ensure round-robin works 80 | for (var i = 0; i < servers.length * 2; i++) { 81 | req(i, function(err, data) { 82 | assert(err === null); 83 | received.push(data); 84 | if (received.length === servers.length * 2) { 85 | assert(received.slice().sort().toString() === servers.concat(servers).sort().toString()); 86 | done(); 87 | } 88 | }); 89 | } 90 | 91 | function req(idx, finished) { 92 | // Fake the request 93 | nock(/https:\/\/api[2-5]?\.yubico\.com/) 94 | .get(/.*/) 95 | .reply(200, function(uri, _requestBody) { 96 | return 'host=' + this.req.headers.host; 97 | }); 98 | 99 | yub.verify(otp, function(err, data) { 100 | finished(err, data.host); 101 | }); 102 | } 103 | 104 | }); 105 | 106 | }); 107 | -------------------------------------------------------------------------------- /img/yubikey2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Yubikey logo](img/yubikey2Text_trans.png) 2 | 3 | ##### Based on [`glynnbird/yub`](https://github.com/glynnbird/yub) 4 | [![NPM](https://nodei.co/npm/yub.png)](https://nodei.co/npm/yub/) 5 | 6 | ## Introduction 7 | 8 | `kibikey` is a module to help you handle yubikeys in a secure way. 9 | The plan is to create two modules: 10 | - one for terminal use 11 | - one for web client use 12 | client id and secret will be saved automatically in the OS's Credential Vault 13 | 14 | What is Yub? It's a simple Yubico Yubikey API client that 15 | 16 | * authenticates a Yubkiey's OTP (one time password) using the Yubico API 17 | * signs the outgoing data 18 | * checks the incoming data's signature 19 | * extracts the Yubkey's unique identifier if the OTP is valid 20 | 21 | ## What is a Yubikey? 22 | 23 | A Yubikey is a USB device manufactured by [Yubico](https://www.yubico.com/products/yubikey-hardware/yubikey/) that appears 24 | to your computer as a USB keyboard. 25 | 26 | ![Yubikeys](img/yubikeys.jpg) 27 | 28 | It generates one-time passwords consisting of: 29 | 30 | * your Yubikey's unique identity 31 | * a string of characters 32 | 33 | This sequence of characters can be sent to Yubico's web service which will verify whether the string is valid or not. Your Yubikey 34 | can be used for a variety of authentication tasks. This library is designed to allow simple integration with the Yubico web service 35 | so that you can interpret your Yubikey's one-time-password in your own Node.js scripts. e.g. 36 | 37 | * allow a user to authenticate via Yubikey in your web app 38 | * create command-line scripts that only operate with a valid Yubikey 39 | * etc 40 | 41 | ## What do you need? 42 | 43 | * a [Yubikey](http://www.yubico.com/) 44 | * [node.js](http://nodejs.org/) 45 | * [npm](https://npmjs.org/) 46 | 47 | ## Installation 48 | 49 | Yub is published as an NPM module for your convenience: 50 | 51 | ``` 52 | npm install yub 53 | ``` 54 | 55 | You'll also need a Yubico API Key from here: [https://upgrade.yubico.com/getapikey/]([https://upgrade.yubico.com/getapikey/). This gives you the 56 | client_id and secret_key that must be passed to "yub.init()", see below. 57 | 58 | ## Example code 59 | 60 | ``` 61 | var yub = require('yub'); 62 | 63 | // initialise the yub library 64 | yub.init("", ""); 65 | 66 | // attempt to verify the key 67 | yub.verify("", function(err,data) { 68 | console.log(err, data) 69 | }); 70 | ``` 71 | 72 | ## What's in the 'data' returned by yub.verify? 73 | 74 | A typical 'data' return from yub.verify looks like this: 75 | 76 | ``` 77 | { 78 | t: '2013-08-31T07: 13: 27Z0111', 79 | otp: 'cccaccbtbvkwjjirhcctvdgbahdbijduldcjdurgjgfi', 80 | nonce: '50fb8a88a327b4af16e6e7bd9ec4e4e6c692f2e5', 81 | sl: '25', 82 | status: 'OK', 83 | signatureVerified: true, 84 | nonceVerified: true, 85 | identity: 'cccaccbtbvkw', 86 | serial: 123456, 87 | valid: true 88 | } 89 | ``` 90 | 91 | * t - the timestamp of the interaction 92 | * otp - the supplied one-time-password 93 | * nonce - a unique piece of information provided by the client to the server 94 | * sl - the percentage of servers responding. This library only picks one (of the 5) Yubico server to authenticate with, so this value should be 20 (percent) 95 | * status - whether the supplied one-time-password was valid or not. Common return values 96 | ** 'OK' - everything's fine 97 | ** 'BAD_OTP' - invalid password supplied 98 | ** 'REPLAYED_OTP' - the password has been used before 99 | ** further return values documented here https://code.google.com/p/yubikey-val-server-php/wiki/ValidationProtocolV20 100 | * signatureVerified - whether the reply from the Yubico server was correctly signed 101 | * nonceVerified - whether the reply 'nonce' was the same as the outgoing 'nonce' 102 | * identity - the unique identifier of the Yubikey that generated the password. If you want to write software the detects the presence of a specific Yubikey (not just any Yubikey), then data.identity is your friend. 103 | * encrypted - the 32 digit encrypted portion of the OTP in 'modhex' format 104 | * encryptedHex - the encrypted portion of the OTP in hexadecimal 105 | * serial - the serial number of the Yubikey. This is derived by decoding the identity's modhex encoding. 106 | * valid - this is true if the status = 'OK', the signature is verified and the nonce is verified 107 | 108 | ## Offline verification 109 | 110 | You can also call "yub.verifyOffline", which returns the same object in the same format but 111 | without contacting the Yubico servers i.e. it simply extracts the identity of the Yubikey 112 | from the OTP without any network access. This is, of course, far less secure, but is useful 113 | for offline applications. As we cannot validate the status online, "valid" is always false in 114 | offline mode. 115 | 116 | ## Further examples 117 | 118 | In the 'example' directory is an example command-line utility (test.js) which exits with a different return code, depending 119 | on whether the supplied OTP was valid or not. This could easily be plumbed into a command-line script to only allow execution 120 | to proceed with a valid OTP. 121 | 122 | It takes the OTP as a command-line parameter i.e. type "node test.js ", insert your Yubikey and press the gold button: 123 | 124 | ``` 125 | > node test.js cccaccbtbvkwjjirhcctvdgbahdbijduldcjdurgjgfi 126 | { t: '2013-08-31T07: 13: 27Z0111', 127 | otp: 'cccaccbtbvkwjjirhcctvdgbahdbijduldcjdurgjgfi', 128 | nonce: '50fb8a88a327b4af16e6e7bd9ec4e4e6c692f2e5', 129 | sl: '25', 130 | status: 'OK', 131 | signatureVerified: true, 132 | nonceVerified: true, 133 | identity: 'cccaccbtbvkw', 134 | valid: true } 135 | ``` 136 | 137 | ## References 138 | 139 | * https://code.google.com/p/yubikey-val-server-php/wiki/GettingStartedWritingClients 140 | * https://code.google.com/p/yubikey-val-server-php/wiki/ValidationProtocolV20 141 | 142 | ## Plans for web component 143 | will be based on this codepen: https://codepen.io/neilkalman/pen/LdagNR 144 | maybe without the modal and different text. here's the icon only version: https://codepen.io/neilkalman/pen/GxeYjy 145 | 146 | ## Disclaimer 147 | 148 | This software is open-source and is a personal project, not officially endorsed by Yubico in any way. 149 | 150 | -------------------------------------------------------------------------------- /lib/yub.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var crypto = require('crypto'); 3 | var modhex = require('./modhex.js'); 4 | var https = require('https'); 5 | var qs = require('querystring'); 6 | 7 | // List of valid servers. We go through them in round-robin fashion. 8 | var servers = ['api.yubico.com', 'api2.yubico.com', 'api3.yubico.com', 'api4.yubico.com', 'api5.yubico.com']; 9 | var currentServerIdx = Math.floor(Math.random() * servers.length); 10 | 11 | // Length of random nonce generated per-request 12 | var DEFAULT_NONCE_LENGTH = 40; 13 | // For automatic retry 14 | var DEFAULT_MAX_TRIES = 3; 15 | 16 | function Yub(clientID, secretKey, options) { 17 | // store the client credentials 18 | // Apply here https://upgrade.yubico.com/getapikey/ 19 | if (!clientID || !secretKey) throw new Error('Provide a client ID & secret key to yub.init()!'); 20 | if (!options) options = {}; 21 | this.clientID = clientID; 22 | this.secretKey = secretKey; 23 | this.maxTries = options.maxTries || DEFAULT_MAX_TRIES; 24 | this.nonceLength = options.nonceLength || DEFAULT_NONCE_LENGTH; 25 | } 26 | 27 | // parse the returned date which is CR/LF delimited string with key/value pairs 28 | // separated by '=' 29 | function parse (data) { 30 | var obj = data.split('\r\n'), 31 | retval = {}, 32 | kv = []; 33 | Object.keys(obj).map(function(key) { 34 | kv = obj[key].split('=', 2); 35 | if (kv[0].length > 0) { 36 | retval[kv[0]] = kv[1]; 37 | } 38 | }); 39 | return retval; 40 | }; 41 | 42 | // extract the identity portion of the Yubikey OTP i.e. 43 | // the string with last 32 characters removed 44 | function calculateIdentity (otp) { 45 | var len = otp.length; 46 | return (len > 32) ? otp.substring(0, len - 32) : null; 47 | }; 48 | 49 | // extract the encrypted portion of the Yubikey OTP i.e. 50 | // the last 32 characters 51 | function calculateEncrypted (otp) { 52 | var len = otp.length; 53 | return (len > 32) ? otp.substring(len - 32, len) : null; 54 | }; 55 | 56 | // calculate the string that is required to be hashed i.e. 57 | // keys in alphabetical order, separated from values by '=' 58 | // and by each other by '&' (like querystrings, but without the escaping) 59 | function calculateStringToHash (obj) { 60 | return Object 61 | .keys(obj) 62 | .sort() 63 | .map(function(key) { 64 | return key + '=' + obj[key]; 65 | }) 66 | .join('&'); 67 | }; 68 | 69 | // calculate the Hmac signature of an object 70 | // according to instructions here: https://code.google.com/p/yubikey-val-server-php/wiki/ValidationProtocolV20 71 | function calculateHmac (obj, secretKey) { 72 | var str = calculateStringToHash(obj); 73 | var buf = new Buffer(secretKey, 'base64'); 74 | var hmac = crypto.createHmac('sha1', buf); 75 | return hmac.update(str).digest('base64'); 76 | }; 77 | 78 | // Verify with a random Yubico server. 79 | Yub.prototype.verifyWithYubico = function (params, callback, currentTry) { 80 | // Automatic retry logic 81 | if (!currentTry) currentTry = 1; 82 | 83 | // Choose a server in round-robin fashion. First offset is random. 84 | currentServerIdx = (currentServerIdx + 1) % servers.length; 85 | var server = servers[currentServerIdx]; 86 | var uri = 'https://' + server + '/wsapi/2.0/verify'; 87 | var fullURI = uri + '?' + qs.stringify(params); 88 | var me = this; 89 | 90 | // Send to Yubico. 91 | https.get(fullURI, function(res) { 92 | // Error handling 93 | var shouldRetry = (currentTry < me.maxTries); 94 | var badStatus = (res && res.statusCode !== 200); 95 | var serverError = (res && res.statusCode >= 500); 96 | if (shouldRetry && serverError) { 97 | // Errored, but retry 98 | return me.verifyWithYubico(params, callback, currentTry + 1); 99 | } else if (badStatus) { 100 | return callback(new Error('Bad status code: ' + res.statusCode)); 101 | } 102 | 103 | // Parse body & go 104 | var buffer = ''; 105 | res.on('data', function(chunk) { 106 | buffer += chunk; 107 | }); 108 | res.on('end', function() { 109 | var body = parse(buffer); 110 | 111 | // check whether the signature of the reply checks out 112 | var bodyh = body.h; 113 | delete body.h; 114 | var h = calculateHmac(body, me.secretKey); 115 | body.signatureVerified = (bodyh === h.replace('=', '')); 116 | 117 | // check whether the nonce is the same as the one we gave it 118 | body.nonceVerified = (params.nonce === body.nonce); 119 | 120 | // calculate the key's identity 121 | body.identity = null; 122 | body.encrypted = null; 123 | body.encryptedHex = null; 124 | body.serial = null; 125 | if (body.status === 'OK') { 126 | body.identity = calculateIdentity(params.otp); 127 | body.encrypted = calculateEncrypted(params.otp); 128 | body.encryptedHex = modhex.decode(body.encrypted); 129 | body.serial = modhex.decodeInt(body.identity); 130 | body.valid = (body.signatureVerified && body.nonceVerified); 131 | } else { 132 | body.valid = false; 133 | } 134 | 135 | callback(null, body); 136 | }); 137 | }) 138 | .on('error', callback); 139 | }; 140 | 141 | // Calculate the params to be sent to Yubico. 142 | Yub.prototype.calculateParams = function (otp, callback) { 143 | var me = this; 144 | // create a nonceLength-character random string 145 | crypto.randomBytes(me.nonceLength / 2, function (err, buf) { 146 | if (err) return callback(err); 147 | 148 | // turn it to hex 149 | var nonce = buf.toString('hex'); 150 | 151 | // create parameters to send to web service 152 | var params = { 153 | id: me.clientID, 154 | nonce: nonce, 155 | otp: otp 156 | }; 157 | 158 | // calculate sha1 signature 159 | params.h = calculateHmac(params, me.secretKey); 160 | 161 | callback(null, params); 162 | }); 163 | }; 164 | 165 | // Verify that the supplied one-time-password is valid or not 166 | // calls back with (err,data). If err is not null, then you have 167 | // an object in data to work with. 168 | Yub.prototype.verify = function (otp, callback) { 169 | var me = this; 170 | this.calculateParams(otp, function (err, params) { 171 | if (err) return callback(err); 172 | 173 | me.verifyWithYubico(params, callback); 174 | }); 175 | }; 176 | 177 | // If we have no network connectivity, we still may wish to extract the 178 | // identity from the OTP, but handy for offline applications. 179 | Yub.prototype.verifyOffline = function (otp, callback) { 180 | 181 | var identity = calculateIdentity(otp); 182 | var encrypted = calculateEncrypted(otp); 183 | 184 | var body = { 185 | t: null, 186 | otp: otp, 187 | nonce: null, 188 | sl: '0', 189 | status: null, 190 | signatureVerified: false, 191 | nonceVerified: false, 192 | identity: identity, 193 | encrypted: encrypted, 194 | encryptedHex: modhex.decode(encrypted), 195 | serial: modhex.decodeInt(identity), 196 | valid: false 197 | }; 198 | 199 | callback(null, body); 200 | }; 201 | 202 | // Export Yub. To maintain backcompat, we attach a few static properties. 203 | module.exports = Yub; 204 | 205 | var legacyInstance = null; 206 | // Don't break old methods! 207 | Yub.init = function(clientID, secretKey) { 208 | // As not to break backcompat, don't use retries with old API 209 | legacyInstance = new Yub(clientID, secretKey, {maxTries: 0}); 210 | }; 211 | Yub.verify = function(otp, callback) { 212 | if (!legacyInstance) throw new Error('init() before verifying!'); 213 | return legacyInstance.verify(otp, callback); 214 | }; 215 | Yub.verifyOffline = Yub.prototype.verifyOffline; // No actual legacy instance required 216 | Yub._calculateHmac = calculateHmac; // for tests 217 | Yub._calculateStringToHash = calculateStringToHash; // for tests 218 | Yub._servers = servers; // for tests 219 | --------------------------------------------------------------------------------