├── .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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 
2 |
3 | ##### Based on [`glynnbird/yub`](https://github.com/glynnbird/yub)
4 | [](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 | 
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 |
--------------------------------------------------------------------------------