Comparing version 0.10.14 to 0.11.0
@@ -1,2 +0,3 @@ | ||
'use strict'; | ||
/* eslint-disable no-console */ | ||
var yub = require('../index.js'); | ||
@@ -7,4 +8,7 @@ | ||
var clientId = process.env.CLIENT_ID || process.env.USER; | ||
var secretKey = process.env.SECRET_KEY || process.env.PASSWORD; | ||
// initialise the yub library | ||
yub.init(process.env.USERNAME, process.env.PASSWORD); | ||
yub.init(clientId, secretKey); | ||
@@ -14,3 +18,3 @@ // attempt to verify the key | ||
if(err) { | ||
console.log("Error"); | ||
console.log('Error'); | ||
process.exit(-1); | ||
@@ -23,5 +27,5 @@ } | ||
} else { | ||
console.log("Invalid OTP"); | ||
console.log('Invalid OTP'); | ||
process.exit(-2); | ||
} | ||
}); |
@@ -1,2 +0,3 @@ | ||
'use strict'; | ||
/* eslint-disable no-console */ | ||
var yub = require('../index.js'); | ||
@@ -7,4 +8,7 @@ | ||
var clientId = process.env.CLIENT_ID || process.env.USER; | ||
var secretKey = process.env.SECRET_KEY || process.env.PASSWORD; | ||
// initialise the yub library | ||
yub.init(process.env.USERNAME, process.env.PASSWORD); | ||
yub.init(clientId, secretKey); | ||
@@ -14,3 +18,3 @@ // attempt to verify the key | ||
if(err) { | ||
console.log("Error"); | ||
console.log('Error'); | ||
process.exit(-1); | ||
@@ -22,5 +26,5 @@ } | ||
} else { | ||
console.log("Invalid OTP"); | ||
console.log('Invalid OTP'); | ||
process.exit(-2); | ||
} | ||
}); |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
module.exports = require('./lib/yub.js'); |
@@ -0,7 +1,8 @@ | ||
'use strict'; | ||
// lookup table for modhex codes | ||
var trans = "cbdefghijklnrtuv"; | ||
var trans = 'cbdefghijklnrtuv'; | ||
// convert number to 2 digit hex = 255 --> ff | ||
var toHex = function (n) { | ||
return ("0" + n.toString(16)).substr(-2); | ||
function toHex (n) { | ||
return ('0' + n.toString(16)).substr(-2); | ||
} | ||
@@ -11,11 +12,11 @@ | ||
// see http://static.yubico.com/var/uploads/pdfs/YubiKey_manual-2.2.pdf | ||
var decode = function (src) { | ||
function decode (src) { | ||
var b = 0; | ||
var flag = false; | ||
var dst = null; | ||
var hex = ""; | ||
var hex = ''; | ||
var p1 = null; | ||
// convert string to hexadecimal string | ||
for (i=0; i < src.length; i++) { | ||
for (var i=0; i < src.length; i++) { | ||
p1 = trans.indexOf(src[i]); | ||
@@ -33,3 +34,3 @@ if (p1 == -1) { | ||
} | ||
// return as hexadecimal number | ||
@@ -41,6 +42,6 @@ return hex; | ||
// see http://static.yubico.com/var/uploads/pdfs/YubiKey_manual-2.2.pdf | ||
var decodeInt = function (src) { | ||
function decodeInt (src) { | ||
var d = decode(src); | ||
return (d == null) ? null : parseInt(d, 16); | ||
} | ||
}; | ||
@@ -50,2 +51,2 @@ module.exports = { | ||
decodeInt: decodeInt | ||
} | ||
}; |
239
lib/yub.js
@@ -0,28 +1,36 @@ | ||
'use strict'; | ||
var crypto = require('crypto'); | ||
var request = require('request'); | ||
var modhex = require('./modhex.js'); | ||
var https = require('https'); | ||
var qs = require('querystring'); | ||
var NODE_ENV = process.env.NODE_ENV; | ||
// stored client credentials | ||
var clientID = null; | ||
var secretKey = null; | ||
// List of valid servers. We go through them in round-robin fashion. | ||
var servers = ['api.yubico.com', 'api2.yubico.com', 'api3.yubico.com', 'api4.yubico.com', 'api5.yubico.com']; | ||
var currentServerIdx = Math.floor(Math.random() * servers.length); | ||
// list of valid servers | ||
var servers = ["api.yubico.com", "api2.yubico.com", "api3.yubico.com", "api4.yubico.com", "api5.yubico.com"]; | ||
var nonceLength = 40; | ||
// Length of random nonce generated per-request | ||
var DEFAULT_NONCE_LENGTH = 40; | ||
// For automatic retry | ||
var DEFAULT_MAX_TRIES = 3; | ||
// store the client credentials | ||
// Apply here https://upgrade.yubico.com/getapikey/ | ||
var init = function (client_id, secretkey) { | ||
clientID = client_id; | ||
secretKey = secretkey; | ||
}; | ||
function Yub(clientID, secretKey, options) { | ||
// store the client credentials | ||
// Apply here https://upgrade.yubico.com/getapikey/ | ||
if (!clientID || !secretKey) throw new Error('Provide a client ID & secret key to yub.init()!'); | ||
if (!options) options = {}; | ||
this.clientID = clientID; | ||
this.secretKey = secretKey; | ||
this.maxTries = options.maxTries || DEFAULT_MAX_TRIES; | ||
this.nonceLength = options.nonceLength || DEFAULT_NONCE_LENGTH; | ||
} | ||
// parse the returned date which is CR/LF delimited string with key/value pairs | ||
// separated by '=' | ||
var parse = function (data) { | ||
var obj = data.split("\r\n"), | ||
function parse (data) { | ||
var obj = data.split('\r\n'), | ||
retval = {}, | ||
kv = []; | ||
Object.keys(obj).map(function(key) { | ||
kv = obj[key].split("=", 2); | ||
kv = obj[key].split('=', 2); | ||
if (kv[0].length > 0) { | ||
@@ -37,3 +45,3 @@ retval[kv[0]] = kv[1]; | ||
// the string with last 32 characters removed | ||
var calculateIdentity = function (otp) { | ||
function calculateIdentity (otp) { | ||
var len = otp.length; | ||
@@ -44,4 +52,4 @@ return (len > 32) ? otp.substring(0, len - 32) : null; | ||
// extract the encrypted portion of the Yubikey OTP i.e. | ||
// the last 32 characters | ||
var calculateEncrypted = function (otp) { | ||
// the last 32 characters | ||
function calculateEncrypted (otp) { | ||
var len = otp.length; | ||
@@ -52,5 +60,5 @@ return (len > 32) ? otp.substring(len - 32, len) : null; | ||
// calculate the string that is required to be hashed i.e. | ||
// keys in alphabetical order, separated from values by '=' | ||
// keys in alphabetical order, separated from values by '=' | ||
// and by each other by '&' (like querystrings, but without the escaping) | ||
var calculateStringToHash = function (obj) { | ||
function calculateStringToHash (obj) { | ||
return Object | ||
@@ -67,5 +75,5 @@ .keys(obj) | ||
// according to instructions here: https://code.google.com/p/yubikey-val-server-php/wiki/ValidationProtocolV20 | ||
var calculateHmac = function (obj) { | ||
function calculateHmac (obj, secretKey) { | ||
var str = calculateStringToHash(obj); | ||
var buf = new Buffer(secretKey, 'base64').toString('binary'); | ||
var buf = new Buffer(secretKey, 'base64'); | ||
var hmac = crypto.createHmac('sha1', buf); | ||
@@ -75,49 +83,44 @@ return hmac.update(str).digest('base64'); | ||
// verify that the supplied one-time-password is valid or not | ||
// calls back with (err,data). If err is not null, then you have | ||
// an object in data to work with | ||
var verify = function (otp, callback) { | ||
// create 40 character random string | ||
crypto.randomBytes(nonceLength, function (err, buf) { | ||
// turn it to hex | ||
var nonce = buf.toString('hex').slice(0, 40); | ||
// create parameters to send to web service | ||
var params = { | ||
id: clientID, | ||
nonce: nonce, | ||
otp: otp | ||
}; | ||
// calculate sha1 signature | ||
// params.h = calculateHmac(params); | ||
// calculate url | ||
var server = servers[Math.floor(Math.random() * servers.length)]; | ||
var uri = 'https://' + server + '/wsapi/2.0/verify'; | ||
// Verify with a random Yubico server. | ||
Yub.prototype.verifyWithYubico = function (params, callback, currentTry) { | ||
// Automatic retry logic | ||
if (!currentTry) currentTry = 1; | ||
// to https request | ||
request({ uri: uri, qs: params}, function (err, res, body) { | ||
// error | ||
if (err) { | ||
return callback(err, null); | ||
} | ||
if (res.statusCode !== 200) { | ||
return callback(true, null); | ||
} | ||
// parse the return value | ||
body = parse(body); | ||
// Choose a server in round-robin fashion. First offset is random. | ||
var server = servers[currentServerIdx++]; | ||
if (NODE_ENV === 'test') server = servers[0]; // For mocking with nock | ||
var uri = 'https://' + server + '/wsapi/2.0/verify'; | ||
var fullURI = uri + '?' + qs.stringify(params); | ||
var me = this; | ||
// check whether the signature of the reply | ||
var bodyh = body.h; | ||
// Send to Yubico. | ||
https.get(fullURI, function(res) { | ||
// Error handling | ||
var shouldRetry = (currentTry < me.maxTries); | ||
var badStatus = (res && res.statusCode !== 200); | ||
var serverError = (res && res.statusCode >= 500); | ||
if (shouldRetry && serverError) { | ||
// Errored, but retry | ||
return me.verifyWithYubico(params, callback, currentTry + 1); | ||
} else if (badStatus) { | ||
return callback(new Error('Bad status code: ' + res.statusCode)); | ||
} | ||
// Parse body & go | ||
var buffer = ''; | ||
res.on('data', function(chunk) { | ||
buffer += chunk; | ||
}); | ||
res.on('end', function() { | ||
var body = parse(buffer); | ||
// check whether the signature of the reply checks out | ||
var bodyh = body.h; | ||
delete body.h; | ||
var h = calculateHmac(body); | ||
body.signatureVerified = (bodyh === h.replace("=", '')); | ||
var h = calculateHmac(body, me.secretKey); | ||
body.signatureVerified = (bodyh === h.replace('=', '')); | ||
// check whether the nonce is the same as the one we gave it | ||
body.nonceVerified = (nonce === body.nonce); | ||
body.nonceVerified = (params.nonce === body.nonce); | ||
// calculate the key's identity | ||
@@ -128,48 +131,94 @@ body.identity = null; | ||
body.serial = null; | ||
if (typeof body.status != "undefined" && body.status === "OK") { | ||
body.identity = calculateIdentity(otp); | ||
body.encrypted = calculateEncrypted(otp); | ||
if (body.status === 'OK') { | ||
body.identity = calculateIdentity(params.otp); | ||
body.encrypted = calculateEncrypted(params.otp); | ||
body.encryptedHex = modhex.decode(body.encrypted); | ||
body.serial = modhex.decodeInt(body.identity); | ||
body.valid = (body.signatureVerified && body.nonceVerified) | ||
body.valid = (body.signatureVerified && body.nonceVerified); | ||
} else { | ||
body.valid = false; | ||
} | ||
callback(null, body); | ||
}); | ||
}) | ||
.on('error', callback); | ||
}; | ||
}); | ||
// Calculate the params to be sent to Yubico. | ||
Yub.prototype.calculateParams = function (otp, callback) { | ||
var me = this; | ||
// create a nonceLength-character random string | ||
crypto.randomBytes(me.nonceLength / 2, function (err, buf) { | ||
if (err) return callback(err); | ||
// turn it to hex | ||
var nonce = buf.toString('hex'); | ||
// create parameters to send to web service | ||
var params = { | ||
id: me.clientID, | ||
nonce: nonce, | ||
otp: otp | ||
}; | ||
// calculate sha1 signature | ||
params.h = calculateHmac(params, me.secretKey); | ||
callback(null, params); | ||
}); | ||
}; | ||
// if we have no network connectivity, we still may wish to extract the | ||
// identity from the OTP, but handy for offline applications | ||
var verifyOffline = function (otp, callback) { | ||
// Verify that the supplied one-time-password is valid or not | ||
// calls back with (err,data). If err is not null, then you have | ||
// an object in data to work with. | ||
Yub.prototype.verify = function (otp, callback) { | ||
var me = this; | ||
this.calculateParams(otp, function (err, params) { | ||
if (err) return callback(err); | ||
me.verifyWithYubico(params, callback); | ||
}); | ||
}; | ||
// If we have no network connectivity, we still may wish to extract the | ||
// identity from the OTP, but handy for offline applications. | ||
Yub.prototype.verifyOffline = function (otp, callback) { | ||
var identity = calculateIdentity(otp); | ||
var encrypted = calculateEncrypted(otp); | ||
var body = { | ||
t: null, | ||
otp: otp, | ||
nonce: null, | ||
sl: '0', | ||
status: null, | ||
signatureVerified: false, | ||
nonceVerified: false, | ||
identity: identity, | ||
encrypted: encrypted, | ||
encryptedHex: modhex.decode(encrypted), | ||
serial: modhex.decodeInt(identity), | ||
valid: false | ||
}; | ||
t: null, | ||
otp: otp, | ||
nonce: null, | ||
sl: '0', | ||
status: null, | ||
signatureVerified: false, | ||
nonceVerified: false, | ||
identity: identity, | ||
encrypted: encrypted, | ||
encryptedHex: modhex.decode(encrypted), | ||
serial: modhex.decodeInt(identity), | ||
valid: false | ||
}; | ||
callback(null, body); | ||
}; | ||
// Export Yub. To maintain backcompat, we attach a few static properties. | ||
module.exports = Yub; | ||
module.exports = { | ||
init: init, | ||
verify: verify, | ||
verifyOffline: verifyOffline | ||
var legacyInstance = null; | ||
// Don't break old methods! | ||
Yub.init = function(clientID, secretKey) { | ||
// As not to break backcompat, don't use retries with old API | ||
legacyInstance = new Yub(clientID, secretKey, {maxTries: 0}); | ||
}; | ||
Yub.verify = function(otp, callback) { | ||
if (!legacyInstance) throw new Error('init() before verifying!'); | ||
return legacyInstance.verify(otp, callback); | ||
}; | ||
Yub.verifyOffline = Yub.prototype.verifyOffline; // No actual legacy instance required | ||
Yub._calculateHmac = calculateHmac; // for tests | ||
Yub._calculateStringToHash = calculateStringToHash; // for tests |
{ | ||
"name": "yub", | ||
"version": "0.10.14", | ||
"version": "0.11.0", | ||
"description": "Yubico Yubikey API Client for Node.js", | ||
@@ -17,10 +17,17 @@ "main": "index.js", | ||
"scripts": { | ||
"test": "mocha" | ||
"test": "mocha", | ||
"lint": "eslint ." | ||
}, | ||
"dependencies": { | ||
"request": "2.73.0" | ||
}, | ||
"devDependencies": { | ||
"mocha": "^2.5.3" | ||
} | ||
"eslint": "^3.1.1", | ||
"mocha": "^3.0.0", | ||
"nock": "^8.0.0", | ||
"pre-commit": "^1.1.3" | ||
}, | ||
"pre-commit": [ | ||
"lint", | ||
"test" | ||
] | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
59040
0
13
368
4
11
2
- Removedrequest@2.73.0
- Removedansi-regex@2.1.1(transitive)
- Removedansi-styles@2.2.1(transitive)
- Removedasn1@0.2.6(transitive)
- Removedassert-plus@0.2.01.0.0(transitive)
- Removedasync@2.6.4(transitive)
- Removedaws-sign2@0.6.0(transitive)
- Removedaws4@1.13.2(transitive)
- Removedbcrypt-pbkdf@1.0.2(transitive)
- Removedbl@1.1.2(transitive)
- Removedboom@2.10.1(transitive)
- Removedcaseless@0.11.0(transitive)
- Removedchalk@1.1.3(transitive)
- Removedcombined-stream@1.0.8(transitive)
- Removedcommander@2.20.3(transitive)
- Removedcore-util-is@1.0.21.0.3(transitive)
- Removedcryptiles@2.0.5(transitive)
- Removeddashdash@1.14.1(transitive)
- Removeddelayed-stream@1.0.0(transitive)
- Removedecc-jsbn@0.1.2(transitive)
- Removedescape-string-regexp@1.0.5(transitive)
- Removedextend@3.0.2(transitive)
- Removedextsprintf@1.3.0(transitive)
- Removedforever-agent@0.6.1(transitive)
- Removedform-data@1.0.1(transitive)
- Removedgenerate-function@2.3.1(transitive)
- Removedgenerate-object-property@1.2.0(transitive)
- Removedgetpass@0.1.7(transitive)
- Removedhar-validator@2.0.6(transitive)
- Removedhas-ansi@2.0.0(transitive)
- Removedhawk@3.1.3(transitive)
- Removedhoek@2.16.3(transitive)
- Removedhttp-signature@1.1.1(transitive)
- Removedinherits@2.0.4(transitive)
- Removedis-my-ip-valid@1.0.1(transitive)
- Removedis-my-json-valid@2.20.6(transitive)
- Removedis-property@1.0.2(transitive)
- Removedis-typedarray@1.0.0(transitive)
- Removedisarray@1.0.0(transitive)
- Removedisstream@0.1.2(transitive)
- Removedjsbn@0.1.1(transitive)
- Removedjson-schema@0.4.0(transitive)
- Removedjson-stringify-safe@5.0.1(transitive)
- Removedjsonpointer@5.0.1(transitive)
- Removedjsprim@1.4.2(transitive)
- Removedlodash@4.17.21(transitive)
- Removedmime-db@1.52.0(transitive)
- Removedmime-types@2.1.35(transitive)
- Removednode-uuid@1.4.8(transitive)
- Removedoauth-sign@0.8.2(transitive)
- Removedpinkie@2.0.4(transitive)
- Removedpinkie-promise@2.0.1(transitive)
- Removedprocess-nextick-args@1.0.7(transitive)
- Removedqs@6.2.4(transitive)
- Removedreadable-stream@2.0.6(transitive)
- Removedrequest@2.73.0(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsntp@1.0.9(transitive)
- Removedsshpk@1.18.0(transitive)
- Removedstring_decoder@0.10.31(transitive)
- Removedstringstream@0.0.6(transitive)
- Removedstrip-ansi@3.0.1(transitive)
- Removedsupports-color@2.0.0(transitive)
- Removedtough-cookie@2.2.2(transitive)
- Removedtunnel-agent@0.4.3(transitive)
- Removedtweetnacl@0.14.5(transitive)
- Removedutil-deprecate@1.0.2(transitive)
- Removedverror@1.10.0(transitive)
- Removedxtend@4.0.2(transitive)