appc-security
Advanced tools
Comparing version 0.0.10 to 0.0.11
318
index.js
@@ -9,318 +9,2 @@ /** | ||
*/ | ||
var crypto = require('crypto'); | ||
var ITERATIONS = 100000; | ||
var SALT_LENGTH = 512; | ||
var IV_LENGTH = 32; | ||
var HMAC_LENGTH = 64; | ||
var KEY_LENGTH = 32; | ||
var DEBUG = false; | ||
/** | ||
* SecurityError class | ||
*/ | ||
function SecurityError(message, code, status) { | ||
this.constructor.prototype.__proto__ = Error.prototype; | ||
Error.captureStackTrace(this, this.constructor); | ||
this.name = this.constructor.name; | ||
this.message = message; | ||
this.code = code; | ||
this.status = status || 500; | ||
} | ||
/** | ||
* simple SHA1 | ||
*/ | ||
function sha1(value, encoding) { | ||
var cipher = crypto.createHash('SHA1'); | ||
cipher.update(value); | ||
return cipher.digest(encoding || 'hex'); | ||
} | ||
/** | ||
* a constant time comparision which helps prevent timing attacks | ||
* | ||
* @param {String} a value | ||
* @param {String} b value | ||
* @preturns {boolean} returns true if the same, false if not | ||
*/ | ||
function constantTimeCompare(a, b) { | ||
var sentinel; | ||
/* istanbul ignore if */ | ||
if (a.length !== b.length) { | ||
return false; | ||
} | ||
for (var c = 0; c <= (a.length - 1); c++) { | ||
sentinel |= a.charCodeAt(c) ^ b.charCodeAt(c); | ||
} | ||
return sentinel === 0; | ||
} | ||
/** | ||
* return a large random bytes using encoding (default is hex) | ||
* | ||
* @param {Number} size | ||
* @param {String} encoding, defaults to hex | ||
* @returns {String} value | ||
*/ | ||
function generateLargeRandomValue(size, encoding) { | ||
size = size || SALT_LENGTH/2; | ||
var value = new Buffer(crypto.randomBytes(size)); | ||
return value.toString(encoding || 'hex'); | ||
} | ||
/** | ||
* check the size of the encoding used 128, 192, 256 | ||
*/ | ||
function checkSize(size) { | ||
size = size || 256; | ||
if (size < 128 || size > 256) { | ||
throw new SecurityError('invalid algorithm size: '+size); | ||
} | ||
return size; | ||
} | ||
/** | ||
* encrypt a plainText Buffer | ||
* | ||
* @param {String} plainText buffer to encrypt | ||
* @param {String} shared key used for encryption | ||
* @param {String} shared pepper which is used to hash the salt | ||
* @param {String} shared hmac_key for verifying the hmac of the encrypted blob | ||
* @param {String} encoding of the returned encrypted buffer (defaults to hex) | ||
* @param {Number} size of the algorithm to used (128, 192, 256) | ||
* @returns {Object} object hash of encryption results. value property is the encryptedText | ||
*/ | ||
function encrypt (plainText, key, pepper, hmac_key, encoding, size) { | ||
// validate the size | ||
size = checkSize(size); | ||
try { | ||
var keySizeFactor = 256 / size, | ||
// create random IV | ||
iv = new Buffer(generateLargeRandomValue(IV_LENGTH/2),'hex'), | ||
// create an HMAC | ||
hmac = crypto.createHmac('SHA256', hmac_key), | ||
// create a random salt | ||
salt = generateLargeRandomValue(), | ||
// create a salt + pepper hash which hashes our salt with a known pepper | ||
saltAndPepper = sha1(salt+pepper), | ||
// use the password to create a derived key used for encrypting | ||
derivedKey = crypto.pbkdf2Sync(key, saltAndPepper, ITERATIONS, KEY_LENGTH / keySizeFactor), | ||
// create our cipher | ||
cipher = crypto.createCipheriv('AES-'+size+'-CBC', derivedKey, iv); | ||
// encrypt the plainText | ||
var encrypted = cipher.update(plainText,'utf-8','hex'); | ||
encrypted += cipher.final('hex'); | ||
// create an HMAC of the encrypted value + the saltAndPepper + the iv | ||
// we'll use this before decrypting to validate that the encrypted value | ||
// wasn't tampered with before decryption | ||
hmac.update(encrypted); | ||
hmac.update(saltAndPepper); | ||
hmac.update(iv.toString('hex')); | ||
var hmacEncoding = hmac.digest('hex'); | ||
/* istanbul ignore if */ | ||
if (DEBUG) { | ||
console.log('------- BEGIN ENCRYPTION ------\n'); | ||
console.log('size=',size+'\n'); | ||
console.log('keySizeFactor=',keySizeFactor+'\n'); | ||
console.log('hmac_key=',hmac_key+'\n'); | ||
console.log('hmacEncoding=',hmacEncoding.length,hmacEncoding+'\n'); | ||
console.log('salt=',salt.length,salt+'\n'); | ||
console.log('pepper=',pepper+'\n'); | ||
console.log('iv=',iv.toString('hex').length,iv.toString('hex')+'\n'); | ||
console.log('encrypted=',encrypted.length,encrypted+'\n'); | ||
console.log('saltAndPepper=',saltAndPepper.length,saltAndPepper+'\n'); | ||
console.log('------- END ENCRYPTION ------\n'); | ||
} | ||
// our encrypted string in hex format | ||
// these are all safe to be plain text since we used | ||
// shared keys and salt + pepper which only the two parties will know | ||
var encryptedText = hmacEncoding + salt + iv.toString('hex') + encrypted; | ||
if (encoding && encoding!=='hex') { | ||
// if we need to return a different encoding than hex, encode it | ||
encryptedText = new Buffer(encryptedText,'hex').toString(encoding); | ||
} | ||
return { | ||
value: encryptedText, | ||
derivedKey: derivedKey, | ||
saltAndPepper: saltAndPepper, | ||
salt: salt, | ||
iv: iv | ||
}; | ||
} | ||
catch (E) { | ||
throw new SecurityError('encryption failed'); | ||
} | ||
} | ||
/** | ||
* decrypt an encrypted blob | ||
* | ||
* @param {String} encrypted string | ||
* @param {String} shared key used for encryption | ||
* @param {String} shared pepper which is used to hash the salt | ||
* @param {String} shared hmac_key for verifying the hmac of the encrypted blob | ||
* @param {String} encoding of the encrypted buffer (defaults to hex) | ||
* @param {Number} size of the algorithm used (128, 192, 256) | ||
* @returns {String} decrypted blob | ||
*/ | ||
function decrypt (encrypted, key, pepper, hmac_key, encoding, size) { | ||
// validate the size | ||
size = checkSize(size); | ||
// if this is encoded, we first need to decode it back to hex | ||
if (encoding && encoding!=='hex') { | ||
encrypted = new Buffer(encrypted,encoding); | ||
encrypted = encrypted.toString('hex'); | ||
} | ||
if (encrypted.length <= HMAC_LENGTH + SALT_LENGTH + IV_LENGTH) { | ||
throw new SecurityError("invalid encrypted data"); | ||
} | ||
try { | ||
var keySizeFactor = 256 / size, | ||
hmacValue = encrypted.substring(0, HMAC_LENGTH), | ||
salt = encrypted.substring(HMAC_LENGTH, HMAC_LENGTH + SALT_LENGTH), | ||
iv = encrypted.substring(SALT_LENGTH + HMAC_LENGTH, SALT_LENGTH + HMAC_LENGTH + IV_LENGTH), | ||
encrypted = encrypted.substring(SALT_LENGTH + HMAC_LENGTH + IV_LENGTH), | ||
hmac = crypto.createHmac('SHA256', hmac_key), | ||
saltAndPepper = sha1(salt+pepper); | ||
// we first need to re-create our HMAC and make sure that the encrypted blob is the | ||
// same as the encryption routine created and that it hasn't been tampered with | ||
hmac.update(encrypted); | ||
hmac.update(saltAndPepper); | ||
hmac.update(iv); | ||
hmac = hmac.digest('hex'); | ||
/* istanbul ignore if */ | ||
if (DEBUG) { | ||
console.log('------- BEGIN DECRYPTION ------\n'); | ||
console.log('size=',size+'\n'); | ||
console.log('keySizeFactor=',keySizeFactor+'\n'); | ||
console.log('hmac_key=',hmac_key+'\n'); | ||
console.log('hmac=',hmacValue+'\n'); | ||
console.log('salt=',salt+'\n'); | ||
console.log('pepper=',pepper+'\n'); | ||
console.log('iv=',iv.length,iv+'\n'); | ||
console.log('encrypted=',encrypted+'\n'); | ||
console.log('saltAndPepper=',saltAndPepper+'\n'); | ||
console.log('hmac result=',hmac+'\n'); | ||
} | ||
// test for constant time comparison which both checks the values and ensures that | ||
// we don't have a timing attack. this validates that the encrypted value hasn't been | ||
// modified from what we encrypted | ||
if (!constantTimeCompare(hmac,hmacValue)) { | ||
throw new SecurityError('encrypted data has been tampered with'); | ||
} | ||
// create our derived key | ||
var derivedKey = crypto.pbkdf2Sync(key, saltAndPepper, ITERATIONS, KEY_LENGTH/keySizeFactor), | ||
ivKey = new Buffer(iv,'hex'), | ||
// create our decryption cipher | ||
cipher = crypto.createDecipheriv('AES-'+size+'-CBC', derivedKey, ivKey); | ||
/* istanbul ignore if */ | ||
if (DEBUG) { | ||
console.log('derived key=',derivedKey.toString('hex')+'\n'); | ||
console.log('------- END DECRYPTION ------\n'); | ||
} | ||
var decrypted = cipher.update(encrypted,'hex','utf-8'); | ||
return decrypted + cipher.final('utf-8'); | ||
} | ||
catch (E) { | ||
if (E instanceof SecurityError) { | ||
throw E; | ||
} | ||
else { | ||
throw new SecurityError('decryption failed'); | ||
} | ||
} | ||
} | ||
// export our APIs | ||
exports.sha1 = sha1; | ||
exports.generateLargeRandomValue = generateLargeRandomValue; | ||
exports.encrypt = encrypt; | ||
exports.decrypt = decrypt; | ||
Object.defineProperty(exports,'HMAC_LENGTH',{ | ||
set: function(value) { | ||
HMAC_LENGTH = value; | ||
}, | ||
get: function() { | ||
return HMAC_LENGTH; | ||
} | ||
}); | ||
Object.defineProperty(exports,'ITERATIONS',{ | ||
set: function(value) { | ||
ITERATIONS = value; | ||
}, | ||
get: function() { | ||
return ITERATIONS; | ||
} | ||
}); | ||
Object.defineProperty(exports,'SALT_LENGTH',{ | ||
set: function(value) { | ||
SALT_LENGTH = value; | ||
}, | ||
get: function() { | ||
return SALT_LENGTH; | ||
} | ||
}); | ||
Object.defineProperty(exports,'IV_LENGTH',{ | ||
set: function(value) { | ||
IV_LENGTH = value; | ||
}, | ||
get: function() { | ||
return IV_LENGTH; | ||
} | ||
}); | ||
Object.defineProperty(exports,'KEY_LENGTH',{ | ||
set: function(value) { | ||
KEY_LENGTH = value; | ||
}, | ||
get: function() { | ||
return KEY_LENGTH; | ||
} | ||
}); | ||
Object.defineProperty(exports,'DEBUG',{ | ||
set: function(value) { | ||
DEBUG = value; | ||
}, | ||
get: function() { | ||
return DEBUG; | ||
} | ||
}); | ||
// for testing | ||
/* istanbul ignore if */ | ||
if (module.id === ".") { | ||
var key = 'key', | ||
pepper = 'pepper', | ||
hmacKey = 'hmacKey'; | ||
DEBUG = true; | ||
ITERATIONS = 100; | ||
var keysize = 256; | ||
var result = encrypt('ABC',key,pepper,hmacKey,'base64',keysize); | ||
var result2 = decrypt(result,key,pepper,hmacKey,'base64',keysize); | ||
console.log('static NSString *ENC = @"'+result+'";\n'); | ||
console.log('static NSString *KEY = @"'+key+'";\n'); | ||
console.log('static NSString *PEPPER = @"'+pepper+'";\n'); | ||
console.log('static NSString *HMACKEY = @"'+hmacKey+'";\n'); | ||
console.log('OK ?',result2==='ABC'); | ||
} | ||
module.exports = require('./lib'); |
{ | ||
"name": "appc-security", | ||
"version": "0.0.10", | ||
"version": "0.0.11", | ||
"description": "Appcelerator Security Best Practices Library for Node", | ||
@@ -25,14 +25,19 @@ "main": "index.js", | ||
"devDependencies": { | ||
"express": "^4.13.3", | ||
"grunt": "^0.4.5", | ||
"grunt-cli": "^0.1.13", | ||
"grunt-appc-coverage": "^0.1.5", | ||
"grunt-appc-js": "*", | ||
"grunt-bump": "^0.3.0", | ||
"grunt-appc-coverage": "^0.1.5", | ||
"grunt-kahvesi": "git://github.com/appcelerator-modules/grunt-kahvesi.git", | ||
"grunt-cli": "^0.1.13", | ||
"grunt-kahvesi": "appcelerator-modules/grunt-kahvesi", | ||
"grunt-mocha-test": "^0.12.7", | ||
"load-grunt-tasks": "^1.0.0", | ||
"mocha": "^2.1.0", | ||
"request": "^2.61.0", | ||
"should": "^4.6.1", | ||
"load-grunt-tasks": "^1.0.0", | ||
"time-grunt": "^1.0.0" | ||
}, | ||
"dependencies": { | ||
"jsonwebtoken": "^5.0.5" | ||
} | ||
} |
@@ -249,2 +249,68 @@ var should = require('should'), // jshint ignore:line | ||
it('should support encoding session token for API key', function () { | ||
var token = lib.createSessionTokenFromAPIKey('123', '456', 10000, {foo:'bar'}); | ||
should(token).be.a.string; | ||
var result = lib.verifySessionTokenForAPIKey(token, '456'); | ||
should(result).be.an.object; | ||
should(result).have.property('apikey', '123'); | ||
should(result).have.property('iss', 'https://security.appcelerator.com'); | ||
should(result).have.property('sub', 'apikey'); | ||
should(result).have.property('headers'); | ||
should(result).have.property('iat'); | ||
result.iat.should.be.approximately(Math.floor(Date.now() / 1000), 2); | ||
should(result).have.property('exp'); | ||
result.exp.should.be.approximately(Math.floor((Date.now() + 10000) / 1000), 2); | ||
}); | ||
it('should fail encoding session token for API key with invalid secret', function () { | ||
var token = lib.createSessionTokenFromAPIKey('123', '456', 10000, {foo:'bar'}); | ||
should(token).be.a.string; | ||
(function () { | ||
lib.verifySessionTokenForAPIKey(token, '123'); | ||
}).should.throw('invalid signature'); | ||
}); | ||
it('should fail encoding session token for API key with expired token', function (done) { | ||
var token = lib.createSessionTokenFromAPIKey('123', '456', 1000, {foo:'bar'}); | ||
should(token).be.a.string; | ||
setTimeout(function () { | ||
(function () { | ||
lib.verifySessionTokenForAPIKey(token, '456'); | ||
}).should.throw('token expired'); | ||
done(); | ||
}, 1200); | ||
}); | ||
it('should fail encoding session token for API key with expired token and expiredAt property', function (done) { | ||
var token = lib.createSessionTokenFromAPIKey('123', '456', 1000, {foo:'bar'}); | ||
should(token).be.a.string; | ||
setTimeout(function () { | ||
try { | ||
lib.verifySessionTokenForAPIKey(token, '456'); | ||
} | ||
catch (E) { | ||
should(E).have.property('expiredAt'); | ||
should(E.expiredAt).be.a.Date; | ||
E.expiredAt.getTime().should.be.approximately(Math.floor((Date.now() + 1000)), 5000); | ||
} | ||
done(); | ||
}, 1200); | ||
}); | ||
it('should encoding session token for API key with utf8 encoding', function () { | ||
var token = lib.createSessionTokenFromAPIKey('123', '456', 1000, {foo:'bar'}, 'utf8'); | ||
should(token).be.a.string; | ||
var encoded = lib.verifySessionTokenForAPIKey(token, '456', 'utf8'); | ||
should(encoded).be.an.object; | ||
should(encoded).have.property('apikey', '123'); | ||
should(encoded).have.property('iss', 'https://security.appcelerator.com'); | ||
}); | ||
it('should allow setting various parameters', function () { | ||
['HMAC_LENGTH', 'ITERATIONS', 'SALT_LENGTH', 'IV_LENGTH', 'KEY_LENGTH', 'DEBUG'].forEach(function (k) { | ||
var value = lib[k]; | ||
lib[k] = value; | ||
should(lib[k]).be.equal(value); | ||
}); | ||
}); | ||
}); |
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
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
47610
12
1261
1
13
0
1
+ Addedjsonwebtoken@^5.0.5
+ Addedbuffer-equal-constant-time@1.0.1(transitive)
+ Addedecdsa-sig-formatter@1.0.11(transitive)
+ Addedjsonwebtoken@5.7.0(transitive)
+ Addedjwa@1.4.1(transitive)
+ Addedjws@3.2.2(transitive)
+ Addedms@0.7.3(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedxtend@4.0.2(transitive)