diff --git a/gulpfile.js b/gulpfile.js index d3cae0fd6e..c688750d47 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -17,7 +17,8 @@ var gulp = require('gulp'), settings = require('./settings.json'), project = require('./package.json'), jshint = require('gulp-jshint'), - _ = require('lodash'); + _ = require('lodash'), + webpack = require('webpack-stream'); var paths = {}; paths.dist = './dist/'; @@ -42,7 +43,7 @@ gulp.task('lint', function () { gulp.task('build', function (cb) { return runSequence( 'clean', - ['lib', 'less', 'settings', 'lint'], + ['lib', 'webpack', 'less', 'settings', 'lint'], cb); }); @@ -142,10 +143,6 @@ gulp.task('lib', ['clean:lib'], function () { src: paths.npmDir + 'angular-messages/*messages*.js', dest: paths.libDir + 'angular-messages' }, - { - src: [paths.npmDir + 'sjcl/core/cbc.js', paths.npmDir + 'sjcl/core/bitArray.js', paths.npmDir + 'sjcl/sjcl.js'], - dest: paths.libDir + 'sjcl' - }, { src: paths.npmDir + 'ngstorage/*.js', dest: paths.libDir + 'ngstorage' @@ -178,6 +175,33 @@ gulp.task('lib', ['clean:lib'], function () { return merge(tasks); }); +gulp.task('webpack', ['webpack:forge']); + +gulp.task('webpack:forge', function () { + var forgeDir = paths.npmDir + '/node-forge/lib/'; + + return gulp.src([ + forgeDir + 'pbkdf2.js', + forgeDir + 'aes.js', + forgeDir + 'hmac.js', + forgeDir + 'sha256.js', + forgeDir + 'random.js', + forgeDir + 'forge.js' + ]).pipe(webpack({ + output: { + filename: 'forge.js', + library: 'forge', + libraryTarget: 'umd' + }, + node: { + Buffer: false, + process: false, + crypto: false, + setImmediate: false + } + })).pipe(gulp.dest(paths.libDir + 'forge')); +}); + gulp.task('settings', function () { return config() .pipe(gulp.dest(paths.webroot + 'app')); @@ -290,8 +314,6 @@ gulp.task('dist:js:app', function () { gulp.task('dist:js:lib', function () { return gulp .src([ - paths.libDir + 'sjcl/sjcl.js', - paths.libDir + 'sjcl/*.js', paths.libDir + 'angulartics/angulartics.js', paths.libDir + '**/*.js', '!' + paths.libDir + '**/*.min.js', diff --git a/package.json b/package.json index 23e654dc3a..ae5025cf01 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "jquery": "2.2.4", "font-awesome": "4.6.3", "bootstrap": "3.3.6", - "sjcl": "1.0.3", "angular": "1.5.6", "angular-resource": "1.5.6", "angular-bootstrap-npm": "0.14.3", @@ -42,6 +41,8 @@ "clipboard": "1.5.12", "ngclipboard": "1.1.1", "angulartics": "1.1.2", - "angulartics-google-analytics": "0.2.1" + "angulartics-google-analytics": "0.2.1", + "node-forge": "0.7.0", + "webpack-stream": "3.2.0" } } diff --git a/src/app/services/cryptoService.js b/src/app/services/cryptoService.js index dc9720bc77..c7b40c6991 100644 --- a/src/app/services/cryptoService.js +++ b/src/app/services/cryptoService.js @@ -4,15 +4,11 @@ angular .factory('cryptoService', function ($sessionStorage) { var _service = {}, _key, - _b64Key, - _aes, - _aesWithMac; - - sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."](); + _b64Key; _service.setKey = function (key) { _key = key; - $sessionStorage.key = sjcl.codec.base64.fromBits(key); + $sessionStorage.key = forge.util.encode64(key); }; _service.getKey = function (b64) { @@ -24,11 +20,11 @@ angular } if ($sessionStorage.key) { - _key = sjcl.codec.base64.toBits($sessionStorage.key); + _key = forge.util.decode64($sessionStorage.key); } if (b64 && b64 === true) { - _b64Key = sjcl.codec.base64.fromBits(_key); + _b64Key = forge.util.encode64(_key); return _b64Key; } @@ -37,24 +33,29 @@ angular _service.getEncKey = function (key) { key = key || _service.getKey(); - return key.slice(0, 4); + + var buffer = forge.util.createBuffer(key); + return buffer.getBytes(16); }; _service.getMacKey = function (key) { key = key || _service.getKey(); - return key.slice(4); + + var buffer = forge.util.createBuffer(key); + buffer.getBytes(16); // skip first half + return buffer.getBytes(16); }; _service.clearKey = function () { - _key = _b64Key = _aes = _aesWithMac = null; + _key = _b64Key = null; delete $sessionStorage.key; }; _service.makeKey = function (password, salt, b64) { - var key = sjcl.misc.pbkdf2(password, salt, 5000, 256, null); + var key = forge.pbkdf2(password, salt, 5000, 256 / 8, 'sha256'); if (b64 && b64 === true) { - return sjcl.codec.base64.fromBits(key); + return forge.util.encode64(key); } return key; @@ -69,24 +70,8 @@ angular throw 'Invalid parameters.'; } - var hashBits = sjcl.misc.pbkdf2(key, password, 1, 256, null); - return sjcl.codec.base64.fromBits(hashBits); - }; - - _service.getAes = function () { - if (!_aes && _service.getKey()) { - _aes = new sjcl.cipher.aes(_service.getKey()); - } - - return _aes; - }; - - _service.getAesWithMac = function () { - if (!_aesWithMac && _service.getKey()) { - _aesWithMac = new sjcl.cipher.aes(_service.getEncKey()); - } - - return _aesWithMac; + var hashBits = forge.pbkdf2(key, password, 1, 256 / 8, 'sha256'); + return forge.util.encode64(hashBits); }; _service.encrypt = function (plaintextValue, key) { @@ -103,22 +88,21 @@ angular encKey = key || _service.getKey(); } - var response = {}; - var params = { - mode: 'cbc', - iv: sjcl.random.randomWords(4, 10) - }; - - var ctJson = sjcl.encrypt(encKey, plaintextValue, params, response); - - var ct = ctJson.match(/"ct":"([^"]*)"/)[1]; - var iv = sjcl.codec.base64.fromBits(response.iv); + var buffer = forge.util.createBuffer(plaintextValue, 'utf8'); + var ivBytes = forge.random.getBytesSync(16); + var cipher = forge.cipher.createCipher('AES-CBC', encKey); + cipher.start({ iv: ivBytes }); + cipher.update(buffer); + cipher.finish(); + var iv = forge.util.encode64(ivBytes); + var ctBytes = cipher.output.getBytes(); + var ct = forge.util.encode64(ctBytes); var cipherString = iv + '|' + ct; // TODO: Turn on whenever ready to support encrypt-then-mac if (false) { - var mac = computeMac(ct, response.iv); + var mac = computeMac(ctBytes, ivBytes); cipherString = cipherString + '|' + mac; } @@ -126,7 +110,7 @@ angular }; _service.decrypt = function (encValue) { - if (!_service.getAes() || !_service.getAesWithMac()) { + if (!_service.getKey()) { throw 'AES encryption unavailable.'; } @@ -135,35 +119,33 @@ angular return ''; } - var ivBits = sjcl.codec.base64.toBits(encPieces[0]); - var ctBits = sjcl.codec.base64.toBits(encPieces[1]); + var ivBytes = forge.util.decode64(encPieces[0]); + var ctBytes = forge.util.decode64(encPieces[1]); var computedMac = null; if (encPieces.length === 3) { - computedMac = computeMac(ctBits, ivBits); + computedMac = computeMac(ctBytes, ivBytes); if (computedMac !== encPieces[2]) { console.error('MAC failed.'); return ''; } } - var decBits = sjcl.mode.cbc.decrypt(computedMac ? _service.getAesWithMac() : _service.getAes(), ctBits, ivBits, null); - return sjcl.codec.utf8String.fromBits(decBits); + var ctBuffer = forge.util.createBuffer(ctBytes); + var decipher = forge.cipher.createDecipher('AES-CBC', computedMac ? _service.getEncKey() : _service.getKey()); + decipher.start({ iv: ivBytes }); + decipher.update(ctBuffer); + decipher.finish(); + + return decipher.output.toString('utf8'); }; - function computeMac(ct, iv) { - if (typeof ct === 'string') { - ct = sjcl.codec.base64.toBits(ct); - } - if (typeof iv === 'string') { - iv = sjcl.codec.base64.toBits(iv); - } - - var macKey = _service.getMacKey(); - var hmac = new sjcl.misc.hmac(macKey, sjcl.hash.sha256); - var bits = iv.concat(ct); - var mac = hmac.encrypt(bits); - return sjcl.codec.base64.fromBits(mac); + function computeMac(ct, iv, macKey) { + var hmac = forge.hmac.create(); + hmac.start('sha256', macKey || _service.getMacKey()); + hmac.update(iv + ct); + var mac = hmac.digest(); + return forge.util.encode64(mac.getBytes()); } return _service; diff --git a/src/index.html b/src/index.html index 5e6a4fc2ed..b91b336400 100644 --- a/src/index.html +++ b/src/index.html @@ -55,10 +55,7 @@ - - - - +